zoukankan      html  css  js  c++  java
  • 堆栈攻击总结

    Structure

    可以用来劫持控制流的关键点用黑体加粗

    地址从低到高
    .Text Gadgets
    .Got Function Pointers
    .Bss File Pointers
    New Stack Data
    Cannary
    Saved Registers
    Return Address
    Old Stack
    Old Chunk
    New Chunk Prev Size(P=0)/Prev Data(P=1)
    Size(高地址)&AMP(低地址)
    Data Fd
    Bk
    Top Chunk
    Libc Hook Pointers
    One Gadgets
    Vsyscall Gadgets

    标志位

    A:NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于。

    M:IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。

    P:PREV_INUSE,记录前一个 chunk 块是否被分配。

    Top chunk

    当所有的bin都无法满足用户请求的大小时,分割top chunk产生新chunk

    初始情况下,我们可以将 unsorted chunk 作为 top chunk。

    main_arena

    与 thread 不同的是,main_arena 并不在申请的 heap 中,而是一个全局变量,在 libc.so 的数据段。

    Startup

    Linux环境下程序的加载过程

    Linux x86 Program Start Up

    Command

    ls -ali 查看inode

    exec 1>&2 恢复重定向

    Shell

    命令

    onegadget,system,execve

    跳板

    malloc_hook,free_hook,got,rop,return addr,(虚表,堆喷)

    Libc

    泄露:unsorted bin UAF(超大chunk调用mmap),读取got(无leak需要先构造puts/printf),任意读->DynELF,构造printf读取栈上的__libc_start_main返回地址

    Printf

    可控格式化参数,利用%n%p进行任意读/写

    Stack

    栈中shellcode

    ret gadget->jmp rsp跳板

    bss中shellcode

    控制ret addr指向bss

    ret位置

    shellcode/onegadget(需要先leak libc)/backdoor

    执行rop链

    rop(ret=pop rip)->栈溢出/栈迁移->指向rop链

    Rop

    先通过gadget控制寄存器,再调用依赖寄存器传参的函数/onegadget

    Gadget1(pop Value到寄存器,ret=pop rip)

    Value1

    Value2

    Gadget2(pop Value到寄存器,ret=pop rip)

    Value3

    Func(execve依赖寄存器/system@plt依赖寄存器和栈/onegadget依赖寄存器或无依赖)

    Fake return addr(对于依赖栈的函数需要伪造栈结构,call=push rip)

    Arg1

    Arg2

    栈溢出

    通过栈溢出,从return addr处开始构造rop链,ret时rsp指向rop链

    栈迁移

    修改备份的帧指针,使其指向已经构造好的rop链,第一次leave时pop rbp,第二次leave时mov rsp,rbp,ret时rsp指向rop链

    vsyscall

    相当于ret,可以用来在rop链中占位

    Ret2dl_resolve(解析libc任意函数地址)

    Stage1:migrate

    Rop1:padding+read(base)+migrate(base)

    Rop2:write(str)+str

    Stage2:migrate+dl_resolve

    Rop1:padding+read(base)+migrate(base)

    Rop2:plt0(str)+str

    Stage3:migrate+dl_resolve+fake_index

    Rop1:padding+read(base)+migrate(base)

    Rop2:plt0(str)+fake_reloc->write_symbol@dynsym+str

    Stage4:migrate+dl_resolve+fake_index+fake_symbol

    Rop1:padding+read(base)+migrate(base)

    Rop2:plt0(str)+fake_reloc->fake_symbol+fake_symbol->write_str@dynstr+str

    Stage5:migrate+dl_resolve+fake_index+fake_symbol+fake_str(fake_str=’write’)

    Rop1:padding+read(base)+migrate(base)

    Rop2:plt0(str)+fake_reloc->fake_symbol+fake_symbol->fake_str+fake_str+str

    Stage6:migrate+dl_resolve+fake_index+fake_symbol+fake_str(fake_str=’system’)

    Rop1:padding+read(base)+migrate(base)

    Rop2:plt0(str)+fake_reloc->fake_symbol+fake_symbol->fake_str+fake_str+str

    总结:plt[0]->dl_resolve->index->reloc->symbol->str

    plt0作用:执行dl_resolve来解析函数的地址并将地址填写到got,再执行got指向的函数
    fake_reloc = flat([write_got, r_info])
    write_got可以用原来的got,也在bss段随便找一个位置,只要对应位置可写就可以

    ctf-wiki用的是原来的got,ROP_LEVEL5用的是bss+0x200

    Heap基本知识

    任意分配

    Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块。如果更进一步修改 fd 指针,则能够实现任意地址分配 chunk。

    House of Spirit 在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。

    当然,也存在直接UAF就可以修改fd指针的情况。

    堆地址对齐0x10

    Tcache

    Lib>2.26:第一次malloc在申请目标chunk之前,会先申请一个大小为0x251的tcache_perthread_struct、输入缓冲区和输出缓冲区(后两个可以通过setbuf关闭)

    chunk 分配位置

    栈:控制返回地址等关键数据。

    libc:使用字节错位来绕过 size 域的检验,实现直接分配 fastbin 到_malloc_hook的位置来控制程序流程。

    与chunk有关的功能

    堆在编辑的时候有溢出:直接溢出改fd

    堆在新建的时候有溢出:先new多个chunk进行内存布局,再用free和new溢出改后面chunk的fd

    堆无溢出:double free,第一次申请改fd

    堆有溢出且程序有指针列表:Unlink修改指针列表,从而达到任意读写

    Heap攻击流程

    从漏洞成因的角度入手

    悬挂指针/越界访问

    从指针控制的角度入手

    libc/got/ret/chunk指针列表/可读可写可操作可执行指针

    指针操作

    malloc(sz) 申请ptr,并修改*ptr

    read(ptr) 输出*ptr

    edit(ptr) 修改*ptr

    free(ptr) 依据*ptr

    ①unsorted bin,修改*ptr=&libc

    ②fast bin改fd(堆溢出/悬挂指针:直接改或者间接用double free改),同时*fd满足一定条件,申请到ptr=fd

    ③unlink伪造chunk(堆溢出),同时存在ptr'指向伪造chunk,修改ptr'=&ptr'-3

    ④先free掉再用malloc,申请到同一个ptr(应对malloc和edit绑定的情况)

    ⑤先free掉本来不存在的chunk再用malloc,申请到同一个ptr(首先要有可控指针)

    从安全检测的角度入手

    '',0,负数,有/无符号比较,整数溢出,数组溢出(off by one),alloc/free时堆块内容没清空,字符串末尾没截断

    Main arena

    #define NBINS 128
    static struct malloc_state main_arena;
    struct malloc_state {
    mutex_t mutex;
    int flags;
    mfastbinptr      fastbins[NFASTBINS];
    mchunkptr        top;
    mchunkptr        last_remainder;
    mchunkptr        bins[NBINS * 2 - 2];
    unsigned int     binmap[BINMAPSIZE];
    struct malloc_state *next;
    INTERNAL_SIZE_T system_mem;
    INTERNAL_SIZE_T max_system_mem;
    };
    

    Chunk分类

    fast bins,unsorted bins,small bins,large bins

    main_arena.bins存储各个表头chunk的fd和bk(双向链表),包含unsorted bins(1),small bins(2~63),large bins(64~127)

    fastbins指向各个fast bins(单向链表)

    main_arena.bins结构

    bins[0]=bin1.fd bins[1]=bin1.bk
    bins[2]=bin2.fd bins[3]=bin2.bk
    

    后边的bins以此类推

    Unsorted bin UAF

    linux中使用free()进行内存释放时,不大于 max_fast (默认值为 64B)的 chunk 被释放后,首先会被放到 fast bins中,大于max_fast的chunk或者fast bins 中的空闲 chunk 合并后会被放入unsorted bin中(参考glibc内存管理ptmalloc源码分析一文)

    而在fastbin为空时,unsortbin的fd和bk指向自身main_arena中(双向链表的表头位于main_arena.bins),该地址的相对偏移值存放在libc.so中,可以通过use after free后打印出main_arena的实际地址,结合偏移值从而得到libc的加载地址。

    def offset_bin_main_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap(前面的prev_size和size)
    return offset
    
    unsortedbin_offset_main_arena = offset_bin_main_arena(0)
    main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
    libc_base = main_arena_addr - main_arena_offset(偏移值写在libc源码中)
    

    House of Einherjar(堆溢出)

    修改prev_size和P标志位->任意分配

    House of Force(堆溢出,任意大小malloc)

    修改top_chunk的size->任意分配

    House of Orange(无free产生unsorted bin)

    申请大于top chunk的chunk,会申请新的top chunk,原来的top chunk会被放入unsorted bin

    Unlink(堆溢出)

    修改prev_size、size的P位、fd和bk->修改某个指向chunk的指针指向其上面的地方->修改周围敏感数据及指针自身->任意写

    P Q地址相邻,在P的Data区填写构造假chunk F,包含prev_size size fd bk和next chunk's prev_size(也可以用Q的prev_size),并覆写Q的prev_size和size.prev_inuse

    从而使Free Q时让堆管理器认为F已经释放,于是把F Q合并,并将F从原来的链表unlink,又由于P的fd和bk是精心构造的,导致unlink的时候能够修改某个原本指向P的Data区(即F)的指针ptr重新指向&ptr-3,从而修改ptr周围的数据

    修改前

    ptr=&P.Data
    (F.bk)->fd=(F.fd)->bk=&ptr

    修改后

    ptr=&ptr-3

    堆地址对齐,空间复用,Unlink,off by one

    堆地址对齐0x10,如果申请大小size%0x10大于0且不大于8,则申请的chunk和下个chunk的prev_size形成空间复用,从而在off by one的时候可以覆盖到下个chunk的size.prev_inuse,进而构造Unlink

    Oreo(可控指针)

    Str溢出造成指针可控->

    任意读->泄露

    任意释放->任意分配->任意写

    控制next指针指向got表项,泄露libc base

    控制next指针指向bss,释放假chunk(Spirit),从而申请到包含msg指针的chunk,使msg指针指向got表

    控制got表项指向system,获得shell

    str溢出->改ptr->leak libc

    str溢出->改ptr->释放目标chunk->分配目标chunk->改ptr->改got->获得shell

    Search Engine(悬挂指针)

    悬挂指针->’’绕过检测->指针复用

    unsorted bin UAF->泄露

    fast bin UAF->程序无修改功能->Double free->任意写

    利用 unsorted bin 地址泄漏 libc 基地址

    利用 double free 构造 fastbin 循环链表

    分配 chunk 到 malloc_hook 附近,修改malloc_hook 为 one_gadget

    申请unsorted chunk(uc)->free->ptr没清零且校验可用绕过->leak libc

    申请a b c,均含'd'

    list:c->b->a->uc(已清零)->NULL

    delete 'd'

    y(c)y(b)y(a)

    0x70bin:a->b->c->NULL

    list:c(已清零)->b(已清零)->a(已清零)->uc(已清零)->NULL

    delete ''

    (c的fd为NULL,所以初始字节为,没通过*i->sentence_ptr检验,不会提示,这也就是c的作用,否则b在这里就不会有删除的提示,就无法double free)

    y(b)n(a)n(uc)

    0x70bin:b->a->b->a->...(申请a是为了绕过相邻检测)

    分配b并使fd指向_malloc_hook->分配a->分配b->分配chunk到_malloc_hook->改_malloc_hook指向one_gadget->获得shell

    Wheelofrobots(悬挂指针)

    悬挂指针->off by one绕过检测->double free->修改读入限制

    堆溢出->unlink->可控指针->泄露libc->改got->获得shell

    Roc826's_Note(悬挂指针)

    堆内容没有初始化->泄露libc

    悬挂指针->double free->修改got->获得shell

    Babyheap(堆溢出)

    堆溢出->

    修改fd(和size)->任意分配->

    任意读(程序删指针,需要构造两个指针unsorted bin UAF)->泄露

    任意写

    利用 unsorted bin 地址泄漏 libc 基地址。

    利用 fastbin attack 将chunk 分配到 malloc_hook 附近。

    利用0x1000对齐可以1/16爆破相邻地址

    申请0x10大小的a@0x00 b@0x20 c@0x40 d@0x60和0x80大小的e@0x80

    (a b c用来构造指向e的fd,d用来修改e的size,从而申请到e的第二个指针)

    List:1(a) 2(b) 3(c) 4(d) 5(e)

    释放3 2

    Bin:b->c->null

    填充a的str溢出->控制b的fd指向e

    填充d的str溢出->控制e的size为0x21

    分配2(b)->分配3(e)

    List:1(a) 2(b) 3(e) 4(d) 5(e)

    释放5->读取3->UAF泄露libc

    同样的方法申请chunk到_malloc_hook->指向onegadget->malloc触发->获得shell

    Stkof(堆溢出)

    堆溢出->unlink->修改指针周围敏感数据(指针列表)->指针可控->任意写

    Unlink修改指针global[2]=global-1

    修改global[0]指向free@got->修改free@got指向puts@plt->构造leak

    修改global[1]指向puts@got->调用free实则是puts->泄露libc

    修改global[2]指向atoi@got->修改atoi@got指向system->获得shell

    note2(堆溢出)

    堆溢出->unlink->修改指针周围敏感数据(指针列表)->可控指针->任意写

    新建node时Size填0导致size-1溢出无穷大,从而产生堆溢出

    申请note的大小分别为0x80(a),0(b),0x80(c),完成内存布局

    这里因为只有新建node时有漏洞,而编辑node时没有漏洞,所以需要先free掉,再利用new写进内容(由于bin的机制,free和new的chunk是同一个)

    布局假chunk需要5个word,而b中只有2个word的空间,所以需要申请一个a来布局

    a中布局假chunk,利用b溢出覆写c,free c触发unlink,ptr[0]=ptr-3

    修改ptr[0]指向atoi@got->查看内容->泄露libc

    修改atoi@got指向system->获得shell

    Annevi_Note(堆溢出)

    堆内容没有初始化->泄露libc

    堆溢出->Unlink->可控指针->改got->获得shell

    New 0(0xb0) 1(0xb0)

    Edit 0 (p64(0)+p64(0xb0)+p64(list_addr-0x18)+p64(list_addr-0x10)).ljust(0xb0,'x00')+p64(0xb0)+p64(0xc0)

    Free 1->unlink

    申请0xb0不会和下个chunk的prev_size空间复用

    Annevi_Note2(堆溢出)

    程序关闭标准输入输出,需要修改stdout为stderr,修改低两字节(4K对齐,1/16概率)

    堆溢出->Unlink->可控指针

    修改stdout为stderr->泄露libc

    还原stdout和stdin

    修改free_hook为system->获得shell

    用可控指针修改指针列表时还没有泄露libc,所以修改的时候保留一个指针指向指针列表,等到泄露libc知道free_hook位置后,再用这个保留的指针修改指针列表,从而修改free_hook为system

    E99p1ant_Note(堆溢出)

    堆内容没有初始化->泄露libc(main_arena)

    负数越界

    show(-7)->泄露指针列表位置

    show(-23)->泄露libc(IO_2_1_stdout)

    堆溢出->Unlink->可控指针->改free_hook_addr->获得shell

    new 0(0x98),1(0x88)

    edit 0 (p64(0)+p64(0x90)+p64(list_addr-0x18)+p64(list_addr-0x10)).ljust(0x90,'x00')+p64(0x90)+'x90'(<-off by one@1)

    free 1->unlink

    list[0]=list_addr-0x18

    edit 0 p64(0)*3+p64(free_hook_addr)+' '

    list[0]=free_hook_addr

    edit 0 system_addr

    new 2 '/bin/sh'

    del 2 ->获得shell

    申请0x98的时候,最后的0x8会和下个chunk的prev_size空间复用,从而在off by one的时候可以覆盖下个chunk的size.prev_inuse

    ROP_LEVEL5(栈溢出)

    Ret2dl_resolve

    解析并调用system

    ROP_LEVEL2(栈溢出)

    栈迁移+ORW(seccomp)

    rop@bss布局

    read stdin->file@bss

    open file

    read file

    puts file

    ret前栈布局

    data

    s->rop

    r->leave<-rsp rbp=rop

    执行leave;ret

    rsp=rbp

    rip=[rsp]=[rop]

    执行rop链

    read <-'./flag'

    open './flag'->4

    read 4->buf

    puts buf->flag

    形而上的坏死(栈溢出)

    任意读和任意写

    Ret2main修改ret两个低字节(0x1000对齐,1/16概率),然后进行多次循环

    改got为printf->泄露canary和libc

    有无符号数比较->绕过读入限制->rop

    Fys(linux文件系统)

    利用Linux文件系统的inode来定位

    参考资料

    https://www.cnblogs.com/alisecurity/p/5486458.html

    https://paper.seebug.org/1109/#14-unsafe_unlink

    https://wiki.x10sec.org/pwn/heap/unlink/#_5

    https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_overview-zh/

  • 相关阅读:
    win7 64bit下使用PL/SQL Developer连接Oracle
    C#高级开发之 特性(Attribute)三
    K3单据表结构描述和相关关联SQL语句以及金蝶插件相关说明
    K3老单插件控制字段显示
    金蝶K3插件开发-控制单据焦点(BOS单据、工业单据)
    K3 单据,单据体自定义字段显示及时库存
    C#高级开发之反射(Reflection)二
    C#高级开发之泛型一
    python学习——协程
    python学习——线程
  • 原文地址:https://www.cnblogs.com/algonote/p/13128828.html
Copyright © 2011-2022 走看看