2020年(第四届)“强网杯”writeup – 作者:龙渊实验室LongYuanLab

背景介绍:

为全面贯彻落实习总书记关于网络强国的重要思想,延揽储备锻炼网信领域特殊人才,提升国家网络空间安全能力水平,2020年8月举办第四届“强网杯”全国网络安全挑战赛。

说明:

本文章主要讲述的是“强网杯”中的Reverse类别的xx_warmup_obf和imitation_game的解题过程并且提供解密脚本。

一、xx_warmup_obf

题目序号:xx

题目名称:xx_warmup_obf

题目类别:Reverse

1.1 解题思路

IDA载入之后定位到main函数F5发现无法反编译,查看报错信息是undefined instruction,查看报错位置之前的代码发现混淆代码,模板如下:

push rdx

push r15

mov r15, 464BAB92h

add r15, 12E311F3h

mov rdx, cs:qword_60B2E8

add r15, rdx

mov rdx, [rbp-0Ch]

and rdx, 1FF00000h

add r15, rdx

mov rdx, cs:qword_60B668

and rdx, 7FFFFFFFh

and r15, 7FFFFFFFh

cmp r15, rdx

jz short loc_409D40

写ida脚本将jz指令换成jmp指令尝试去混淆失败,继续分析发现jz指令不一定会跳转,控制跳转的逻辑在异常处理的handle里面,通过int 3触发。

去混淆无望,改用单步跟踪法。很快就可以跟踪到程序读入输入的地方,接着后面0x402D14开始就是对输入的校验算法。

校验的逻辑很简单,进行len(flag)轮校验,第i轮校验前i个输入。将每个输入字符分别乘上一个常数再求和,判断其是否等于一个常数。所以只需要将每轮校验用到的所有常数提取出来就可以从左到右依次逆推出flag的值。使用ida脚本提取所有常数:

IDA_sc.py

import binascii

p_addr = 0x402D14

FIDX = []

MUL = []

CHK = []

fidx = []

mul = []

chk = 0

count = 0

while 1:

if GetMnem(p_addr) == “jz”:

opnd = GetDisasm(p_addr).split(“_”)[-1]

opnd_ = int(opnd, 16)

CHK.append(chk)

FIDX.append(fidx)

MUL.append(mul)

chk = 0

fidx = []

mul = []

count += 1

if count >= 0x1c:

break

p_addr = opnd_

if GetMnem(p_addr) == “cmp”:

opnd = GetDisasm(p_addr).split(“,”)[-1]

if opnd[-1] == “h”:

opnd = opnd[:-1]

opnd_ = int(opnd, 16)

if opnd_ & 0x80000000:

opnd_ = -((~opnd_ & 0xffffffff)+1)

chk = opnd_

if GetDisasm(p_addr) == “mov     rax, [rbp-8]”:

p_addr = FindCode(p_addr + 1,1)

if GetMnem(p_addr) != “add”:

fidx.append(0)

else:

opnd = GetDisasm(p_addr).split(“,”)[-1]

if opnd[-1] == “h”:

opnd = opnd[:-1]

opnd_ = int(opnd, 16)

fidx.append(opnd_)

while 1:

p_addr = FindCode(p_addr + 1,1)

if GetMnem(p_addr) == “imul”:

break

opnd = GetDisasm(p_addr).split(“,”)[-1]

if opnd[-1] == “h”:

opnd = opnd[:-1]

opnd_ = int(opnd, 16)

if opnd_ & 0x80000000:

opnd_ = -((~opnd_ & 0xffffffff)+1)

mul.append(opnd_)

else:

p_addr = FindCode(p_addr, 1)

print(FIDX)

print(MUL)

print(CHK)

# 0x409bec

求解flag的脚本如下:

xx_warmup_obf.py

flag = “flag{las_shaco_lee012345678}”

flag = [ord(i) for i in flag]

