CTF真(太)好(难)玩(了)

0%

强网杯 2020 Re 部分题解

这次比赛虽然很辛苦,爆肝了两天,但是在这个过程中让我学习到很多东西,看到了很多有意思的题目,比起前天的国赛,有趣的多。整一个比赛过程虽然出现了很多插曲,但最终还是以第31名出线

Nepnep,冲鸭!
result

0x01 侧防

Re签到题,算法在sub_12F0里,就这么一点点,直接逆就可以

main

1
2
3
4
5
6
7
8
9
10
11
12
13
ciper = [
0x78, 0x7C, 0x64, 0x4C, 0x55, 0x77, 0x65, 0x54,0x49,
0x76, 0x4E, 0x5C, 0x43, 0x42, 0x4F, 0x68, 0x71, 0x44, 0x4E,0x4C,
0x57, 0x7D, 0x49, 0x66, 0x46, 0x5A, 0x43, 0x6D, 0x69,
0x79, 0x78, 0x74, 0x5C, 0x50, 0x57,0x4F,0x65, 0x62, 0x44,0x5E,
]

key = [0x51, 0x57, 0x42, 0x6C, 0x6F, 0x67, 0x73]
flag = []
for i in range(len(ciper)):
flag.append( (ciper[i]-65)^key[i%7] )
print(flag)
print("".join([chr(x) for x in flag]))

PS:我也不知道为什么解出来会最后少两个字符,瞒猜是单词“sorce”,然后回括号,交了就对了

0x02 xx_warmup_obf

这题就有意思了。考点应该是ollvm混淆+花指令+解方程+基于信号量的反调试。先通过字符串交叉引用找过来,可以看到整一片代码都是面目全非的,而且这不像是普通的花指令,反而像是数据和代码直接混在了一起,通过分发器和跳转指令来运行这片代码。

1

没办法,起调试,但是调试的过程当作发现有大量的int 3指令会被运行,而且“pass to app”后,就会进死循环。
2

这就很诡异了,本身程序自身的代码出现int 3指令就是一件很诡异的事情,而且这里有大量的int 3,而且运行完后就会进死循环。然后看到init_array里有两个函数,在sub_400B4C 里,注册了信号量处理函数,也就是说,当没有调试器附加的时候,执行 int 3 指令,会有信号量发送给这个处理函数,处理函数会更改分发,从而不进入死循环。那我们调试的时候,只需要在进入死循环后修改跳转条件跳出来就可以了(比如上一幅图的0x409D5F处),这样就可以愉快的调试了。我后来是采用输入后attach的调试方法,然后程序就会在完成输入后的那个int处中断下来,这样可以节省很多手动改跳转的时间。

然后一路向下走,过了长度检测,最终会来到0x402D05,这个位置就是算法的开始,仔细观察一下,会发现全部都是这样的结构
3
rbp-8 就是输入,后面add的数就是找下标索引,然后与一个数相乘后把结果加到edx或者eax里,最后拿去做比较,如果相同则通过JZ指令跳到下一条方程,否在就会更改分发器和标志位,去结束掉程序。

看懂程序后就要想办法把数据dump出来,因为不能出伪代码(后来知道有的带哥是手动去花去混淆修复出伪代码的),直接从两千多条指令中扣出28个方程难度实在是太大,我就写了一个IDAPython脚本,先dump出所有指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# trace.py
#coding=utf-8
StartAddr = 0x402D05 # 起始地址
EndAddr = 0x409ACB # 终止地址
add_bpt(EndAddr, 0, BPT_SOFT)
result = ""

while True:
StepOver()
event = GetDebuggerEvent(WFNE_ANY, -1)
addr = GetEventEa()

print GetDisasm(addr)
result += GetDisasm(addr) + '\n'
if GetMnem(addr) == "cmp" and GetOpnd(addr, 0) == "eax":
SetRegValue("0x" + GetOpnd(addr, 1).rstrip('h'), "eax") # 要在cmp的时候修改eax寄存器的值,来让判断为真进入下一个方程
fp = open("D:/trace.txt", 'a')
fp.write(result)
fp.close()
result = ""
if addr == EndAddr: break # 通过地址判断trace结束

