从零开始学howtoheap:理解glibc分配机制和UAF漏洞利用

2024-02-09 18:20

本文主要是介绍从零开始学howtoheap:理解glibc分配机制和UAF漏洞利用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

how2heap是由shellphish团队制作的堆利用教程,介绍了多种堆利用技术,后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指令-CSDN博客

1.理解glibc分配机制

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> 
 

2.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 <stdio.h>
#include <stdlib.h>
#include <string.h>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");
}

3.调试程序 

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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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  0x0RCX  0x7ffff7b042c0 (__write_nocancel+7) ◂— cmp    rax, -0xfffRDX  0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0RDI  0x2RSI  0x4141414141414141 ('AAAAAAAA')R8   0x3bR9   0x7ffff7dd2540 (_IO_2_1_stderr_) ◂— 0xfbad2887R10  0x1R11  0x246R12  0x400550 (_start) ◂— xor    ebp, ebpR13  0x7fffffffe6b0 ◂— 0x1R14  0x0R15  0x0RBP  0x7fffffffe5d0 —▸ 0x4008c0 (__libc_csu_init) ◂— push   r15RSP  0x7fffffffe5b0 —▸ 0x4008c0 (__libc_csu_init) ◂— push   r15RIP  0x40078b (main+325) ◂— mov    rax, qword ptr [rip + 0x2018ce]
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────► 0x40078b <main+325>    mov    rax, qword ptr [rip + 0x2018ce] <0x602060>0x400792 <main+332>    mov    rcx, qword ptr [rbp - 0x18]0x400796 <main+336>    mov    rdx, qword ptr [rbp - 0x18]0x40079a <main+340>    mov    esi, 0x400b380x40079f <main+345>    mov    rdi, rax0x4007a2 <main+348>    mov    eax, 00x4007a7 <main+353>    call   fprintf@plt <0x400510>0x4007ac <main+358>    mov    rax, qword ptr [rip + 0x2018ad] <0x602060>0x4007b3 <main+365>    mov    rcx, rax0x4007b6 <main+368>    mov    edx, 0x1f0x4007bb <main+373>    mov    esi, 1
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/first_fit.c17     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+325f 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 指向 CCCCCCCCBreakpoint 2, main () at first_fit.c:35
35          fprintf(stderr, "可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 \"CCCCCCCC\"\n");
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────RAX  0x2eRBX  0x0RCX  0x7ffff7b042c0 (__write_nocancel+7) ◂— cmp    rax, -0xfffRDX  0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0RDI  0x2RSI  0x7fffffffbf20 ◂— 0xace680b8e4acace7R8   0x7ffff7feb700 ◂— 0x7ffff7feb700R9   0x2eR10  0x8R11  0x246R12  0x400550 (_start) ◂— xor    ebp, ebpR13  0x7fffffffe6b0 ◂— 0x1R14  0x0R15  0x0RBP  0x7fffffffe5d0 —▸ 0x4008c0 (__libc_csu_init) ◂— push   r15RSP  0x7fffffffe5b0 —▸ 0x4008c0 (__libc_csu_init) ◂— push   r15RIP  0x400893 (main+589) ◂— mov    rax, qword ptr [rip + 0x2017c6]
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────► 0x400893 <main+589>           mov    rax, qword ptr [rip + 0x2017c6] <0x602060>0x40089a <main+596>           mov    rcx, rax0x40089d <main+599>           mov    edx, 0x540x4008a2 <main+604>           mov    esi, 10x4008a7 <main+609>           mov    edi, 0x400ca00x4008ac <main+614>           call   fwrite@plt <0x400530>0x4008b1 <main+619>           mov    eax, 00x4008b6 <main+624>           leave  0x4008b7 <main+625>           ret    0x4008b8                      nop    dword ptr [rax + rax]0x4008c0 <__libc_csu_init>    push   r15
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/first_fit.c30     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+589f 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> 

 4.first_fit1程序

加上参数重新编译一个版本: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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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 000x0c347fffbe10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x0c347fffbe20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x0c347fffbe30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c347fffbe40: 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 fd0x0c347fffbe60: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c347fffbe70: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c347fffbe80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c347fffbe90: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c347fffbea0: 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:           00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone:       faHeap right redzone:      fbFreed heap region:       fdStack left redzone:      f1Stack mid redzone:       f2Stack right redzone:     f3Stack partial redzone:   f4Stack after return:      f5Stack use after scope:   f8Global redzone:          f9Global init order:       f6Poisoned by user:        f7Container overflow:      fcArray cookie:            acIntra object redzone:    bbASan internal:           fe
==72==ABORTING
[Inferior 1 (process 72) exited with code 01]
pwndbg> 