fidx = [[0], [1, 0], [2, 0, 1], [0, 1, 2, 3], [4, 0, 3, 2, 1], [5, 2, 4, 1, 3, 0], [6, 5, 2, 1, 3, 0, 4], [0, 3, 6, 2, 1, 7, 5, 4], [5, 8, 1, 4, 2, 0, 7, 3, 6], [1, 3, 6, 2, 9, 4, 5, 7, 8, 0], [6, 5, 0, 3, 10, 2, 9, 1, 8, 4, 7], [0, 11, 2, 3, 10, 5, 1, 7, 8, 9, 6, 4], [7, 3, 2, 0, 8, 9, 11, 6, 1, 12, 10, 4, 5], [0, 13, 2, 9, 4, 7, 3, 6, 1, 5, 10, 12, 8, 11], [8, 12, 14, 10, 11, 1, 6, 2, 4, 9, 5, 13, 3, 0, 7], [7, 1, 10, 9, 14, 6, 11, 4, 8, 5, 12, 2, 3, 13, 0, 15], [16, 1, 2, 11, 4, 5, 7, 6, 12, 9, 8, 13, 15, 10, 14, 3, 0], [7, 16, 4, 14, 15, 1, 8, 10, 12, 9, 0, 5, 2, 13, 3, 17, 11, 6], [10, 6, 0, 11, 4, 12, 17, 7, 18, 3, 8, 15, 16, 14, 1, 5, 13, 2, 9], [2, 1, 10, 12, 18, 15, 4, 19, 14, 11, 6, 17, 3, 8, 16, 0, 13, 9, 7, 5], [12, 5, 10, 3, 4, 14, 18, 2, 0, 17, 1, 6, 16, 19, 15, 20, 11, 8, 13, 9, 7], [0, 13, 20, 9, 5, 11, 3, 1, 10, 12, 7, 21, 15, 4, 14, 2, 16, 8, 19, 17, 6, 18], [5, 7, 2, 16, 8, 12, 3, 14, 21, 1, 10, 6, 17, 13, 0, 15, 22, 20, 11, 4, 18, 9, 19], [9, 18, 2, 8, 22, 0, 12, 16, 11, 21, 15, 19, 5, 7, 6, 23, 3, 17, 14, 10, 4, 1, 20, 13], [0, 6, 17, 19, 2, 12, 11, 24, 23, 13, 18, 20, 5, 7, 9, 14, 16, 22, 3, 1, 8, 10, 21, 15, 4], [15, 18, 10, 14, 7, 23, 13, 21, 9, 17, 25, 6, 4, 0, 12, 5, 16, 20, 1, 8, 24, 3, 22, 2, 19, 11], [10, 1, 16, 17, 19, 26, 0, 7, 18, 8, 20, 13, 4, 3, 2, 6, 22, 25, 5, 12, 21, 14, 23, 15, 24, 9, 11], [16, 1, 17, 24, 15, 20, 6, 19, 22, 18, 26, 8, 2, 4, 25, 3, 5, 10, 14, 27, 0, 21, 7, 23, 9, 12, 11, 13]]

