how2heap是由shellphish团队制作的堆利用教程,介绍了多种堆利用技术,后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指令-CSDN博客
pwndbg> r
Starting program: /ctf/work/how2heap/first_fit
尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x603010
第二个 b = malloc(0x256) 在: 0x603530
我们可以继续分配
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x603010 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x603010
第三次 c = malloc(0x500) 在: 0x603010
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
第三次申请的 c 0x603010 指向 CCCCCCCC
第一次申请的 a 0x603010 指向 CCCCCCCC
可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 "CCCCCCCC"
[Inferior 1 (process 60) exited normally]
pwndbg>
first_fit程序
这个程序并不展示如何攻击,而是展示glibc的一种分配规则。glibc 使用一种first-fit算法去选择一个free-chunk。如果存在一个free-chunk并且足够大的话,malloc会优先选取这个chunk。这种机制就可以在被利用于use after free(简称 uaf) 的情形中.
使用命令gcc -g first_fit.c -o first_fit
编译,-g参数会保留代码的文字信息,便于调试。以下为first_fit.c程序
#include
#include
#include
int main()
{
fprintf(stderr, "尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制\n");
fprintf(stderr, "glibc 使用首次适应算法选择空闲的堆块\n");
fprintf(stderr, "如果有一个空闲堆块且足够大,那么 malloc 将选择它\n");
fprintf(stderr, "如果存在 use-after-free 的情况那可以利用这一特性\n");
fprintf(stderr, "首先申请两个比较大的 chunk\n");
char* a = malloc(0x512);
char* b = malloc(0x256);
char* c;
fprintf(stderr, "第一个 a = malloc(0x512) 在: %p\n", a);
fprintf(stderr, "第二个 b = malloc(0x256) 在: %p\n", b);
fprintf(stderr, "我们可以继续分配\n");
fprintf(stderr, "现在我们把 \"AAAAAAAA\" 这个字符串写到 a 那里 \n");
strcpy(a, "AAAAAAAA");
fprintf(stderr, "第一次申请的 %p 指向 %s\n", a, a);
fprintf(stderr, "接下来 free 掉第一个...\n");
free(a);
fprintf(stderr, "接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: %p\n", a);
c = malloc(0x500);
fprintf(stderr, "第三次 c = malloc(0x500) 在: %p\n", c);
fprintf(stderr, "我们这次往里写一串 \"CCCCCCCC\" 到刚申请的 c 中\n");
strcpy(c, "CCCCCCCC");
fprintf(stderr, "第三次申请的 c %p 指向 %s\n", c, c);
fprintf(stderr, "第一次申请的 a %p 指向 %s\n", a, a);
fprintf(stderr, "可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 \"CCCCCCCC\"\n");
}
root@pwn_test1604:/ctf/work/how2heap# gdb ./first_fit
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 171 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./first_fit...done.
pwndbg> b 22
Breakpoint 1 at 0x40078b: file first_fit.c, line 22.
pwndbg> b 35
Breakpoint 2 at 0x400893: file first_fit.c, line 35.
pwndbg> r
Starting program: /ctf/work/how2heap/first_fit
尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x603010
第二个 b = malloc(0x256) 在: 0x603530
我们可以继续分配
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
Breakpoint 1, main () at first_fit.c:22
22 fprintf(stderr, "第一次申请的 %p 指向 %s\n", a, a);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0x603010 ◂— 'AAAAAAAA'
RBX 0x0
RCX 0x7ffff7b042c0 (__write_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0
RDI 0x2
RSI 0x4141414141414141 ('AAAAAAAA')
R8 0x3b
R9 0x7ffff7dd2540 (_IO_2_1_stderr_) ◂— 0xfbad2887
R10 0x1
R11 0x246
R12 0x400550 (_start) ◂— xor ebp, ebp
R13 0x7fffffffe6b0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffe5d0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffe5b0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
RIP 0x40078b (main+325) ◂— mov rax, qword ptr [rip + 0x2018ce]
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x40078b mov rax, qword ptr [rip + 0x2018ce] <0x602060>
0x400792 mov rcx, qword ptr [rbp - 0x18]
0x400796 mov rdx, qword ptr [rbp - 0x18]
0x40079a mov esi, 0x400b38
0x40079f mov rdi, rax
0x4007a2 mov eax, 0
0x4007a7 call fprintf@plt <0x400510>
0x4007ac mov rax, qword ptr [rip + 0x2018ad] <0x602060>
0x4007b3 mov rcx, rax
0x4007b6 mov edx, 0x1f
0x4007bb mov esi, 1
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/first_fit.c
17 fprintf(stderr, "第一个 a = malloc(0x512) 在: %p\n", a);
18 fprintf(stderr, "第二个 b = malloc(0x256) 在: %p\n", b);
19 fprintf(stderr, "我们可以继续分配\n");
20 fprintf(stderr, "现在我们把 \"AAAAAAAA\" 这个字符串写到 a 那里 \n");
21 strcpy(a, "AAAAAAAA");
► 22 fprintf(stderr, "第一次申请的 %p 指向 %s\n", a, a);
23
24 fprintf(stderr, "接下来 free 掉第一个...\n");
25 free(a);
26
27 fprintf(stderr, "接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: %p\n", a);
─────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe5b0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
01:0008│ 0x7fffffffe5b8 —▸ 0x603010 ◂— 'AAAAAAAA'
02:0010│ 0x7fffffffe5c0 —▸ 0x603530 ◂— 0x0
03:0018│ 0x7fffffffe5c8 ◂— 0x0
04:0020│ rbp 0x7fffffffe5d0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
05:0028│ 0x7fffffffe5d8 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax
06:0030│ 0x7fffffffe5e0 —▸ 0x7fffffffe6b8 —▸ 0x7fffffffe8e3 ◂— '/ctf/work/how2heap/first_fit'
... ↓
───────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 40078b main+325
f 1 7ffff7a2d830 __libc_start_main+240
Breakpoint /ctf/work/how2heap/first_fit.c:22
pwndbg> parseheap
addr prev size status fd bk
0x603000 0x0 0x520 Used None None
0x603520 0x0 0x260 Used None None
pwndbg> x/10gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000521
0x603010: 0x4141414141414141 0x0000000000000000
0x603020: 0x0000000000000000 0x0000000000000000
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
第一次申请的 0x603010 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x603010
第三次 c = malloc(0x500) 在: 0x603010
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
第三次申请的 c 0x603010 指向 CCCCCCCC
第一次申请的 a 0x603010 指向 CCCCCCCC
Breakpoint 2, main () at first_fit.c:35
35 fprintf(stderr, "可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 \"CCCCCCCC\"\n");
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0x2e
RBX 0x0
RCX 0x7ffff7b042c0 (__write_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0
RDI 0x2
RSI 0x7fffffffbf20 ◂— 0xace680b8e4acace7
R8 0x7ffff7feb700 ◂— 0x7ffff7feb700
R9 0x2e
R10 0x8
R11 0x246
R12 0x400550 (_start) ◂— xor ebp, ebp
R13 0x7fffffffe6b0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffe5d0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffe5b0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
RIP 0x400893 (main+589) ◂— mov rax, qword ptr [rip + 0x2017c6]
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x400893 mov rax, qword ptr [rip + 0x2017c6] <0x602060>
0x40089a mov rcx, rax
0x40089d mov edx, 0x54
0x4008a2 mov esi, 1
0x4008a7 mov edi, 0x400ca0
0x4008ac call fwrite@plt <0x400530>
0x4008b1 mov eax, 0
0x4008b6 leave
0x4008b7 ret
0x4008b8 nop dword ptr [rax + rax]
0x4008c0 <__libc_csu_init> push r15
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/first_fit.c
30 fprintf(stderr, "第三次 c = malloc(0x500) 在: %p\n", c);
31 fprintf(stderr, "我们这次往里写一串 \"CCCCCCCC\" 到刚申请的 c 中\n");
32 strcpy(c, "CCCCCCCC");
33 fprintf(stderr, "第三次申请的 c %p 指向 %s\n", c, c);
34 fprintf(stderr, "第一次申请的 a %p 指向 %s\n", a, a);
► 35 fprintf(stderr, "可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 \"CCCCCCCC\"\n");
36 }
─────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe5b0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
01:0008│ 0x7fffffffe5b8 —▸ 0x603010 ◂— 'CCCCCCCC'
02:0010│ 0x7fffffffe5c0 —▸ 0x603530 ◂— 0x0
03:0018│ 0x7fffffffe5c8 —▸ 0x603010 ◂— 'CCCCCCCC'
04:0020│ rbp 0x7fffffffe5d0 —▸ 0x4008c0 (__libc_csu_init) ◂— push r15
05:0028│ 0x7fffffffe5d8 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax
06:0030│ 0x7fffffffe5e0 —▸ 0x7fffffffe6b8 —▸ 0x7fffffffe8e3 ◂— '/ctf/work/how2heap/first_fit'
... ↓
───────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 400893 main+589
f 1 7ffff7a2d830 __libc_start_main+240
Breakpoint /ctf/work/how2heap/first_fit.c:35
pwndbg> x/10gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000521
0x603010: 0x4343434343434343 0x00007ffff7dd1f00
0x603020: 0x0000000000603000 0x0000000000603000
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
pwndbg>
加上参数重新编译一个版本:gcc -fsanitize=address -g first_fit.c -o first_fit1,会提示有个 use-after-free 漏洞
root@pwn_test1604:/ctf/work/how2heap# gcc -fsanitize=address -g first_fit.c -o first_fit1
root@pwn_test1604:/ctf/work/how2heap# gdb ./first_fit1
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 171 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./first_fit1...done.
pwndbg> r
Starting program: /ctf/work/how2heap/first_fit1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x61a00001f280
第二个 b = malloc(0x256) 在: 0x61600000fc80
我们可以继续分配
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x61a00001f280 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x61a00001f280
第三次 c = malloc(0x500) 在: 0x61a00001ec80
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
第三次申请的 c 0x61a00001ec80 指向 CCCCCCCC
=================================================================
==72==ERROR: AddressSanitizer: heap-use-after-free on address 0x61a00001f280 at pc 0x7ffff6eca1e9 bp 0x7fffffffe460 sp 0x7fffffffdbd8
READ of size 2 at 0x61a00001f280 thread T0
#0 0x7ffff6eca1e8 (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x601e8)
#1 0x7ffff6ecabcc in vfprintf (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x60bcc)
#2 0x7ffff6ecacf9 in fprintf (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x60cf9)
#3 0x400db4 in main /ctf/work/how2heap/first_fit.c:34
#4 0x7ffff6ac082f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#5 0x400878 in _start (/ctf/work/how2heap/first_fit1+0x400878)
0x61a00001f280 is located 0 bytes inside of 1298-byte region [0x61a00001f280,0x61a00001f792)
freed by thread T0 here:
#0 0x7ffff6f022ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x400c4c in main /ctf/work/how2heap/first_fit.c:25
#2 0x7ffff6ac082f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
previously allocated by thread T0 here:
#0 0x7ffff6f02602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400a97 in main /ctf/work/how2heap/first_fit.c:13
#2 0x7ffff6ac082f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
SUMMARY: AddressSanitizer: heap-use-after-free ??:0 ??
Shadow bytes around the buggy address:
0x0c347fffbe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c347fffbe10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c347fffbe20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c347fffbe30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c347fffbe40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c347fffbe50:[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c347fffbe60: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c347fffbe70: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c347fffbe80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c347fffbe90: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c347fffbea0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
==72==ABORTING
[Inferior 1 (process 72) exited with code 01]
pwndbg>
UAF 漏洞简单来说就是第一次申请的内存释放之后,没有进行内存回收,下次申请的时候还能申请到这一块内存,导致我们可以用以前的内存指针来访问修改过的内存。
来看一下一个简单的 UAF 的利用的例子。
gcc -g uaf.c -o uaf
#include
#include
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
system(command);
}
void echo(char content[])
{
printf("%s",content);
}
int main()
{
func_ptr *p1=(func_ptr*)malloc(0x20);
printf("申请了4个int大小的内存");
printf("p1 的地址: %p\n",p1);
p1[1]=echo;
printf("把p1[1]赋值为echo函数,然后打印出\"hello world\"");
p1[1]("hello world\n");
printf("free 掉 p1");
free(p1);
printf("因为并没有置为null,所以p1[1]仍然是echo函数,仍然可以输出打印了\"hello again\"");
p1[1]("hello again\n");
printf("接下来再去malloc一个p2,会把释放掉的p1给分配出来,可以看到他俩是同一地址的");
func_ptr *p2=(func_ptr*)malloc(0x20);
printf("p2 的地址: %p\n",p2);
printf("p1 的地址: %p\n",p1);
printf("然后把p2[1]给改成evil_fuc也就是system函数");
p2[1]=evil_fuc;
printf("传参调用");
p1[1]("/bin/sh");
return 0;
}
依然使用之前的编译命令,然后动态调试看一下,首先申请了一个chunk,把那个p1[1]改成了echo函数的地址。 free掉之后再申请一个大小相同的p2,这时候会把之前p1的内存区域分配给p2,也就是说可以用p2来控制p1的内容了
root@pwn_test1604:/ctf/work/how2heap# gdb ./uaf
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 171 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./uaf...done.
pwndbg> b 18
Breakpoint 1 at 0x400680: file uaf.c, line 18.
pwndbg> b 30
Breakpoint 2 at 0x400744: file uaf.c, line 30.
pwndbg> r
Starting program: /ctf/work/how2heap/uaf
申请了4个int大小的内存p1 的地址: 0x602010
Breakpoint 1, main () at uaf.c:18
18 printf("把p1[1]赋值为echo函数,然后打印出\"hello world\"");
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0x602018 —▸ 0x400611 (echo) ◂— push rbp
RBX 0x0
RCX 0x7fffffe9
RDX 0x7ffff7dd3780 (_IO_stdfile_1_lock) ◂— 0x0
RDI 0x1
RSI 0x1
R8 0x0
R9 0x17
R10 0x0
R11 0x246
R12 0x400500 (_start) ◂— xor ebp, ebp
R13 0x7fffffffe6b0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffe5d0 —▸ 0x400770 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffe5c0 —▸ 0x602010 ◂— 0x0
RIP 0x400680 (main+74) ◂— mov edi, 0x400838
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x400680 mov edi, 0x400838
0x400685 mov eax, 0
0x40068a call printf@plt <0x4004c0>
0x40068f mov rax, qword ptr [rbp - 0x10]
0x400693 add rax, 8
0x400697 mov rax, qword ptr [rax]
0x40069a mov edi, 0x400873
0x40069f call rax
0x4006a1 mov edi, 0x400880
0x4006a6 mov eax, 0
0x4006ab call printf@plt <0x4004c0>
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/uaf.c
13 {
14 func_ptr *p1=(func_ptr*)malloc(0x20);
15 printf("申请了4个int大小的内存");
16 printf("p1 的地址: %p\n",p1);
17 p1[1]=echo;
► 18 printf("把p1[1]赋值为echo函数,然后打印出\"hello world\"");
19 p1[1]("hello world\n");
20 printf("free 掉 p1");
21 free(p1);
22 printf("因为并没有置为null,所以p1[1]仍然是echo函数,仍然可以输出打印了\"hello again\"");
23 p1[1]("hello again\n");
─────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe5c0 —▸ 0x602010 ◂— 0x0
01:0008│ 0x7fffffffe5c8 ◂— 0x0
02:0010│ rbp 0x7fffffffe5d0 —▸ 0x400770 (__libc_csu_init) ◂— push r15
03:0018│ 0x7fffffffe5d8 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax
04:0020│ 0x7fffffffe5e0 —▸ 0x7fffffffe6b8 —▸ 0x7fffffffe8ef ◂— '/ctf/work/how2heap/uaf'
... ↓
06:0030│ 0x7fffffffe5f0 ◂— 0x1f7b99608
07:0038│ 0x7fffffffe5f8 —▸ 0x400636 (main) ◂— push rbp
───────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 400680 main+74
f 1 7ffff7a2d830 __libc_start_main+240
Breakpoint /ctf/work/how2heap/uaf.c:18
pwndbg> heap
heapbase : 0x602000
pwndbg> x/10gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000031
0x602010: 0x0000000000000000 0x0000000000400611
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000411
0x602040: 0xbae4b7afe8b394e7 0x746e69aab8e43486
pwndbg> x/4gx 0x0000000000400611
0x400611 : 0x10ec8348e5894855 0xf8458b48f87d8948
0x400621 : 0x004007f8bfc68948 0xfe8de800000000b8
pwndbg> c
Continuing.
把p1[1]赋值为echo函数,然后打印出"hello world"hello world
free 掉 p1因为并没有置为null,所以p1[1]仍然是echo函数,仍然可以输出打印了"hello again"hello again
接下来再去malloc一个p2,会把释放掉的p1给分配出来,可以看到他俩是同一地址的p2 的地址: 0x602010
p1 的地址: 0x602010
Breakpoint 2, main () at uaf.c:30
30 printf("传参调用");
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0x602018 —▸ 0x4005f6 (evil_fuc) ◂— push rbp
RBX 0x0
RCX 0x34
RDX 0x7ffff7dd3780 (_IO_stdfile_1_lock) ◂— 0x0
RDI 0x602074 ◂— 0x85e98688e599bbe7
RSI 0x4009bc ◂— add ah, ah
R8 0xb095e6bd87e56d65
R9 0x34
R10 0xe46375665f6c6976
R11 0x246
R12 0x400500 (_start) ◂— xor ebp, ebp
R13 0x7fffffffe6b0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffe5d0 —▸ 0x400770 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffe5c0 —▸ 0x602010 ◂— 0x0
RIP 0x400744 (main+270) ◂— mov edi, 0x4009bd
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x400744 mov edi, 0x4009bd
0x400749 mov eax, 0
0x40074e call printf@plt <0x4004c0>
0x400753 mov rax, qword ptr [rbp - 0x10]
0x400757 add rax, 8
0x40075b mov rax, qword ptr [rax]
0x40075e mov edi, 0x4009ca
0x400763 call rax
0x400765 mov eax, 0
0x40076a leave
0x40076b ret
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/uaf.c
25 func_ptr *p2=(func_ptr*)malloc(0x20);
26 printf("p2 的地址: %p\n",p2);
27 printf("p1 的地址: %p\n",p1);
28 printf("然后把p2[1]给改成evil_fuc也就是system函数");
29 p2[1]=evil_fuc;
► 30 printf("传参调用");
31 p1[1]("/bin/sh");
32 return 0;
33 }
─────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe5c0 —▸ 0x602010 ◂— 0x0
... ↓
02:0010│ rbp 0x7fffffffe5d0 —▸ 0x400770 (__libc_csu_init) ◂— push r15
03:0018│ 0x7fffffffe5d8 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax
04:0020│ 0x7fffffffe5e0 —▸ 0x7fffffffe6b8 —▸ 0x7fffffffe8ef ◂— '/ctf/work/how2heap/uaf'
... ↓
06:0030│ 0x7fffffffe5f0 ◂— 0x1f7b99608
07:0038│ 0x7fffffffe5f8 —▸ 0x400636 (main) ◂— push rbp
───────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 400744 main+270
f 1 7ffff7a2d830 __libc_start_main+240
Breakpoint /ctf/work/how2heap/uaf.c:30
pwndbg> heap
heapbase : 0x602000
pwndbg> x/10gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000031
0x602010: 0x0000000000000000 0x00000000004005f6
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000411
0x602040: 0x8ae68e90e5b684e7 0xbbe75d315b32708a
pwndbg> p evil_fuc
$1 = {void (char *)} 0x4005f6
pwndbg>
【PWN】how2heap | 狼组安全团队公开知识库