pwnable.tw unexploitable writeup

花了将近一天半的时间研究这一题,因为最近在熟练srop技术,所以在看到pwnbale.kr上的那道unexploitable之后,想趁热打铁来试试这道500p的题目。收货颇丰啊~~~ 虽然看了网上的思路,但exp还是自己搞了出来,还是有点小小成就感。

题目的二进制文件和libc自己去网站上找吧pwnable.tw.

首先拿到题目我们看到没有了syscall,变为了call read,这个变化导致题目直接上了一个档次。使我们的思路陷入死活同:我们需要调用system(’/bin/sh’)的话,需要先泄露libc,但是程序本身没有输出函数,我们又只能通过系统调用来调用write等函数,可是又没有syscall;或者我们需要调用execve(‘/bin/sh’),仍然需要syscall来触发。也就是说没有syscall我们什么都干不了。。。。

  1. 这个时候就需要独特的思路和脑洞。。。这也是为什么值500p的原因。。。。我们动态调试read函数内部会发现其第三条语句就是syscall,而且read()和syscall的LSB相等!!如果先将read()最后一位改为syscall的,再搭配64位通用gadgets,call [read_got]就等于jmp syscall。我们获得了syscall。
  2. 之后思路分为:
    1. 先利用 pop rbp; ret; 和 leave; ret; 来劫持栈到我们选定的bss段(实际上是加载后的空闲段)。
    2. 然后用64位通用gadgets来改写read()的LSB(这一步过后就没法调用read()了,所以我们得用其他函数来控制rax),之后rax的值为1,正好调用write(),再利用write()出59个字符使rax为59,再调用exevce()(其参数应早一步在bss段布置好) (这种思路最简单,已成功
    3. 或者用write()泄露出Libc,然后调用system(),但是这里需要再输入一次,所以我们必须利用write()来控制sleep(),再利用sleep()返回0,来调用read()写入system()真实地址。(这种思路未成功,因为不知道nanosleep()的返回值具体机制,但是似乎没有调用之后read()

思考和收货:

  1. 是不是所有的系统调用函数内部都是通过syscall实现的。(我猜应该是的)

  2. gdb的print fun功能(直接打印函数fun的真实地址)和peda的find ‘‘string’’ addr1 addr2(在addr1和addr2之间找string的位置。注意双引号不能换成单引号!)

  3. 劫持栈的gadget:p64(pop_rbp)+p64(fake_stack_addr)+p64(leave_addr)

  4. 64位通用gadgets的熟悉运用,定义函数(大致结构,具体因题而异):

    #part1为地址大的,part2为地址小的。
    def call_fun(fun_addr,arg1,arg2,arg3):
        payload = p64(part1) 
        payload+= p64(0)
        payload+= p64(1)
        payload+= p64(fun_addr)
        payload+= p64(arg1)
        payload+= p64(arg2)
        payload+= p64(arg3)
        payload+= p64(part2)
        payload+= 'a'*0x38
        return payload
    
  5. p = process(’./unexploitable’,env={‘LD_PRELOAD’ :’./libc_64.so.6’}) 用或不用LD_PRELOAD会影响动态链接加载的函数真实地址,最好看ida,最后一位不会变的。因为分页分配内存。

  6. 如果两次read()之间没有recv()的话,需要加一个sleep(),或者raw_input()来暂停一下,不然会有吞字的现象。

  7. 因为内存的分页分配机制,0x1000为最小单位,所以我们会发现我们的程序实际上只到0x601070(比如),但之后到0x602000都是可写入的空的内存,我们可以任意使用(权限足够的话)。

  8. one_gadgets 可以写入一个数组。orz.

上一篇:2019西湖论剑网络安全技能大赛(大学生组)部分WriteUp


下一篇:盘点Windows 8.1中隐藏着的25个秘密功能