mul = [[23925], [281400, -7037], [-255300, 174826, -283573], [-276718, -98445, 259881, 4524], [-228216, 94721, -274569, 285576, -60353], [125853, 264844, -294195, -5496, 260927, -153661], [-190371, -130259, -244086, -244952, -258397, 17630, -109961], [228408, -6727, 218992, 18513, -198175, 268397, 117817, 224658], [52068, 92684, -84462, 190784, 77982, -236774, -218493, -288418, -243023], [-280754, 96631, 229049, -269632, -39259, 171321, -142792, -64473, -196269, -168397], [121516, -93524, 292706, 182157, 195039, -25900, -32946, -256202, 162669, -235026, 165207], [50166, -165434, 80519, 272791, -4940, -272650, 133728, -258188, -111160, -92964, -131770, 148713], [-93199, 162962, -214348, 20489, -138253, 141532, 62018, -100280, -184125, 71182, 9710, -262820, 147171], [-75412, -224754, -119275, -61880, 22859, 116256, 122945, 25739, -51437, -200702, -86956, 220404, -55254, 59999], [300012, -249960, 233663, -228149, -88326, 144834, 67553, -2621, 135809, 157462, 278745, -189890, 198502, 111310, 91783], [114175, -129997, 54553, -163572, -48167, 148005, 230636, -235783, -233813, -181764, 104421, 125666, 194067, -11943, 15897, -251681], [277429, -39495, 254969, 154844, -1254, -52501, -289940, 70051, 74128, 22454, -68478, 272318, -203538, 34835, -228520, -90549, -132752], [-210718, -36680, -112241, 10792, 55085, -5107, -247882, -94616, -86968, -53271, 237987, 66107, 189050, -148216, -144172, -5873, 128092, -249539], [12264, -280672, 134548, 175077, -74209, 156976, 1337, 84628, -238861, 100397, -155443, 272227, 58825, 145470, 195447, -65515, 19517, -186088, 56937], [139185, -138274, -18520, 245858, -36580, 162065, -122090, -104395, 137214, 281718, -170051, 137206, 176103, -190345, 54404, -199631, 159144, -283834, -58873, -197535], [-193384, 270604, -109205, -39775, -97284, -199427, 38137, 148338, 108149, -28337, -239133, 27751, 181147, 127913, 150036, -162393, -72984, 74470, 63329, 293345, 168963], [-38942, -273571, 60492, -98937, 293201, 109605, 131692, 44675, 263208, 293781, 64641, -207716, 153071, 179514, -174651, 246135, -220539, -188979, 244009, 111858, 45637, -285946], [-71444, -130852, 255675, 59119, -258754, 9791, 76213, 105293, -148390, -194890, -141434, -43684, -21957, -197632, -58530, 295735, 92896, -86224, -206184, 32897, 234971, -160726, 127285], [-291063, 191404, -60213, -133696, 89330, 196370, -44618, 175825, 281705, -238839, -188959, -180522, -211930, 103466, -40848, 191822, 268813, -236806, 202621, 120347, 144870, 197685, 205675, 13902], [-85246, 91076, 37665, -243227, 14439, -8509, 9601, -94067, -199130, -161113, -210563, -268221, -54321, 234832, 115189, -173902, 7838, 115716, -261617, -78459, 29334, 62004, -19740, 69341, 39558], [226192, 197710, -124307, -73366, 92844, 41768, 10191, -271816, -35931, -242059, 247654, 263000, -65268, 232645, -81477, 180400, -212633, -78437, -111200, -260264, 32044, -252915, 169299, -75568, 38468, 3788], [-155664, -206964, -166693, -293146, -213665, -253617, -121854, -84952, -153409, 83918, -78913, 271210, 219151, 186585, 77915, 231326, 215574, -6866, 77693, 116581, -74795, -15606, -102361, -254282, -188087, -23897, 180598], [167885, 258890, -109447, 231264, 98136, -113009, -106792, 147747, 138998, 88628, 6966, -14347, -136952, -225830, 167370, -164339, 77375, -120743, -143324, -38949, 157781, 40423, 138308, -132906, 278196, 135302, 264405, 246315]]

chk = [2440350, 29673426, -37557732, -13182867, -25506885, 13075233, -111027477, 78775012, -52520267, -70797046, 28263339, -22025185, -31396844, -37063008, 93457153, -36640750, -6628237, -53084017, 60764977, 4912728, 45577809, 77539017, -38197685, 67763764, -98330271, -13464859, -55504393, 133068723]

now = 0

for i in range(now, len(chk)):

total = chk[i]

itx = 0

for j in range(len(mul[i])):

if fidx[i][j] == now:

itx = mul[i][j]

continue

total -= flag[fidx[i][j]]*mul[i][j]

flag[now] = total // itx

now += 1

print(flag)

print(“”.join([chr(i) for i in flag]))

解得flag:flag{g0_Fuck_xx_5egm3nt_0bf}

flag值:

flag{g0_Fuck_xx_5egm3nt_0bf}

二、 imitation_game

题目序号:im

题目名称:imitation_game

题目类别:Reverse

2.1 解题思路

载入IDA进行分析,定位到main函数,发现F5反编译失败。定位到报错位置可以发现简单的混淆代码

jz short near ptr loc_3501+1

jnz short near ptr loc_3501+1

将jz指令改为jmp去除此处混淆。

但是发现仍不能反编译,在0x35A7和0x35BF处有会引起异常的指令,猜测程序可能有异常处理handle或者是父进程调试子进程的模式。程序使用动态链接,静态分析查看不了库函数名称,遂ida动态调试程序,发现程序调用了fork(),于是初步判断程序为父进程调试子进程的模式。

将异常指令附近的跳转指令修改为jmp造成一个死循环,就可以强行对代码进行反编译,然后对代码进行分析,逻辑大致如下:

  1. 父进程创建子进程之后调用wait等待子进程
  2. 子进程读取输入之后进行校验,根据校验结果不同分别返回不同的异常值
  3. 父进程接收到异常之后进行判断,如果为SIGILL则继续执行进入到第二关

首先分析子进程校验逻辑:

  1. 获取输入后对输入进行padding
  2. 对常量1进行密钥扩展
  3. 使用扩展后的密钥和常量2对输入进行解密
  4. 将解密后的数据与常量数据3进行比较