5.UAF程序

UAF 漏洞简单来说就是第一次申请的内存释放之后,没有进行内存回收,下次申请的时候还能申请到这一块内存,导致我们可以用以前的内存指针来访问修改过的内存。

​ 来看一下一个简单的 UAF 的利用的例子。

gcc -g uaf.c -o uaf

#include <stdio.h>
#include <stdlib.h>
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;
}

6.UAF调试

依然使用之前的编译命令,然后动态调试看一下,首先申请了一个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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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 的地址: 0x602010Breakpoint 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   rbpRBX  0x0RCX  0x7fffffe9RDX  0x7ffff7dd3780 (_IO_stdfile_1_lock) ◂— 0x0RDI  0x1RSI  0x1R8   0x0R9   0x17R10  0x0R11  0x246R12  0x400500 (_start) ◂— xor    ebp, ebpR13  0x7fffffffe6b0 ◂— 0x1R14  0x0R15  0x0RBP  0x7fffffffe5d0 —▸ 0x400770 (__libc_csu_init) ◂— push   r15RSP  0x7fffffffe5c0 —▸ 0x602010 ◂— 0x0RIP  0x400680 (main+74) ◂— mov    edi, 0x400838
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────► 0x400680 <main+74>     mov    edi, 0x4008380x400685 <main+79>     mov    eax, 00x40068a <main+84>     call   printf@plt <0x4004c0>0x40068f <main+89>     mov    rax, qword ptr [rbp - 0x10]0x400693 <main+93>     add    rax, 80x400697 <main+97>     mov    rax, qword ptr [rax]0x40069a <main+100>    mov    edi, 0x4008730x40069f <main+105>    call   rax0x4006a1 <main+107>    mov    edi, 0x4008800x4006a6 <main+112>    mov    eax, 00x4006ab <main+117>    call   printf@plt <0x4004c0>
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/uaf.c13 {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+74f 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 <echo>:        0x10ec8348e5894855      0xf8458b48f87d8948
0x400621 <echo+16>:     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 的地址: 0x602010Breakpoint 2, main () at uaf.c:30
30          printf("传参调用");
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────RAX  0x602018 —▸ 0x4005f6 (evil_fuc) ◂— push   rbpRBX  0x0RCX  0x34RDX  0x7ffff7dd3780 (_IO_stdfile_1_lock) ◂— 0x0RDI  0x602074 ◂— 0x85e98688e599bbe7RSI  0x4009bc ◂— add    ah, ahR8   0xb095e6bd87e56d65R9   0x34R10  0xe46375665f6c6976R11  0x246R12  0x400500 (_start) ◂— xor    ebp, ebpR13  0x7fffffffe6b0 ◂— 0x1R14  0x0R15  0x0RBP  0x7fffffffe5d0 —▸ 0x400770 (__libc_csu_init) ◂— push   r15RSP  0x7fffffffe5c0 —▸ 0x602010 ◂— 0x0RIP  0x400744 (main+270) ◂— mov    edi, 0x4009bd
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────► 0x400744 <main+270>    mov    edi, 0x4009bd0x400749 <main+275>    mov    eax, 00x40074e <main+280>    call   printf@plt <0x4004c0>0x400753 <main+285>    mov    rax, qword ptr [rbp - 0x10]0x400757 <main+289>    add    rax, 80x40075b <main+293>    mov    rax, qword ptr [rax]0x40075e <main+296>    mov    edi, 0x4009ca0x400763 <main+301>    call   rax0x400765 <main+303>    mov    eax, 00x40076a <main+308>    leave  0x40076b <main+309>    ret    
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/uaf.c25     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+270f 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 <evil_fuc>
pwndbg> 

7.参考资料

【PWN】how2heap | 狼组安全团队公开知识库

这篇关于从零开始学howtoheap:理解glibc分配机制和UAF漏洞利用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/694970

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

理解java虚拟机内存收集

学习《深入理解Java虚拟机》时个人的理解笔记 1、为什么要去了解垃圾收集和内存回收技术? 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。 2、“哲学三问”内存收集 what?when?how? 那些内存需要回收?什么时候回收?如何回收? 这是一个整体的问题,确定了什么状态的内存可以