del_bpt(EndAddr)

效果大概这样
trace result

然后写python脚本把它parser出来,因为很规则,可以直接通过指令匹配解析。然后这里有个注意点,在本题,运算全部都是带符号运算,但是我们dump出来的数据是没有符号这个概念的,所以需要手动判断一下负数,转一下补码(刚开始我就没注意到这个问题,解析出方程直接上Z3结果跑了一个多小时跑出个unsat)

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
#parser.py
with open("trace.txt") as fp:
l = fp.readlines()

new_bj = 0
pos = 0
equation = ""
for i in l:
if(i == "mov rax, [rbp-8]\n"):
new_bj = 1
if i.startswith("add rax,") and new_bj == 1:
pos = eval( "0x" + i[i.find(',')+2:].rstrip('h\n'))
new_bj = 0
if i.startswith("imul"):
val = eval( "0x" + i[i.find('eax,')+5:].rstrip('h\n'))
if val & 0x80000000 != 0:
val=val^0xffffffff
val=val+1
val = -val
equation += "flag[{}]".format(pos) + "*" + str(val) + " + "
pos = 0
if i.startswith("cmp eax,"):
val = eval( "0x" + i[i.find('eax,')+5:].rstrip('h\n'))
if val & 0x80000000 != 0:
val=val^0xffffffff
val=val+1
val = -val
equation += " == " + str(val)
equation = "solver.add( (" + equation.replace("+ ==",") ==") + ")"
print(equation)
new_bj = 0
pos = 0
equation = ""

解析出28个方程,直接上Z3秒

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

if solver.check() == sat:
model = solver.model()
result = [ chr( model[flag[i]].as_long() ) for i in range(28) ]
print("".join(result))

0x03 imitation_game

main函数中含有花指令,去除后开始动调

main函数前面fork了一个进程,父进程会对子进程ptrace,这样会导致IDA无法attach,所以先把ptace函数nop掉,然后跟踪父进程看看逻辑。逻辑里发现AES的SBOX,IDA findcrypto插件也同样识别出了这里的AES算法。回头看该函数开头有一串异或,故可确定其为AES-CBC,解密得到第一段flag 6c8f1d78770fe672122478c6f9a150e9

然后还原patch调试子进程

程序读取game.bin后,进行了一大段的虚拟机操作,根据hint,知道是chip-8,用CHIP8Decompile进行了反编译,动调可知,题目在收集了十次按键(输入必须为0-9, A-F,保存在内存中的值也是0-9,A-F)后,开始加密和校验。0x043E开始,依次对输入的十个按键进行简单的运算。然后开始调用一系列的函数,这里经过分析发现采用递归算法,递归函数为0x027A。分析后整理出了10个三元一次方程,上Z3解得第二部分flag

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
from z3 import *

flag = [Int('flag%d' % i) for i in range(10)]

s = Solver()
s.add(flag[1]*2 + flag[0] + flag[2] == 33)
s.add(2*flag[0] + flag[1] + flag[2] == 42)
s.add(flag[0] + 2*flag[1] + 2*flag[2] == 48)
s.add(flag[3] + flag[4]*2 + flag[5] == 55)
s.add(flag[3]*2 + flag[4] + flag[5] == 55)
s.add(flag[3] + flag[4]*2 + flag[5]*2 == 59)
s.add(flag[6] + flag[7]*2 + flag[8] == 31)
s.add(flag[6]*2 + flag[7] + flag[8] == 22)
s.add(flag[6] + flag[7]*2 + flag[8]*2 == 32)

print(s.check())
model = s.model()
res = [ model[flag[i]].as_long() for i in range(9) ]
res.append(5)
print(res)

res[0] -= 2
res[1] -= 1
res[2] ^= 1
res[2] -= 1
res[3] -= 3
res[4] -= 2
res[5] -= 1
res[5] ^= 2
res[6] //= 2
res[7] -= 1
res[8] -= 1
res[8] ^= 1
res[9] -= 2
for i in res:
print('%X'%i, end = '')