算法为128b密钥,128b分组,且密钥扩展算法使用到rcon数组,推测其加密的方式为AES_CBC填充0x1a,IV为常量2,解密脚本如下:

from Crypto.Cipher import AES

import binascii

print(“flag{“, end=””)

text1 = [157, 123, 162, 60, 177, 9, 154, 72, 65, 209, 102, 99, 214, 174, 60, 171, 222, 217, 218, 52, 131, 188, 159, 243, 157, 193, 118, 194, 129, 13, 48, 33, 243, 218, 68, 14, 221, 74, 105, 206, 55, 142, 113, 214, 112, 110, 164, 68, 30, 213, 58, 144, 204, 165, 241, 47, 254, 103, 0, 70, 172, 255, 62, 159]

aes_key = [62, 44, 37, 19, 24, 190, 195, 107, 161, 55, 36, 83, 3, 30, 81, 236]

aes_iv = [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]

text1 = bytes(text1)

aes_key = bytes(aes_key)

aes_iv = bytes(aes_iv)

aes = AES.new(aes_key, AES.MODE_CBC, iv=aes_iv)

print(aes.decrypt(text1).strip(b”\x1a”).decode(“ascii”), end=””)

得到flag1:6c8f1d78770fe672122478c6f9a150e7

接下来分析题目的第二部分

抛开与SDL相关的代码,程序首先打开参数指定的文件,然后以大端方式按word进行读取,以高4位为index调用functionTable中的函数。分析几个函数功能之后发现是一个虚拟机,包含对寄存器操作以及输入输出。遂写脚本对game.bin进行反汇编,脚本如下:

# 反编译器代码

def emptyFunc():

print(“nop”)

return

def f10():

funcTable5[op4]()

def f11():

print(“jmp %d” % ((opcode & 0xFFF)-0x200))

def f12():

print(“buf2[r2] = %d” % (ip))

print(“\t\t\tr2 ++”)

print(“\t\t\tjmp %d” % ((opcode & 0xFFF)-0x200))

def f13():

print(“if buf1[%d] == %d” % (op2, op4))

print(“\t\t\t    jmp %d”% ip+2)

def f14():

print(“if buf1[%d] != %d” % (op2, op4))

print(“\t\t\t    jmp %d”% ip+2)

def f15():

print(“if buf1[%d] == buf1[%d]” % (op2, op3))

print(“\t\t\t    jmp %d”% ip+2)

def f16():

print(“buf1[%d] = %d” % (op2, (opcode & 0xff)))

def f17():

print(“buf1[%d] += %d” % (op2, op4))

def f18():

funcTable4[op4]()

def f19():

print(“if buf1[%d] != buf1[%d]” % (op2, op3))

print(“\t\t\t    jmp %d”% ip+2)

def f1a():

print(“r5 = %d” % (opcode & 0xFFF))

def f1b():

print(“jmp buf1[0]+%d” % ((opcode & 0xFFF)-0x200))

def f1c():

print(“buf1[%d] = %d & rand()” % (op2, op4))

def f1d():

print(“alg1()”)

def f1e():

funcTable3[op4]()

def f1f():

funcTable2[opcode&0xff]()

def ff50():

print(“buf1[%d] = r4” % (op2))

def f280():

print(“buf1[%d] = input()” % op2)

def ff70():

print(“r4 = buf1[%d]” % op2)

def ff90():

print(“r3 = buf1[%d]” % op2)

def ffb0():

print(“r5 += buf1[%d]” % op2)

def ffe0():

print(“r5 = 5 * buf1[%d]” % op2)

def f010():

print(“alg2()”)

def f090():

print(“memcpy(buf1, BUFF+r5, %d + 1)” % op2)

print(“\t\t\tr5 += %d + 1” % op2)

def f0f0():

print(“memcpy(BUFF+r5, buf1, %d + 1)” % op2)

print(“\t\t\tr5 += %d + 1” % op2)

# Table 3

def f750():

print(“unknown1()”)

def f790():

print(“unknown2()”)

#Table 4

def fc60():

print(“buf1[%d] = buf1[%d]” % (op2, op3))

def fc90():

print(“buf1[%d] |= buf1[%d]” % (op2, op3))

def fcc0():

print(“buf1[%d] &= buf1[%d]” % (op2, op3))

def fcf0():

print(“buf1[%d] ^= buf1[%d]” % (op2, op3))

def fd20():

print(“buf1[%d] += buf1[%d]” % (op2, op3))

print(“\t\t\tr1 = buf1[%d] > 255” % (op2))

def fd60():#

print(“buf1[%d] -= buf1[%d]” % (op2, op3))

print(“\t\t\tr1 = buf1[%d] >= 0” % (op2))

def fde0():#

print(“buf1[%d] = buf1[%d] >> 1” % (op2, op3))

print(“\t\t\tr1 = buf1[%d] & 1” % (op3))

def fda0():#

print(“buf1[%d] = buf1[%d] – buf1[%d]” % (op2, op3, op2))

print(“\t\t\tr1 = buf1[%d] >= 0” % (op3))

def fe20():

print(“buf1[%d] = 2 * buf1[%d]” % (op2, op3))

print(“\t\t\tr1 = buf1[%d] >> 7” % (op2))

#fcTable5

def fb 50():

print(“memset(buf3, 0, 256)”)

def fb90():

print(“r2 –“)

print(“\t\t\tjmp buf2[r2]”)# ret

funcTable1 = [f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f1a,f1b,f1c,f1d,f1e,f1f]

funcTable2 = [emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, ff50, emptyFunc, emptyFunc, f280, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, ff70, emptyFunc, emptyFunc, ff90, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, ffb0, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, ffe0, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, f010, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, f090, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, f0f0, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, f0f0, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc]

funcTable3 = [emptyFunc, f750, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, f790, emptyFunc]

funcTable4 = [fc60, fc90, fcc0, fcf0, fd20, fd60, fde0, fda0, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, emptyFunc, fe20, emptyFunc]

funcTable5 = [emptyFunc] + [fb 50] + [emptyFunc]*13 + [fb90]

with open(“game.bin”,”rb”) as f:

ip = 0

while(1):

opcode = f.read(2)

if opcode == b”:

break

opcode = int.from_bytes(opcode, ‘big’)

print(“%04d %04x\t” % (ip, opcode), end=””)

op1 = (opcode >> 12) & 0xf

op2 = (opcode >> 8) & 0xf

op3 = (opcode >> 4) & 0xf

op4 = (opcode >> 0) & 0xf

ip+=2

funcTable1[op1]()

反汇编代码长达1300行,这里就只阐述分析后其中的逻辑:

  1. 首先获取用户的输入,并将其输出到屏幕
  2. 对每个输入进行简单的可逆变换,例如加上一个数,减去一个数,异或一个数等
  3. 每3个输入分成一组进行校验,最后余一个输入单独校验

(1)将分组内的每个输入分别乘上一个常数,然后与常数进行比较,等价为方程x1*c1+x2*c2+x3*c3 == c4,每组校验有三轮,于是可以得到三个方程。三个方程三个未知数,就可以解得未知数的值。

(2)最后余下的最后一个输入直接与常数进行比较

解密脚本如下:

import z3

mul = [1,2,1, 2,1,1, 1,2,2, 1,2,1, 2,1,1, 1,2,2, 1,2,1, 2,1,1, 1,2,2]

chk = [33,42,48,55,55,59,31,22,32]

flag = []

for i in range(3):

x = z3.Ints(“x1 x2 x3”)

s = z3.Solver()

for j in range(0, 9, 3):

m0 = mul[i*9 + j + 0]

m1 = mul[i*9 + j + 1]

m2 = mul[i*9 + j + 2]

ck = chk[i*3+j//3]

s.add(x[0]*m0 + x[1]*m1 + x[2]*m2 == ck)

if s.check() == z3.sat:

for i in x:

flag.append(s.model()[i].as_long())

flag.append(5)

flag[0] -= 2

flag[1] -= 1

flag[2] = (flag[2] ^ 1) – 1

flag[3] -= 3

flag[4] -= 2

flag[5] = (flag[5] – 1) ^ 2

flag[6] //= 2

flag[7] -= 1

flag[8] = (flag[8] – 1) ^ 1

flag[9] -= 2

for i in flag:

print(hex(i)[2:], end=””)

print(“}”)

解得flag2:a2def12c13

flag值:

flag{6c8f1d78770fe672122478c6f9a150e7a2def12c13}

原文链接

来源:freebuf.com 2020-08-31 09:38:23 by: 龙渊实验室LongYuanLab

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论