The Flare-On Challenge #12 (2025) WriteUp

30k 词

前言

第一次参加Flare-On挑战赛,经过了一周半左右的不懈坚持与坐牢奋斗,最终实现了9道题目的全解,且是国内真正实现全解的选手中名次比较靠前的(第3名),个人还是比较惊讶和非常满意的,毕竟自己一开始也没想到居然能攻克到最后一关并实现全解,但不知不觉地就完成了。题解是边做边写的,可能有些跳跃(毕竟做题的时候就是一个思路或者灵感跳跃到另一个,难免会有回看之后收获新发现的情况),对于特别复杂的题,已经尽可能地整理成按顺序的思路了,还请见谅。

#1 Drill Baby Drill!

只有一个异或,爆破即可

1
2
3
4
5
6
7
8
encoded = b"\xd0\xc7\xdf\xdb\xd4\xd0\xd4\xdc\xe3\xdb\xd1\xcd\x9f\xb5\xa7\xa7\xa0\xac\xa3\xb4\x88\xaf\xa6\xaa\xbe\xa8\xe3\xa0\xbe\xff\xb1\xbc\xb9"

for j in range(0,2<<15,2<<8):
key = j >> 8
plaintext = bytearray()
for i in range(0, len(encoded)):
plaintext.append(encoded[i] ^ (key+i) & 0xFF)
print(plaintext)

drilling_for_teddies@flare-on.com

#2 project_chimera

先反编译第一层:

1
2
3
4
5
6
7
8
import zlib
import marshal
import dis

encrypted_sequencer_data = b'x\x9cm\x96K\xcf\xe2\xe6\x15\xc7\xfd\xcedf\x92\xe6\xd2J\x93\xceTI\x9b\x8c\x05&\x18\xe4\t\x06\x03/\xc2\xdc1w\xcc\x1dl/\x00_\x01\xe3\x1b6\xc6\xe6\xfa\x15\x9a\xae\xd2\xae\xba\xae\xd2/Q\xf5\x0b\xbc\xd1\xa4JJVUV\xdd\xa5\xca\xae\xab\xf2\xceM\x89\x9ag\xe1\xf3\x9cs~\xe7\xfc\x8f\x1f\xc9\xd6\xf3\x1d\xf0\xa3u\xef\xa5\xfd\xe1\xce\x15\x00|\x0e\x08\x80p\xa5\x00\xcc\x0b{\xc5\\=\xb7w\x98;\xcf\xed]\xe6\xaep\x87y\xe3\x0e \xde\x13\xee~q\xf5\xa2\xf0\nx\xee\xbf\xf1\x13\x1f\x90\xdf\x01\xfeo\x89\xaf\x19\xe6\xc1\x85\xb9\x92\x7f\xf53\xcc\x83\xd7\xcc[\x17\xe6\x8e\xfc\xfe\xcf0o\xbdf\xde~\xae}\xef\'\xdaw\xe5\xdf\xfcL\xcd-\xf9\xee\x17/\xbd/\xee\xbc\xac\x7f\xef\x12}\xefU\xf4\n\xd8^\xc1\xf7\xff}\xbb%\xad\xbf\xbe\t\x00\xbc\xf7 \x06[\xe9\xb8\x0f\x89MU\xb0\xbbc\x97\'E!\x0ea<\t\xfa\xc7\x81aG\xf3\xac\x88\xca\xe1\xe0\x12a\xce\x1b\x18\xa5v\xce59:\x85\xd5Y\xb5)G\xac\x92\xbc\xdbB8Y\xeb\\cc\xeff%\xf6\xcb>\xb5\x10\xdc\xce\x15"\x16\x8f\xcb\xc85\\\xc2\xb4b\xfa\x94\xc1\xcb\xabF\x0c\xd3\x95M\xde\xf2r\x0c\xb6_\x11\xc9\xfd!ed\x9bX\x8e\x13\xb9q ]\xd8U\r\xb361\x0bT\x83B\xb3K8\x8ay+\x95AC\xab\x8a\xd16\xa2\xc0\xb9\xb9\x0c\x06b\xce\xbexR \xaa\xe9\x14\xdb\xb6G.\xd2sj\\$\xf7\xabh\xe7\x10EF+\x08\xcd*y\xf7x<lH\xd48\r\xaa\xd7s84\xf0i=4R\x9c\x1d\xdd\xeb\xfa\x98@\xfc+\xaf\x11:b\xa0\xb2E u\x1f\xaa\x08\xe9q0\x12\xc0[\xfb\x80\x15\xaa#\xca\xf2p\xcc7*\xa3z\xcd\x11;&\xb9\x8b\xee\xa1\x12\x92\xcc\x12\x93\xbd\x10\xac\xaa}%\x8e\xe8q\xdf\xb1\xb5\x87l\x8e\x85\x1d\xb4\xdb\x08\x0cr]*\x10O\xac\x83!|\x9c\xcf\xecT\xa5U\xa4\x12\x870\xb73&\xbb\xb5#o\'}\xa1\xce\xc1($\xb61\x01\xa1\xd6\x8b\x10=\x93\x97\x13\xc8\x01\xc7\x10\xea\xdaMr\x831\xd7>\x7f` \xc6\'\xe3\x12\xb7E\xb5H2X\xc6\x87\xc5\x9c\xb4Z\x8c\xe7h:\x94M\x11\xcbE\x14l\x9eL\xd5\x82X\xc9\x9d\x06m\x97\r\x05\x92\xa5\x9d-\x18+R\xd1\xa2M<\x0b\xb6V\x9a\xc0\xc0]|3\xc7l\xdf\xccPU\x8dm\x8a\x0e\xd7\x0fuk\xdc6\xe3\x97\xd885\xf2\x98i\xa6\x83\r\x08\x9f}8)\x8cE\xd0\'D1\xa4QS\nM\x82\xc6\x10\xa9L\xdbTU3\x1cu\xab\x9fTf\xba\x96\x06\xf5\x8c\xdf[\xaf\xb0\x90\xba!\x15}\xc3$i\xb8\x18\x14c\xb6\x13T\xe9X\x83\xcc\x87\xe9\x84\x8f]r#\x83\xc9*\xf3To\x81\x83\xb5\xec\xfaP(_\xc7\x88),\x1b\xa0\x82\xb9\x04\xed\x9f\xc7\xb3^E\xc9a\xc7|B0\x1a\x01\x19\x16\x1b\xfb\xcd\x90\xe7\xb6M7:\xd9sh\x04&\xb3\x0e{\x12\x8d\xde5#\xe9\xbe\xe1\x84\xf6H\xcd\xc0,\x91\xcc\xc6 9\x05-\xa0Q>\x94\xea\xf4"\xa2#gC\xa7<\xb8Xp6\xde\\\x99f\xadZ\xd9\xab\xbe\x92\x9e+\xe7#\x9e\x10)%]\xf0$l:\x87\x84\'\xc2\x1f\xe1j#\xb6$6\xf3\xfc\xb6\xb6\xc9\xed\xf3\th\xb0\xa2B\xfdY\x00\t\xe6\x96\'r\xe4\xbb\x1cK>\xc3\xc6\x1c\x91\xb88\xe6\xae\xbb\x083y0\x86\xc5+#%76\xcb\xd8l#G\xe8\xb5\xa8GB\xbe\xc01\x19M$\xe3Z\xad\x14\x17\xe7\xf1\x8dLP\x8e\xe3\xb6G\xa3]1\x10\xc1\xab\x1b\xa6\xe7Q\xaa\r\xbf\x12\xc8\xd8\xde$Q^Hu\xa9Q4\x86\\\xc0\xa4\x1a[\x07\xcc\xb5OL\x7f\x8c\xf4R\x18\xb5\x8f\xa0\xeb\x95\x88\xb7\xd0\xa5S\xf6\xce\xf2\x8cf_\x8b\x1b6r\x8a%\xb1\x82k\xf2\x15t\xdf\x99\xed\x9b\xc9r?\x9a\xcd\x0b\xab5d\xed\xdde?Y\xdc\xb2\xf9%\xbcI\xf3}\xd3\x93\xa2\x9aY\xbe\x83\x0c\x19\xa6\x86\xb2\xbb\xf9\x1e-J\'\xc9\x91\xfc\xaa@/\'<Q\x98N=;S\xdc\x0cl\tE\xaa\xf1b\xa5\xber\x13|\xbc)f\x02\x0b\xd26\x13\x17-\x1d\xce\xa19\xb5\xc2\xd5\xc1\x98g\x89\x0b\xc1\x8eJ\xc9\xfa@1s|\xaa\x8b\\\x13\x12\xb1\xd1\xbc\xfd6\x94a\xb804E\x92N)\xcc\xc4\xf9Sg\x0ev\x06\x06\x94-\xc5\x05\x7f\'Y]g5%\x82.\x1c~L\x16\xfa}S\x0e\xb4F0GT\xd2yZ\xe9xiu1\xef\r\xc3\x9d\xa2k\x16\xac:\xd9\xd7\t\xd5"\x17\xd2)\x89T\x1b\xe5\xa0\xe2\xcd\x9e\xacf\x91\xd7\x88\n]\xe5d.\xd3@,G\x87\xd2$I\xc7B\x9dZt\x1anP~\x9f\xb7P\x92\x02#?\xaf\xc4\xd7\xd7\xa1D$\x91\xedT\x82\xe9$\xb8\xaccr\xb3\xbfhur\xc7]3+\xf4\x82\x8e\xba\xc42\xdd\xb5\xb5\xaaZ~rm3\xa6\x9fpd|\xe7R\xecP_[`\x0c?\x0e\xda\xd1\xb4F\x1a\xe8LZ\x8a\x16\xd6\x0f\xec\x84=\x1c\x9b#\xe5\x12\x96&{\x9d\xd6\xb1\x1bH\xa0{~\xba\x04SE\xa4x\xe4X\xd2\x8bJ\xf6\x904\x07\xc5MyA\x0f\xa9\x11\x9d\xafb\xd1\xd8^-\x94\xa7\xf6\xd2f$\x83\x84s\xb8\xbb\xe5R\xd6\x91\xdb\x12\xfe\xe2\x86\x91T\xa3\xbb\xdc\xe8X\xa19\x0b\x96\x02\x91\x02$\xc5<\x19u?\xcb\xf61\x1b)\xe3\'5\x7fr\xca\xd4,I\x0e\x9b\xa5\xa2\xec\x93\xa28\xbc*\xa3\x9e\xb8\xab\xd0B\x89\xe8L\xe4J\xd7\x0e\x88\xbe\xd2@\xed\xa05\xbcl\x1c1\xaf\xbb\xcanY\xa5\xe0w\xe1\x1eR\xaa\x12\xb3\x8e\x18\xac\xba\xb9n\xa3\xd6\xee\xaa\xd9"\xe5\xfa\xd6A|\x1em\x84Z\xdd\x1aN\xe0\xbcs\x8c)Z,#\xba\x8d\xca\xf6\x98\x98\x08\x04f\xec\xd0\xb8\xde\xf0\x9f\x88\xe9\x9e\x9d\x12\x88\xa6\xc73\xd3(l\x14\t\x83\xa4\xfdHl\xc8\xd62\x851^K\xf8\xcb$\x98Kj\xd3v\xbf]d\xf2DrD\xa6\xa3\xcb\x14\xabZS{\xbb\xc5]\x95\xa1\x85lkv\x08a{t\xe0\x0f\xa0\xedr\xa3\x9b\x9eGFT\x86eF\x1d\xe9\x14Kdd\xa4d\xa9\x8dqyS\xd5\xcc\xd9B\xd0\x9b\xe1\xa3\x89\xda\xbe#\x95\x0f\xae\x8ezy\x86\x90]\x8f6\xa6\x02\x98\xbd\xcao3\xe8\x8a\xf6b\xb8\xbck\xe6\xe7T\x0eN\xee\xda\x92\x1b\t\xb8\x03p8\xf2z\xa4\x12\xebk\x16ZR\xb72\xd4BPly\xcd\xb2]\'!\xd0\x198\x0e\xdamP+W\x08\xce\xb3\x0c\xd6\\\xfa\x10\x9e\xa7\x97\xd4\x9e\xdcC\xe0\xb4*m\xda\xd4\xa1\x97\x15A-\x17\xa9nO\x1e\xbe>4a\x88/\xb9{\x95\xee\x95\xe5\xc4\x1c\xadL:1QX\xce\xed\xf2\x12\x8e0\x89\xd9\xc8\x98\x9e\xd4\xda\xae\x1c\xc7\xd4\xb8\x1f\xac\x8du?\x18\x16\xc4\xa9\xda\xcaD\xaa\xc5\x1d?Lz\xbb\x9diV\xd2\x17tE\x91\xa1\xfd\xe5\x87\x9c\xf6,\xfa\x87zz\x83L\xe9\n\xdc\xee\xbb\x1e\xa9k\xfb\x0f\xd9\x9cU\xef{\xdac\x98\xd7X\xf0\x90\xb0\x06\xdb\x01\xd2\\\xe7\xdc\xf6\xb1\x99v\x0e\x05\x1e\xb5\xb0I\xbd\x9a\x98+Fx{\x18\xe4\x88\x9a\xb7\x10\xf6b\xady\xec\x94\xb5e\x04\xa4\x91\xe8\x9a\xd8V\xbd4T\'\n$f\xc7\x14<\x90\x91x\xa7;\x91\x8a\xe3CP\x90\x8b\xd5Z\xd4\x06\xd39\x1fJ&\x16ku\x8fGt\xc4\xd6\x92\x08|\x9d\x18{\x8cj[\xd8\x0f\x9d\xed\xae2AG\xad\xed\x8a\xf1V\xe0\xa5\x97\xa2\x8a\x88\xcb\x0fXi&s)\xd2\xb3\x00\x83-MC\xfa2\xc2\x13:\x17\xf4\x83\xfe|k\xc4\xa6K\xebB2\x8c\x16+{h\\\xad\xe8)\x1eJ\x9aI\xd9Z\x93ht\xd5\x9b\x0c\xc6\xa5T\x8e\xf3\xf2\xd1\xd6<:\xcaH4\x08\x8d7\x02%\x11\xe9(-\x81f\xa54\xc6\xd9\xd24\x1f\xe0\xc4@#\xe5/\x94\xfc\x10B\xe0\x19\x18\xe2B\xde|\r>HaF.C\xd5\x9e\x13d\xae)\xbe0\x95\x830g,\xf1x\x82\xa6F\xc4R`\x87q\xd5)O\x96\x8b\xd6\xe5S\xa3\xb7\xaa\xaf\xe0[\xb8~\xc2\xc8\xc5IO\xe6x`\xbbn\xce\xea\xaaI0,B"\xccb\xb9\r\xa3U\x06\xed\x8dS`3\x9c\xaf\xb5\xa8\xe8\xfa\x0eB\x10\xe4I\x81U\x16\x9c\xc9\xae\x17\xda\xecIY\xd4\xc4\xf5\x82\x7f\xd2\x13W\xb6\xa8\xf1\xa2\xf9\xe4B\xec>.\x8a\xbc.\xdc\xe6yv\xcd*[k\xfd\xa4H\xe6\x9eXk\x93\xd5\x84\xa7O\x9f\xee>\xeam\xb5\xf5\\\xb4\x16\xbb[\xa8\xf0\n\xea\x89\xa6\xad^\xf2\xf0/\xcf\xf79\xd6\x12c\xd8\xf9\x8d\xddE\xec\xfc@eMk\xce*\xe7{\xeb\xad!Z\xe7\xc7\x17-]\x10\x85\xc9\xab\xfe\x93\x17\xbd\xcf\xf7\x0cs\xa1\xad\xcfoq\xd7Q\xe1v\x06\xf1\xfc\x90\xd7U\xc3\x14-\xebG\xf4\xf9\x17\xb7\xc9\x17\xe1\xf3\xe3\x97\xbd\x95\x0b0{\xf1:\x93\xe7\x95\xf7\x14\x9d\x15\xac\xf3\xfb\xaf5n\xa3\x13\x9d\x93E~}~\xa7dk\xfcz\xa1k\xfd\xcb@\xe7\x073E\xe7X\xc5:\x7f\xf8\x1a^h\xb7\xdc\x05\x98H/\xc9\xbf\x00?\xdc^\xfb\xfe\xfb\x10\x7f%c\xbd:\xb5\xf4\xf9M\\\xd5\x05[\x11\xd3\xe6\xaf\x9f\xdf\x12\x01\xc0\xfa\xfd\xe5\xf1\xfd\xdd\xab\xab\xab\xef\x80w\xbf\x05\xde\xfe\x16x\xef[\xe0\x9d\xef\xef\x03\x1f\xd6<7\xc0\xe3\x7f\x01\xf7n\xee#_\x01O\xffy\xbb\xf9\xe4+\xc0\xff\xcd#\xdfg\xd2\xd7\x8f|_>\xf2\xdd|\x92~\xf6(s\x03<\xfc\xe6\x03\xf8\x8f\xde?\x7f\xfa\xa7Oo\x02\xa9g\x1f\xa4/u\xdf<\xf6~\xe6|~\xfc\xc3\xf1\x06\xc2\x9f=N\xdd\x00\xef?\xef\xe4\xfb\n\xf8\xe4\xd2\xfbc\xf4\x8f\xe2\xd7\x1f\x85\xbe\xfc(t\x83\x12\x7fs\xfe\xbe}\xf6Q\xe7\x06\xf8\xf0?\xf7\x81\xab\xdf\xfe\x03\xf8\x9d\xf9\xf02\xd3\xff\x00hw\x9dH'
sequencer_code = zlib.decompress(encrypted_sequencer_data)
code = marshal.loads(sequencer_code)
dis.dis(code)

得到:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
 0           0 RESUME                   0

2 2 LOAD_CONST 0 (0)
4 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (base64)
8 STORE_NAME 0 (base64)

3 10 LOAD_CONST 0 (0)
12 LOAD_CONST 1 (None)
14 IMPORT_NAME 1 (zlib)
16 STORE_NAME 1 (zlib)

4 18 LOAD_CONST 0 (0)
20 LOAD_CONST 1 (None)
22 IMPORT_NAME 2 (marshal)
24 STORE_NAME 2 (marshal)

5 26 LOAD_CONST 0 (0)
28 LOAD_CONST 1 (None)
30 IMPORT_NAME 3 (types)
32 STORE_NAME 3 (types)

8 34 LOAD_CONST 2 (b'c$|e+O>7&-6`m!Rzak~llE|2<;!(^*VQn#qEH||xE2b$*W=zw8NW~2mgIMj3sFjzy%<NJQ84^$vqeTG&mC+yhlE677j-8)F4nD>~?<GqL64olvBs$bZ4{qE;{|=p@M4Abeb^*>CzIprJ_rCXLX1@k)54$HHULnIe5P-l)Ahj!*6w{D~l%XMwDPu#jDYhX^DN{q5Q|5-Wq%1@lBx}}|vN1p~UI8h)0U&nS13Dg}x8K^E-(q$p0}4!ly-%m{0Hd>^+3*<O{*s0K-lk|}BLHWKJweQrNz5{%F-;@E_{d+ImTl7-o7&}O{%uba)w1RL*UARX*79t+0<^B?zmlODX9|2bzp_ztwjy_TdKb)1%eP4d-Xti0Ygjk_%w!^%1xuMNv4Z8&(*Ue7_^Fby1n3;+G<VDAfqi^h1>0@=Eki5!M~rms%afx`+uxa0*;FzudpqNln5M<@!OqndZ)R<vh4u&gpmmnaMewbT0RJby?(fa7XW#r>ZQ4UE&u|~lZsEY~-lpfWMf0_+pV-H`PXInpwmyo~mZ`tfUK?($KHa%mvNlovZ;Y)D+e6uw+mY6LNB2Y9&akbWpZ@lh=Si<!J@t|CG86E`)jp!l4xEY(h7@$llA4}B9dpL*j)eL{vVcbyMx5_{b13)N@wa~epS8Zfo&V_Y#fM*g9;@6%j=%i%WB0=QS3ewj@0~B!iibu<MqrrJIH{m&FoAGB3#0Nf;x!~dvQ|9#3c})IL6kEvhByJvA{B9%UqX0Tg*-+Ak~NW&RJbB?a6weENW&rzRi2ZB!647HWlA^rG4gvj3Yteo30&*};59;7nJF7eh7vjEXwwxPWWzD*3<IvZS#lIL(l*?u$;EGifKfLDpVb*rXLyw!AP~ZT^-S=4X{31tqe<O1kwG$gBZnu8eva3~6;4CxrcH1{Qg{M;GT5@Bdqt%s{xkT;DyaBk)v>cTr#=XM@cQ-VZZJ1azh{1Df~fwf(mdYk_cEC``#zrevUuf1-I7DHKqx9c7Me?*iNur9a3~o)A1AmHbK!6#k<d+QmXjoUlrAc=R-8EfEvn$TP%?Zb2%`-;wF2Z7c~Qh!QUp%@F7d(Q;It@nl31iwc^NCTTrj*OW)bEH>BYlQ$YmihSV2QDxrCsKNToEmsNif~;-ILG+l$@~sMDcnEHYIbjb?L-swo%>NNY60QJ5`2LX(&$CFf*W(cl7t80939@QH+>;!kK4jMTiOQA}zM@dS+wmk4?RtsqIs(NtuZr(Ewj<zxXaVots!6<}UP5>nNp1gfkes4T*zd{)6h-GF4>NSQO}R*91{c`k!=D-D}baN$1fuVNrUDvGiYVXWYBI456{mCG`ukuZfpN)A<xyb=s}byE(DvZfmpRkvo4CMg+F*3C%f6#?m{g@T4u-G<~mB~wGXg;NVMFDj&f5<)qG1#7xlYdFEQ_jHRu*e&FUmQ1J<Gp}4$xq@yalC(x)S-FIEgQe+IxARLJPRm@DXx&t+<h5L0ORJ<E<cw}6ln6?exLHy}9_dE4pz17oL(~E`{a`E-no7?`5)pDEpNY(-6VaJ?C^<J9(GN!A;n`PTPDZBE;WN>5k=ams`uyy<xmZYd@Og|04{1U(*1PGLR>h3WX?aZWQf~69?j-FsmL^GvInrgidoM2}r1u&}XB+q}oGg-NR#n^X*4uqBy?1qY$4<jzMBhXA);zPfx3*xU!VW$#fFa&MCOfRHVn0%6k8aaRw9dY?)7!uP!nGHEb#k+JxY|2h>kX{N{%!`IfvPX|S@e!nA3Iy~#cKVr)%cFx{mYSGj9h1H_Q6edkhuGk)3Z9gWp`~mJzG74m7(!J^o(!2de`mO?3IDzcV;$RQ`@foiYHlj%{3;+>#iT|K>v-`YH)PTx#fRu(|@AsKT#P^)cna!|9sUyU-MtAxP}M>w|Cc1s4_KI9hlp2y|UAEJ$C2$4Oh6~@uj-!Y-5tEyI$Y%KECN4u6l<*?fcwR_fD^|+djDIJ5u!>A&1N9itm{<3o-un;-)89^#pIPd{VwyzH_1WOyqZ$H)k$XXD-xcUafgjb=N#i!+Onn-Tj-cEob+(!(BOWa>FtC;21DH{%^IHo=c%;r;jstN15qS_U^F=Ab$c5Oh5W?fY!%^vdXfE>5Yf!rHF^<aF`B*be*L=(CF(%-E<?)%b0$BJ)|f2ZjG%ISw+Z8XcC`j+)bpk<79YXWEkdaV7mwG_kiObaNYym&C&ix(EpA7N#?}|aRxAsRm;!2e%e)a4AvZnHUPvwCa?b&OiHoo')
36 STORE_NAME 4 (encoded_catalyst_strand)

10 38 PUSH_NULL
40 LOAD_NAME 5 (print)
42 LOAD_CONST 3 ('--- Calibrating Genetic Sequencer ---')
44 CALL 1
52 POP_TOP

11 54 PUSH_NULL
56 LOAD_NAME 5 (print)
58 LOAD_CONST 4 ('Decoding catalyst DNA strand...')
60 CALL 1
68 POP_TOP

12 70 PUSH_NULL
72 LOAD_NAME 0 (base64)
74 LOAD_ATTR 12 (b85decode)
94 LOAD_NAME 4 (encoded_catalyst_strand)
96 CALL 1
104 STORE_NAME 7 (compressed_catalyst)

13 106 PUSH_NULL
108 LOAD_NAME 1 (zlib)
110 LOAD_ATTR 16 (decompress)
130 LOAD_NAME 7 (compressed_catalyst)
132 CALL 1
140 STORE_NAME 9 (marshalled_genetic_code)

14 142 PUSH_NULL
144 LOAD_NAME 2 (marshal)
146 LOAD_ATTR 20 (loads)
166 LOAD_NAME 9 (marshalled_genetic_code)
168 CALL 1
176 STORE_NAME 11 (catalyst_code_object)

16 178 PUSH_NULL
180 LOAD_NAME 5 (print)
182 LOAD_CONST 5 ('Synthesizing Catalyst Serum...')
184 CALL 1
192 POP_TOP

19 194 PUSH_NULL
196 LOAD_NAME 3 (types)
198 LOAD_ATTR 24 (FunctionType)
218 LOAD_NAME 11 (catalyst_code_object)
220 PUSH_NULL
222 LOAD_NAME 13 (globals)
224 CALL 0
232 CALL 2
240 STORE_NAME 14 (catalyst_injection_function)

22 242 PUSH_NULL
244 LOAD_NAME 14 (catalyst_injection_function)
246 CALL 0
254 POP_TOP
256 RETURN_CONST 1 (None)

base85解码后解压缩得到一段新的字节码,继续反编译:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
  0           0 RESUME                   0

2 2 LOAD_CONST 0 (0)
4 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (os)
8 STORE_NAME 0 (os)

3 10 LOAD_CONST 0 (0)
12 LOAD_CONST 1 (None)
14 IMPORT_NAME 1 (sys)
16 STORE_NAME 1 (sys)

4 18 LOAD_CONST 0 (0)
20 LOAD_CONST 1 (None)
22 IMPORT_NAME 2 (emoji)
24 STORE_NAME 2 (emoji)

5 26 LOAD_CONST 0 (0)
28 LOAD_CONST 1 (None)
30 IMPORT_NAME 3 (random)
32 STORE_NAME 3 (random)

6 34 LOAD_CONST 0 (0)
36 LOAD_CONST 1 (None)
38 IMPORT_NAME 4 (asyncio)
40 STORE_NAME 4 (asyncio)

7 42 LOAD_CONST 0 (0)
44 LOAD_CONST 1 (None)
46 IMPORT_NAME 5 (cowsay)
48 STORE_NAME 5 (cowsay)

8 50 LOAD_CONST 0 (0)
52 LOAD_CONST 1 (None)
54 IMPORT_NAME 6 (pyjokes)
56 STORE_NAME 6 (pyjokes)

9 58 LOAD_CONST 0 (0)
60 LOAD_CONST 1 (None)
62 IMPORT_NAME 7 (art)
64 STORE_NAME 7 (art)

10 66 LOAD_CONST 0 (0)
68 LOAD_CONST 2 (('ARC4',))
70 IMPORT_NAME 8 (arc4)
72 IMPORT_FROM 9 (ARC4)
74 STORE_NAME 9 (ARC4)
76 POP_TOP

15 78 LOAD_CONST 3 (<code object activate_catalyst at 0x000001DBD89A1110, file "<catalyst_core>", line 15>)
80 MAKE_FUNCTION 0
82 STORE_NAME 10 (activate_catalyst)

54 84 PUSH_NULL
86 LOAD_NAME 4 (asyncio)
88 LOAD_ATTR 22 (run)
108 PUSH_NULL
110 LOAD_NAME 10 (activate_catalyst)
112 CALL 0
120 CALL 1
128 POP_TOP
130 RETURN_CONST 1 (None)

Disassembly of <code object activate_catalyst at 0x000001DBD89A1110, file "<catalyst_core>", line 15>:
15 0 RETURN_GENERATOR
2 POP_TOP
4 RESUME 0

16 6 LOAD_CONST 1 (b'm\x1b@I\x1dAoe@\x07ZF[BL\rN\n\x0cS')
8 STORE_FAST 0 (LEAD_RESEARCHER_SIGNATURE)

17 10 LOAD_CONST 2 (b'r2b-\r\x9e\xf2\x1fp\x185\x82\xcf\xfc\x90\x14\xf1O\xad#]\xf3\xe2\xc0L\xd0\xc1e\x0c\xea\xec\xae\x11b\xa7\x8c\xaa!\xa1\x9d\xc2\x90')
12 STORE_FAST 1 (ENCRYPTED_CHIMERA_FORMULA)

19 14 LOAD_GLOBAL 1 (NULL + print)
24 LOAD_CONST 3 ('--- Catalyst Serum Injected ---')
26 CALL 1
34 POP_TOP

20 36 LOAD_GLOBAL 1 (NULL + print)
46 LOAD_CONST 4 ("Verifying Lead Researcher's credentials via biometric scan...")
48 CALL 1
56 POP_TOP

22 58 LOAD_GLOBAL 3 (NULL + os)
68 LOAD_ATTR 4 (getlogin)
88 CALL 0
96 LOAD_ATTR 7 (NULL|self + encode)
116 CALL 0
124 STORE_FAST 2 (current_user)

25 126 LOAD_GLOBAL 9 (NULL + bytes)
136 LOAD_CONST 5 (<code object <genexpr> at 0x000001DBD8872730, file "<catalyst_core>", line 25>)
138 MAKE_FUNCTION 0
140 LOAD_GLOBAL 11 (NULL + enumerate)
150 LOAD_FAST 2 (current_user)
152 CALL 1
160 GET_ITER
162 CALL 0
170 CALL 1
178 STORE_FAST 3 (user_signature)

27 180 LOAD_GLOBAL 13 (NULL + asyncio)
190 LOAD_ATTR 14 (sleep)
210 LOAD_CONST 6 (0.01)
212 CALL 1
220 GET_AWAITABLE 0
222 LOAD_CONST 0 (None)
>> 224 SEND 3 (to 234)
228 YIELD_VALUE 2
230 RESUME 3
232 JUMP_BACKWARD_NO_INTERRUPT 5 (to 224)
>> 234 END_SEND
236 POP_TOP

29 238 LOAD_CONST 7 ('pending')
240 STORE_FAST 4 (status)

30 242 LOAD_FAST 4 (status)

31 244 LOAD_CONST 7 ('pending')
246 COMPARE_OP 40 (==)
250 EXTENDED_ARG 1
252 POP_JUMP_IF_FALSE 294 (to 842)

32 254 LOAD_FAST 3 (user_signature)
256 LOAD_FAST 0 (LEAD_RESEARCHER_SIGNATURE)
258 COMPARE_OP 40 (==)
262 POP_JUMP_IF_FALSE 112 (to 488)

33 264 LOAD_GLOBAL 17 (NULL + art)
274 LOAD_ATTR 18 (tprint)
294 LOAD_CONST 8 ('AUTHENTICATION SUCCESS')
296 LOAD_CONST 9 ('small')
298 KW_NAMES 10 (('font',))
300 CALL 2
308 POP_TOP

34 310 LOAD_GLOBAL 1 (NULL + print)
320 LOAD_CONST 11 ('Biometric scan MATCH. Identity confirmed as Lead Researcher.')
322 CALL 1
330 POP_TOP

35 332 LOAD_GLOBAL 1 (NULL + print)
342 LOAD_CONST 12 ('Finalizing Project Chimera...')
344 CALL 1
352 POP_TOP

37 354 LOAD_GLOBAL 21 (NULL + ARC4)
364 LOAD_FAST 2 (current_user)
366 CALL 1
374 STORE_FAST 5 (arc4_decipher)

38 376 LOAD_FAST 5 (arc4_decipher)
378 LOAD_ATTR 23 (NULL|self + decrypt)
398 LOAD_FAST 1 (ENCRYPTED_CHIMERA_FORMULA)
400 CALL 1
408 LOAD_ATTR 25 (NULL|self + decode)
428 CALL 0
436 STORE_FAST 6 (decrypted_formula)

41 438 LOAD_GLOBAL 27 (NULL + cowsay)
448 LOAD_ATTR 28 (cow)
468 LOAD_CONST 13 ('I am alive! The secret formula is:\n')
470 LOAD_FAST 6 (decrypted_formula)
472 BINARY_OP 0 (+)
476 CALL 1
484 POP_TOP
486 RETURN_CONST 0 (None)

43 >> 488 LOAD_GLOBAL 17 (NULL + art)
498 LOAD_ATTR 18 (tprint)
518 LOAD_CONST 14 ('AUTHENTICATION FAILED')
520 LOAD_CONST 9 ('small')
522 KW_NAMES 10 (('font',))
524 CALL 2
532 POP_TOP

44 534 LOAD_GLOBAL 1 (NULL + print)
544 LOAD_CONST 15 ('Impostor detected, my genius cannot be replicated!')
546 CALL 1
554 POP_TOP

45 556 LOAD_GLOBAL 1 (NULL + print)
566 LOAD_CONST 16 ('The resulting specimen has developed an unexpected, and frankly useless, sense of humor.')
568 CALL 1
576 POP_TOP

47 578 LOAD_GLOBAL 31 (NULL + pyjokes)
588 LOAD_ATTR 32 (get_joke)
608 LOAD_CONST 17 ('en')
610 LOAD_CONST 18 ('all')
612 KW_NAMES 19 (('language', 'category'))
614 CALL 2
622 STORE_FAST 7 (joke)

48 624 LOAD_GLOBAL 26 (cowsay)
634 LOAD_ATTR 34 (char_names)
654 LOAD_CONST 20 (1)
656 LOAD_CONST 0 (None)
658 BINARY_SLICE
660 STORE_FAST 8 (animals)

49 662 LOAD_GLOBAL 1 (NULL + print)
672 LOAD_GLOBAL 27 (NULL + cowsay)
682 LOAD_ATTR 36 (get_output_string)
702 LOAD_GLOBAL 39 (NULL + random)
712 LOAD_ATTR 40 (choice)
732 LOAD_FAST 8 (animals)
734 CALL 1
742 LOAD_GLOBAL 31 (NULL + pyjokes)
752 LOAD_ATTR 32 (get_joke)
772 CALL 0
780 CALL 2
788 CALL 1
796 POP_TOP

50 798 LOAD_GLOBAL 43 (NULL + sys)
808 LOAD_ATTR 44 (exit)
828 LOAD_CONST 20 (1)
830 CALL 1
838 POP_TOP
840 RETURN_CONST 0 (None)

51 >> 842 NOP

52 844 LOAD_GLOBAL 1 (NULL + print)
854 LOAD_CONST 21 ('System error: Unknown experimental state.')
856 CALL 1
864 POP_TOP
866 RETURN_CONST 0 (None)

27 >> 868 CLEANUP_THROW
870 EXTENDED_ARG 1
872 JUMP_BACKWARD 320 (to 234)
>> 874 CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
876 RERAISE 1
ExceptionTable:
4 to 226 -> 874 [0] lasti
228 to 228 -> 868 [2]
230 to 868 -> 874 [0] lasti

Disassembly of <code object <genexpr> at 0x000001DBD8872730, file "<catalyst_core>", line 25>:
25 0 RETURN_GENERATOR
2 POP_TOP
4 RESUME 0
6 LOAD_FAST 0 (.0)
>> 8 FOR_ITER 15 (to 42)
12 UNPACK_SEQUENCE 2
16 STORE_FAST 1 (i)
18 STORE_FAST 2 (c)
20 LOAD_FAST 2 (c)
22 LOAD_FAST 1 (i)
24 LOAD_CONST 0 (42)
26 BINARY_OP 0 (+)
30 BINARY_OP 12 (^)
34 YIELD_VALUE 1
36 RESUME 1
38 POP_TOP
40 JUMP_BACKWARD 17 (to 8)
>> 42 END_FOR
44 RETURN_CONST 1 (None)
>> 46 CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
48 RERAISE 1
ExceptionTable:
4 to 44 -> 46 [0] lasti

得到密钥b’m\x1b@I\x1dAoe@\x07ZF[BL\rN\n\x0cS’和密文b’r2b-\r\x9e\xf2\x1fp\x185\x82\xcf\xfc\x90\x14\xf1O\xad#]\xf3\xe2\xc0L\xd0\xc1e\x0c\xea\xec\xae\x11b\xa7\x8c\xaa!\xa1\x9d\xc2\x90’,其中密钥有一个xor 42+idx的自解密,解密得到G0ld3n_Tr4nsmut4t10n,再解密即可

Th3_Alch3m1sts_S3cr3t_F0rmul4@flare-on.com

#3 pretty_devilish_file

文件是一个伪造的pdf,其中有一个320字节的buffer似乎是zlib压缩格式,但是被AES加密了,先解密:

1
qpdf --decrypt --stream-data=uncompress pretty_devilish_file.pdf decrypted.pdf

然后查看解压出来的内容,发现了一串hex:

1
ffd8ffe000104a46494600010100000100010000ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc0000b080001002501011100ffc40017000100030000000000000000000000000006040708ffc400241000000209050100000000000000000000000702050608353776b6b7030436747577ffda0008010100003f00c54d3401dcbbfb9c38db8a7dd265a2159e9d945a086407383aabd52e5034c274e57179ef3bcdfca50f0af80aff00e986c64568c7ffd9

fromhex后是一个jpg图片,只有一排像素,有明暗相间条纹,读取灰度:

1
2
3
4
5
6
7
from PIL import Image

image_path = "data.jpg"
img = Image.open(image_path).convert('L')
width, height = img.size
pixels = bytearray(img.getdata())[:width]
print(pixels)

Puzzl1ng-D3vilish-F0rmat@flare-on.com

#4 UnholyDragon

MZ头被改,改回去,程序运行后生成了4个副本并退出,编写脚本比较:

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
import sys
import os

def find_file_differences(original_path, new_paths):
with open(original_path, 'rb') as f: original_data = f.read()
original_size = len(original_data)
print(f"原始文件 '{original_path}' 大小: {original_size} 字节.")
all_differences = {}
for new_path in new_paths:
with open(new_path, 'rb') as f: new_data = f.read()
new_size = len(new_data)
if new_size != original_size: min_length = min(original_size, new_size)
else: min_length = original_size
for offset in range(min_length):
byte1 = original_data[offset]
byte2 = new_data[offset]
if byte1 != byte2:
if offset not in all_differences:
all_differences[offset] = { 'original_byte': byte1, 'files': {}}
all_differences[offset]['files'][new_path] = byte2
if not all_differences: return
sorted_offsets = sorted(all_differences.keys())
print(f"\n --- Total {len(sorted_offsets)} offsets have difference ---")
print("-----------------------------------------------")
header = [" Offsets ", "Orig", " 151", " 152", " 153", " 154"]
print(" | ".join(header))
print("-----------------------------------------------")
for offset in sorted_offsets:
diff = all_differences[offset]
orig_byte = diff['original_byte']
offset_hex = hex(offset)[2:].upper().zfill(8)
orig_hex = hex(orig_byte)[2:].upper().zfill(2)
output = [f"0x{offset_hex}", f"0x{orig_hex}"]
for i in range(1, 5):
file_name = f'UnholyDragon-{150+i}.exe'
if file_name in diff['files']:
new_byte = diff['files'][file_name]
new_hex = hex(new_byte)[2:].upper().zfill(2)
output.append(f"0x{new_hex}")
else:
output.append(" -- ")
print(" | ".join(output))

if __name__ == "__main__":
original_file = 'UnholyDragon-150.exe'
new_files = [f'UnholyDragon-{150+i}.exe' for i in range(1, 5)]
find_file_differences(original_file, new_files)

发现有4处恰好分别有1、2、3、4个副本不同(虚拟偏移量=真实偏移量+0x400C00):

1
2
3
4
5
6
7
8
    --- Total 4 offsets have difference ---
-----------------------------------------------
Offsets | Orig | 151 | 152 | 153 | 154 虚拟偏移量 段
-----------------------------------------------
0x0006E8F8 | 0xFF | -- | -- | -- | 0x68 0x46F4F8 text
0x001309C1 | 0x5C | 0x94 | 0x94 | 0x94 | 0x94 0x5315C1 data
0x001B19A9 | 0x84 | -- | -- | 0x96 | 0x96 0x5B25A9 data
0x00286162 | 0x0D | -- | 0x43 | 0x43 | 0x43 0x686D62 data

start:

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
int start()
{
TlsIndex *= 4;
hHeap = GetProcessHeap();
OleInitialize(0);
picce_.dwICC = 0;
picce_.dwSize = 8;
InitCommonControlsEx(&picce_);
dword_4B08BC = (int)&off_68EC60;
hModule = GetModuleHandleA(0);
v0 = sub_402068();
LOBYTE(v1) = 0;
if ( v0 >= 0 )
{
v2 = sub_402137(v1);
LOBYTE(v3) = 1;
if ( v2 >= 0 )
{
sub_470792(v3);
sub_4706D3();
sub_470419();
sub_470183();
sub_470419();
sub_470419();
if ( (int)sub_46FE5C(&unk_4B20A8) >= 0 )
{
sub_40225A();
dword_4AD3C0 = (int)&unk_4B1F28;
dword_4B0750 = (int)&unk_4B1E53;
strIn = aUnholydragon; // "UnholyDragon"
dword_4ADFEC = (OLECHAR *)&unk_4B1236;
dword_4B075C = (int)&unk_4B1DFE;
sub_402440(p_sub_4A9C4E, &dword_4AFFD0);
sub_470732();
sub_47065B();
sub_47027D();
sub_4700F6();
sub_47027D();
sub_47027D();
OleUninitialize();
ExitProcess(0);
}
}
}
sub_4A7692();
v4 = (int (*)(void))sub_4A64B1();
return v4();
}

是自己实现的启动函数,由于不好定位关键逻辑,所以尝试断点WriteFile函数,发现sub_4A8AC8是实际执行的函数:

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
void __stdcall SUB_4A8AC8_patchfile(int Hi, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite)
{
DWORD nNumberOfBytesToWrite_1; // edi
DWORD nNumberOfBytesToWrite_2; // eax
int v5; // ecx

nNumberOfBytesToWrite_1 = nNumberOfBytesToWrite;
if ( nNumberOfBytesToWrite )
{
if ( (*(_BYTE *)(Hi + 8) & 4) != 0 )
{
nNumberOfBytesToWrite = 0;
WriteFile(*(HANDLE *)Hi, lpBuffer, nNumberOfBytesToWrite_1, &nNumberOfBytesToWrite, 0);
nNumberOfBytesToWrite_2 = nNumberOfBytesToWrite;
if ( nNumberOfBytesToWrite != nNumberOfBytesToWrite_1 )
*(_DWORD *)(Hi + 36) = -2146828213;
*(_DWORD *)(Hi + 4) += nNumberOfBytesToWrite_2;
}
else
{
v5 = *(_DWORD *)(Hi + 20);
*(_DWORD *)(Hi + 20) = v5 + nNumberOfBytesToWrite;
if ( (signed int)(v5 + nNumberOfBytesToWrite_1) <= *(_DWORD *)(Hi + 12) )
sub_4A8962(v5 + *(_DWORD *)(Hi + 16), lpBuffer, nNumberOfBytesToWrite_1);
}
}
}

其中第一次断点时lpBuffer的值就是第一个被修改的字节0x94,nNumberOfBytesToWrite_1是1,查询谁调用了这个函数,得到sub_4A8F30,其中有一些关键逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
416: 
pvarLeft.decVal.Hi32 = buf_[0].cyVal.Hi - 1; //获取文件名后缀
*(_DWORD *)&pvarLeft.vt = (buf_[0].cyVal.Hi - 1) ^ 0x6746; //文件名后缀xor 0x6746
pvarSrc.cyVal.Hi = *((_DWORD *)&buf_[1].decVal.Lo64 + 2 * v3 + 1);
429:
*(_DWORD *)&buf_[3].vt = (&bstrLeft_1)[2 * v3];
*(_QWORD *)&pvarLeft.vt = __PAIR64__(buf_[2].cyVal.Lo, *(unsigned int *)&buf_[3].vt); //被修改的地址
pvarSrc.cyVal.Hi = Hi_4;
433:
sub_4A5EEF(pvarSrc.cyVal.Hi, &pvarSrc.decVal.Lo32 + v3 - 32, 1u);
Hi32_1 = sub_4A5FB2(pvarSrc.cyVal.Hi);
437:
LOBYTE(Hi32_1) = buf_[3].decVal.Hi32;
pvarLeft.decVal.Hi32 = Hi32_1;
*(_DWORD *)&pvarLeft.vt = LOBYTE(buf_[3].decVal.Hi32);
pvarSrc.cyVal.Hi = *((_DWORD *)&buf_[1].decVal.Lo64 + 2 * v3 + 1);
443:
Hi_5 = *(_DWORD *)&pvarLeft.vt ^ (unsigned int)(&bstrRight)[2 * v3]; //单字节异或加密

因为本质是基于固定表的单字节的异或加密,所以直接把150改成0即可,运行发现生成了一堆exe,运行其中的编号150得到flag:

dr4g0n_d3n1al_of_s3rv1ce@flare-on.com

#5 ntfsm

程序中存在一个12M的巨型函数sub_14000C0B0,以及一个90781项的跳转表jmptable_140C687B8,在其反编译之前按U取消定义,否则会卡死

注意到函数体中都会jmp到loc_140C685EE,猜测此为控制流分发函数,跳转表内为全部函数的顺序

程序从start开始做了一些简单的初始化后直接进入0x14000C0B0,从这里开始分析,发现只要进行代码操作就会因跳转表无法解析卡死,所以直接清零整个跳转表后再识别

1
2
3
4
5
6
7
8
9
10
11
12
import ida_bytes
import ida_ida
import ida_kernwin

start_addr = 0x140C687B8
num_dwords = 90781
size = num_dwords * 4
end_addr = start_addr + size

max_ea = ida_ida.inf_get_max_ea()
zero_bytes = b'\x00' * size
patched = ida_bytes.patch_bytes(start_addr, zero_bytes)

然后将0x14000C0B0的位置开始反编译,直到末尾出现一个jmp rcx(间接跳转):

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
__int64 __fastcall main_logic(int n2, __int64 a2)
{
fsm_make_struct((__int64)v125, (__int64)"state");
fsm_make_struct((__int64)v126, (__int64)"input");
fsm_make_struct((__int64)v127, (__int64)"position");
fsm_make_struct((__int64)v128, (__int64)"transitions");
n16 = 0;
lpBuffer_[0] = 0;
lpBuffer_[1] = v105;
ptr = fsm_get_ptr((__int64)v105, (__int64)v127);
ptr_1 = ptr;
fsm_load_stage(ptr, &n16);
v7 = v106;
ptr_2 = fsm_get_ptr((__int64)v106, (__int64)v128);
ptr_3 = ptr_2;
fsm_load_stage(ptr_2, lpBuffer_);
if ( n16 == 16 )
{
if ( lpBuffer_[0] == 16 )
{
printf("correct!\n");
memset(p_Filename, 0, 0x11u);
v10 = v107;
ptr_4 = fsm_get_ptr((__int64)v107, (__int64)v126);
ptr_5 = ptr_4;
if ( (unsigned __int8)sub_140003E0E(ptr_4, p_Filename, 0x10u) )
{
fsm_make_struct((__int64)v129, (__int64)p_Filename);
j_Reward_function((__int64)v129);
sub_140001947((__int64)v129);
}
...
}
else
{
printf("wrong!\n");
...
}
}
memset(buf, 0, 0x11u);
v45 = v115;
ptr_30 = fsm_get_ptr((__int64)v115, (__int64)v125);
ptr_31 = ptr_30;
fsm_load_stage(ptr_30, &jmptable_size);
if ( jmptable_size == -1 )
{
if ( n2 == 2 )
{
if ( !(unsigned int)sub_140004822(*(_QWORD *)(a2 + 8), "-r") )
{
...
}
if ( sub_1400026CB(*(_QWORD *)(a2 + 8)) != 16 )
{
printf("input 16 characters");
sub_1400013ED(1);
}
...
}
else
{
printf("usage: ./ntfsm <password>\nto reset the binary in case of weird behavior: ./ntfsm -r");
sub_1400013ED(1);
}
}
v76 = v124;
ptr_52 = fsm_get_ptr((__int64)v124, (__int64)v126);
ptr_53 = ptr_52;
sub_140003E0E(ptr_52, buf, 0x10u);
v79 = 0;
v80 = 0;
v81 = 0;
v82 = 0;
if ( jmptable_size == -1 )
jmptable_size = 0;
n0x1629C = jmptable_size;
if ( (unsigned __int64)jmptable_size <= 0x1629C )
__asm { jmp rcx }
printf("Something went wrong!");
...
}

程序是一个有限状态机,第一次初始化时,将输入存入程序的:input数据流,之后都从这个数据流读取(除非-r清除),查看其隐藏数据流的方式:

1
2
3
4
5
6
$FilePath = ".\ntfsm.exe"
$StreamName = "state"
$ADSPath = "$FilePath`:$StreamName"
$Bytes = Get-Content -Path $ADSPath -Encoding Byte -Raw
$Hex = [System.BitConverter]::ToString($Bytes)
Write-Output $Hex

程序用输入的16字节经过处理后检测返回值是不是16,如果是则解密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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
__int64 __fastcall Reward_function(__int64 a1)
{
phAlgorithm = 0;
phKey = 0;
sub_140003260(v28);
v4 = BCryptOpenAlgorithmProvider(&phAlgorithm, L"AES", 0, 0);
if ( v4 >= 0 )
{
v5 = BCryptSetProperty(phAlgorithm, L"ChainingMode", (PUCHAR)L"ChainingModeCBC", 0x20u, 0);
if ( v5 >= 0 )
{
if ( BCryptOpenAlgorithmProvider(&hAlgorithm, L"SHA256", 0, 0) >= 0 )
{
if ( BCryptCreateHash(hAlgorithm, &phHash, 0, 0, 0, 0, 0) >= 0 )
{
cbInput = sub_140003B2A(a1);
pbInput = (PUCHAR)sub_1400014B5(a1);
hHash = phHash;
if ( BCryptHashData(phHash, pbInput, cbInput, 0) >= 0 )
{
v2 = sub_140002810((__int64)&v11);
sub_140002D74(v29, 32, v2);
cbOutput = sub_1400025BD(v29);
pbOutput = (PUCHAR)sub_140004778(v29);
phHash_1 = phHash;
v6 = BCryptFinishHash(phHash, pbOutput, cbOutput, 0);
BCryptDestroyHash(phHash);
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
if ( v6 >= 0 )
{
cbSecret = sub_1400025BD(v29);
pbSecret = (PUCHAR)sub_140004778(v29);
phAlgorithm_1 = phAlgorithm;
v7 = BCryptGenerateSymmetricKey(phAlgorithm, &phKey, 0, 0, pbSecret, cbSecret, 0);
if ( v7 >= 0 )
{
if ( phKey )
{
v34 = sub_140002810((__int64)&v12);
cipher[0] = -127;
...
qmemcpy(dst_, (const void *)sub_140002649((__int64)v78, (__int64)cipher, (__int64)v84), sizeof(dst_));
qmemcpy(dst, dst_, sizeof(dst));
sub_140003729((__int64)v30, (__int64)dst, v34);
if ( (unsigned __int64)sub_1400025BD(v30) >= 0x10 )
{
...
pcbResult = 0;
cbIV = sub_1400025BD(v72);
pbIV = (PUCHAR)sub_140004778(v72);
cbInput_1 = sub_1400025BD(v71);
pbInput_1 = (PUCHAR)sub_140004778(v71);
hKey = phKey;
v8 = BCryptDecrypt(phKey, pbInput_1, cbInput_1, 0, pbIV, cbIV, 0, 0, &pcbResult, 1u);
if ( v8 >= 0 )
{
v62 = sub_140002810((__int64)v15);
n32 = pcbResult;
sub_140002D74(v73, pcbResult, v62);
pcbResult_2 = pcbResult;
pbOutput_1 = (PUCHAR)sub_140004778(v73);
cbIV_1 = sub_1400025BD(v72);
pbIV_1 = (PUCHAR)sub_140004778(v72);
cbInput_2 = sub_1400025BD(v71);
pbInput_2 = (PUCHAR)sub_140004778(v71);
phKey_1 = phKey;
v9 = BCryptDecrypt(
phKey,
pbInput_2,
cbInput_2,
0,
pbIV_1,
cbIV_1,
pbOutput_1,
pcbResult_2,
&pcbResult,
1u);
if ( v9 >= 0 )
{
sub_140002A22(v73, pcbResult);
v3 = (const char *)sub_140004778(v73);
printf("Your reward: %s", v3);
sub_140001F9B(v73);
sub_140001F9B(v71);
sub_140001F9B(v72);
sub_140001F9B(v30);
sub_140001F9B(v29);
return sub_140001F9B(v28);
}
...

定义了一个64字节密文,并用之前正确输入的sha256做密钥进行AES_CBC解密

动态调试发现会jmp rcx,此时进入跳转表第一个函数:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
.text:0000000140860241 sub_140860241 proc near
.text:0000000140860241
.text:0000000140860241 lpDirectory= qword ptr 20h
.text:0000000140860241 nShowCmd= dword ptr 28h
.text:0000000140860241 arg_28= byte ptr 30h
.text:0000000140860241 arg_3BB84= byte ptr 3BB8Ch
.text:0000000140860241 arg_58AB0= qword ptr 58AB8h
.text:0000000140860241 arg_58D10= qword ptr 58D18h
.text:0000000140860241 arg_58D18= qword ptr 58D20h
.text:0000000140860241 arg_58D28= qword ptr 58D30h
.text:0000000140860241
.text:0000000140860241 rdtsc
.text:0000000140860243 shl rdx, 20h
.text:0000000140860247 or rax, rdx
.text:000000014086024A mov [rsp+arg_58D10], rax
.text:0000000140860252
.text:0000000140860252 loc_140860252: ; CODE XREF: sub_140860241+3E↓j
.text:0000000140860252 rdtsc
.text:0000000140860254 shl rdx, 20h
.text:0000000140860258 or rax, rdx
.text:000000014086025B mov [rsp+arg_58D18], rax
.text:0000000140860263 mov rax, [rsp+arg_58D10]
.text:000000014086026B mov rcx, [rsp+arg_58D18]
.text:0000000140860273 sub rcx, rax
.text:0000000140860276 mov rax, rcx
.text:0000000140860279 cmp rax, 12AD1659h
.text:000000014086027F jl short loc_140860252
.text:0000000140860281 movzx eax, [rsp+arg_28]
.text:0000000140860286 mov [rsp+arg_3BB84], al
.text:000000014086028D cmp [rsp+arg_3BB84], 4Ah ; 'J'
.text:0000000140860295 jz short loc_1408602CE
.text:0000000140860297 cmp [rsp+arg_3BB84], 55h ; 'U'
.text:000000014086029F jz short loc_1408602EF
.text:00000001408602A1 cmp [rsp+arg_3BB84], 69h ; 'i'
.text:00000001408602A9 jz short loc_1408602AD
.text:00000001408602AB jmp short loc_140860310
.text:00000001408602AD ; ---------------------------------------------------------------------------
.text:00000001408602AD
.text:00000001408602AD loc_1408602AD: ; CODE XREF: sub_140860241+68↑j
.text:00000001408602AD mov [rsp+arg_58D28], 1
.text:00000001408602B9 mov rax, [rsp+arg_58AB0]
.text:00000001408602C1 inc rax
.text:00000001408602C4 mov [rsp+arg_58AB0], rax
.text:00000001408602CC jmp short loc_14086033F
.text:00000001408602CE ; ---------------------------------------------------------------------------
.text:00000001408602CE
.text:00000001408602CE loc_1408602CE: ; CODE XREF: sub_140860241+54↑j
.text:00000001408602CE mov [rsp+arg_58D28], 2
.text:00000001408602DA mov rax, [rsp+arg_58AB0]
.text:00000001408602E2 inc rax
.text:00000001408602E5 mov [rsp+arg_58AB0], rax
.text:00000001408602ED jmp short loc_14086033F
.text:00000001408602EF ; ---------------------------------------------------------------------------
.text:00000001408602EF
.text:00000001408602EF loc_1408602EF: ; CODE XREF: sub_140860241+5E↑j
.text:00000001408602EF mov [rsp+arg_58D28], 3
.text:00000001408602FB mov rax, [rsp+arg_58AB0]
.text:0000000140860303 inc rax
.text:0000000140860306 mov [rsp+arg_58AB0], rax
.text:000000014086030E jmp short loc_14086033F
.text:0000000140860310 ; ---------------------------------------------------------------------------
.text:0000000140860310
.text:0000000140860310 loc_140860310: ; CODE XREF: sub_140860241+6A↑j
.text:0000000140860310 mov [rsp+nShowCmd], 5 ; nShowCmd
.text:0000000140860318 mov [rsp+lpDirectory], 0 ; lpDirectory
.text:0000000140860321 lea r9, Parameters ; " /c msg * Hello there, Hacker"
.text:0000000140860328 lea r8, File ; "cmd.exe"
.text:000000014086032F lea rdx, Operation ; "open"
.text:0000000140860336 xor ecx, ecx ; hwnd
.text:0000000140860338 call cs:__imp_ShellExecuteA
.text:000000014086033E nop
.text:000000014086033F
.text:000000014086033F loc_14086033F: ; CODE XREF: sub_140860241+8B↑j
.text:000000014086033F ; sub_140860241+AC↑j ...
.text:000000014086033F jmp control_flow_distributor
.text:000000014086033F sub_140860241 endp

比较第一个字节是J、U、i中的一个,根据其不同向跳转表指针状态位+2、3、1,否则弹窗,之后返回到控制流分发函数,保存上一个函数中的状态值,并跳转到下一个函数,之后所有的函数都是这个形式,因此编写规则匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re
import sys

def find(file_path):
rdtsc = bytes.fromhex("0F3148C1E220480BC2")
compare = bytes.fromhex("482BC8488BC1483D5916AD127CD1")
pattern = re.compile(
re.escape(rdtsc)+b'.{8}'+re.escape(rdtsc)+b'.{24}'+re.escape(compare),
re.DOTALL)
with open(file_path, 'rb') as f:
data = f.read()
matches = pattern.findall(data)
return len(matches)

if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py <File>")
sys.exit(1)
file_path = sys.argv[1]
count = find(file_path)
print(f"Found {count} matches.")

刚好匹配到全部90781个函数,提取其后的比较和增加量:

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
import re
import sys

def signed_byte(b): return b - 256 if b >= 128 else b

def find_and_extract(file_path):
rdtsc = bytes.fromhex("0F3148C1E220480BC2")
compare = bytes.fromhex("482BC8488BC1483D5916AD127CD1")
base_pattern = re.escape(rdtsc) + b'.{8}' + re.escape(rdtsc) + b'.{24}' + re.escape(compare)
pattern = re.compile(base_pattern, re.DOTALL)
with open(file_path, 'rb') as f: data = f.read()
results = []
for match in pattern.finditer(data):
start_offset = match.start()
base_end = match.end()
pos = base_end + 12
if data[pos] != 0x80:
new_offset = start_offset + 0xC00
results.append(f"0x{new_offset:X}\tEOF")
continue
output_parts = [f"0x{(start_offset + 0xC00):X}"]
current_pos = pos
while True:
cmp_val = data[current_pos + 7]
jz_addr = current_pos + 8
jz_disp = data[jz_addr + 1]
target_addr = jz_addr + 1 + signed_byte(jz_disp)
mov_val_addr = target_addr + 9
dword_bytes = data[mov_val_addr : mov_val_addr + 4]
dword_val = int.from_bytes(dword_bytes, byteorder='little')
output_parts.append(f"0x{cmp_val:02X}")
output_parts.append(f"0x{dword_val:08X}")
next_pos = current_pos + 10
if data[next_pos] != 0x80:
output_parts.append("EOF")
break
current_pos = next_pos
results.append("\t".join(output_parts))
return results

if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py <File>")
sys.exit(1)
file_path = sys.argv[1]
results = find_and_extract(file_path)
with open("out.txt", "w") as out_file:
for line in results:
out_file.write(line + "\n")
print(f"Write to out.txt (Total {len(results)} cases)")

用输出的out.txt和jmptable进行dfs:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import sys

def read_jmptable(jmptable_path):
with open(jmptable_path, 'rb') as f: data = f.read()
assert len(data) == 90781 * 4, f"jmptable size error: {len(data)} bytes"
table = []
for i in range(90781):
dword = int.from_bytes(data[i*4:(i+1)*4], 'little')
table.append(dword)
return table

def parse_out_txt(out_path):
state_map = {}
with open(out_path, 'r') as f:
for line in f:
parts = line.strip().split('\t')
if not parts or parts[0] == '': continue
state_key = int(parts[0], 16)
transitions = []
i = 1
while i + 1 < len(parts) and parts[i] != 'EOF':
char_str = parts[i]
next_str = parts[i+1]
if char_str == 'EOF' or next_str == 'EOF': break
char_byte = int(char_str, 16)
next_index = int(next_str, 16)
transitions.append((char_byte, next_index))
i += 2
state_map[state_key] = transitions
return state_map

def byte_to_char(b):
if 32 <= b <= 126: return chr(b)
else: return f"\\x{b:02X}"

def dfs(current_key, jmptable, state_map, path, visited, all_paths):
transitions = state_map[current_key]
if not transitions:
all_paths.append(''.join(path))
return
visited.add(current_key)
for char_byte, next_index in transitions:
next_key = jmptable[next_index]
new_path = path + [byte_to_char(char_byte)]
dfs(next_key, jmptable, state_map, new_path, visited, all_paths)
visited.remove(current_key)

def main():
if len(sys.argv) != 2:
print("Usage: python dfs_search.py <jmptable_file>")
sys.exit(1)

jmptable_path = sys.argv[1]
out_path = "out.txt"
output_path = "cases.txt"

print("Reading jmptable...")
jmptable = read_jmptable(jmptable_path)

print("Parsing out.txt...")
state_map = parse_out_txt(out_path)

start_key = 0x860241

if start_key not in state_map:
print(f"Start key 0x{start_key:X} not found in out.txt")
return

all_paths = []
dfs(start_key, jmptable, state_map, [], set(), all_paths)
max_len = max(len(p) for p in all_paths)
longest_paths = [p for p in all_paths if len(p) == max_len]
longest_paths = sorted(set(longest_paths))
print(f"Found {len(longest_paths)} longest path(s) of length {max_len}")
with open(output_path, 'w') as f:
for path in longest_paths: f.write(path + '\n')
print(f"Longest paths written to {output_path}")

if __name__ == "__main__":
main()

得到唯一解:iqg0nSeCHnOMPm2Q

f1n1t3_st4t3_m4ch1n3s_4r3_fun@flare-on.com

#6 Chain of Demands

发现是pyinstaller打包的elf文件,提取并反编译:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
import tkinter as tk
from tkinter import scrolledtext, messagebox, simpledialog, Checkbutton, BooleanVar, Toplevel
import platform
import hashlib
import time
import json
from threading import Thread
import math
import random
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, long_to_bytes, isPrime
import os
import sys
from web3 import Web3
from eth_account import Account
from eth_account.signers.local import LocalAccount

def resource_path(relative_path):
"""\n Get the absolute path to a resource, which works for both development\n and for a PyInstaller-bundled executable.\n """ # inserted
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath('.')
return os.path.join(base_path, relative_path)

class SmartContracts:
rpc_url = ''
private_key = ''

def deploy_contract(contract_bytes, contract_abi):
try:
w3 = Web3(Web3.HTTPProvider(SmartContracts.rpc_url))
if not w3.is_connected():
raise ConnectionError(f'[!] Failed to connect to Ethereum network at {SmartContracts.rpc_url}')
print(f'[+] Connected to Sepolia network at {SmartContracts.rpc_url}')
print(f'[+] Current block number: {w3.eth.block_number}')
if not SmartContracts.private_key:
raise ValueError('Please add your private key.')
account = Account.from_key(SmartContracts.private_key)
w3.eth.default_account = account.address
print(f'[+] Using account: {account.address}')
balance_wei = w3.eth.get_balance(account.address)
print(f"[+] Account balance: {w3.from_wei(balance_wei, 'ether')} ETH")
if balance_wei == 0:
print('[!] Warning: Account has 0 ETH. Deployment will likely fail. Get some testnet ETH from a faucet (e.g., sepoliafaucet.com)!')
Contract = w3.eth.contract(abi=contract_abi, bytecode=contract_bytes)
gas_estimate = Contract.constructor().estimate_gas()
print(f'[+] Estimated gas for deployment: {gas_estimate}')
gas_price = w3.eth.gas_price
print(f"[+] Current gas price: {w3.from_wei(gas_price, 'gwei')} Gwei")
transaction = Contract.constructor().build_transaction({'from': account.address, 'nonce': w3.eth.get_transaction_count(account.address), 'gas': gas_estimate + 200000, 'gasPrice': gas_price})
signed_txn = w3.eth.account.sign_transaction(transaction, private_key=SmartContracts.private_key)
print('[+] Deploying contract...')
tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
print(f'[+] Deployment transaction sent. Hash: {tx_hash.hex()}')
print('[+] Waiting for transaction to be mined...')
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=300)
print(f'[+] Transaction receipt: {tx_receipt}')
if tx_receipt.status == 0:
print('[!] Transaction failed (status 0). It was reverted.')
return
contract_address = tx_receipt.contractAddress
print(f'[+] Contract deployed at address: {contract_address}')
deployed_contract = w3.eth.contract(address=contract_address, abi=contract_abi)
return deployed_contract
except ConnectionError as e:
print(f'[!] Connection error: {e}')
print('Please check your RPC_URL and network connection.')
return None
except ValueError as e:
print(f'[!] Configuration error: {e}')
return None
except Exception as e:
print(f'[!] An unexpected error occurred: {e}')
return None

class LCGOracle:
def __init__(self, multiplier, increment, modulus, initial_seed):
self.multiplier = multiplier
self.increment = increment
self.modulus = modulus
self.state = initial_seed
self.contract_bytes = '6080604052348015600e575f5ffd5b506102e28061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063115218341461002d575b5f5ffd5b6100476004803603810190610042919061010c565b61005d565b6040516100549190610192565b60405180910390f35b5f5f848061006e5761006d6101ab565b5b86868061007e5761007d6101ab565b5b8987090890505f5f8411610092575f610095565b60015b60ff16905081816100a69190610205565b858260016100b49190610246565b6100be9190610205565b6100c89190610279565b9250505095945050505050565b5f5ffd5b5f819050919050565b6100eb816100d9565b81146100f5575f5ffd5b50565b5f81359050610106816100e2565b92915050565b5f5f5f5f5f60a08688031215610125576101246100d5565b5b5f610132888289016100f8565b9550506020610143888289016100f8565b9450506040610154888289016100f8565b9350506060610165888289016100f8565b9250506080610176888289016100f8565b9150509295509295909350565b61018c816100d9565b82525050565b5f6020820190506101a55f830184610183565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61020f826100d9565b915061021a836100d9565b9250828202610228816100d9565b9150828204841483151761023f5761023e6101d8565b5b5092915050565b5f610250826100d9565b915061025b836100d9565b9250828203905081811115610273576102726101d8565b5b92915050565b5f610283826100d9565b915061028e836100d9565b92508282019050808211156102a6576102a56101d8565b5b9291505056fea2646970667358221220c7e885c1633ad951a2d8168f80d36858af279d8b5fe2e19cf79eac15ecb9fdd364736f6c634300081e0033'
self.contract_abi = [{'inputs': [{'internalType': 'uint256', 'name': 'LCG_MULTIPLIER', 'type': 'uint256'}, {'internalType': 'uint256', 'name': 'LCG_INCREMENT', 'type': 'uint256'}, {'internalType': 'uint256', 'name': 'LCG_MODULUS', 'type': 'uint256'}, {'internalType': 'uint256', 'name': '_currentState', 'type': 'uint256'}, {'internalType': 'uint256', 'name': '_counter', 'type': 'uint256'}], 'name': 'nextVal', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'pure', 'type': 'function'}]
self.deployed_contract = None

def deploy_lcg_contract(self):
self.deployed_contract = SmartContracts.deploy_contract(self.contract_bytes, self.contract_abi)

def get_next(self, counter):
print(f'\n[+] Calling nextVal() with _currentState={self.state}')
self.state = self.deployed_contract.functions.nextVal(self.multiplier, self.increment, self.modulus, self.state, counter).call()
print(f' _counter = {counter}: Result = {self.state}')
return self.state

class TripleXOROracle:
def __init__(self):
self.contract_bytes = '61030f61004d600b8282823980515f1a6073146041577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe7300000000000000000000000000000000000000003014608060405260043610610034575f3560e01c80636230075614610038575b5f5ffd5b610052600480360381019061004d919061023c565b610068565b60405161005f91906102c0565b60405180910390f35b5f5f845f1b90505f845f1b90505f61007f85610092565b9050818382181893505050509392505050565b5f5f8290506020815111156100ae5780515f525f5191506100b6565b602081015191505b50919050565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b6100df816100cd565b81146100e9575f5ffd5b50565b5f813590506100fa816100d6565b92915050565b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61014e82610108565b810181811067ffffffffffffffff8211171561016d5761016c610118565b5b80604052505050565b5f61017f6100bc565b905061018b8282610145565b919050565b5f67ffffffffffffffff8211156101aa576101a9610118565b5b6101b382610108565b9050602081019050919050565b828183375f83830152505050565b5f6101e06101db84610190565b610176565b9050828152602081018484840111156101fc576101fb610104565b5b6102078482856101c0565b509392505050565b5f82601f83011261022357610222610100565b5b81356102338482602086016101ce565b91505092915050565b5f5f5f60608486031215610253576102526100c5565b5b5f610260868287016100ec565b9350506020610271868287016100ec565b925050604084013567ffffffffffffffff811115610292576102916100c9565b5b61029e8682870161020f565b9150509250925092565b5f819050919050565b6102ba816102a8565b82525050565b5f6020820190506102d35f8301846102b1565b9291505056fea26469706673582212203fc7e6cc4bf6a86689f458c2d70c565e7c776de95b401008e58ca499ace9ecb864736f6c634300081e0033'
self.contract_abi = [{'inputs': [{'internalType': 'uint256', 'name': '_primeFromLcg', 'type': 'uint256'}, {'internalType': 'uint256', 'name': '_conversationTime', 'type': 'uint256'}, {'internalType': 'string', 'name': '_plaintext', 'type': 'string'}], 'name': 'encrypt', 'outputs': [{'internalType': 'bytes32', 'name': '', 'type': 'bytes32'}], 'stateMutability': 'pure', 'type': 'function'}]
self.deployed_contract = None

def deploy_triple_xor_contract(self):
self.deployed_contract = SmartContracts.deploy_contract(self.contract_bytes, self.contract_abi)

def encrypt(self, prime_from_lcg, conversation_time, plaintext_bytes):
print(f'\n[+] Calling encrypt() with prime_from_lcg={prime_from_lcg}, time={conversation_time}, plaintext={plaintext_bytes}')
ciphertext = self.deployed_contract.functions.encrypt(prime_from_lcg, conversation_time, plaintext_bytes).call()
print(f' _ciphertext = {ciphertext.hex()}')
return ciphertext

class ChatLogic:
def __init__(self):
self.lcg_oracle = None
self.xor_oracle = None
self.rsa_key = None
self.seed_hash = None
self.super_safe_mode = False
self.message_count = 0
self.conversation_start_time = 0
self.chat_history = []
self._initialize_crypto_backend()

def _get_system_artifact_hash(self):
artifact = platform.node().encode('utf-8')
hash_val = hashlib.sha256(artifact).digest()
seed_hash = int.from_bytes(hash_val, 'little')
print(f'[SETUP] - Generated Seed {seed_hash}...')
return seed_hash

def _generate_primes_from_hash(self, seed_hash):
primes = []
current_hash_byte_length = (seed_hash.bit_length() + 7) // 8
current_hash = seed_hash.to_bytes(current_hash_byte_length, 'little')
print('[SETUP] Generating LCG parameters from system artifact...')
iteration_limit = 10000
iterations = 0
while len(primes) < 3 and iterations < iteration_limit:
current_hash = hashlib.sha256(current_hash).digest()
candidate = int.from_bytes(current_hash, 'little')
iterations += 1
if candidate.bit_length() == 256 and isPrime(candidate):
primes.append(candidate)
print(f'[SETUP] - Found parameter {len(primes)}: {str(candidate)[:20]}...')
if len(primes) < 3:
error_msg = '[!] Error: Could not find 3 primes within iteration limit.'
print('Current Primes: ', primes)
print(error_msg)
exit()
return (primes[0], primes[1], primes[2])

def _initialize_crypto_backend(self):
self.seed_hash = self._get_system_artifact_hash()
m, c, n = self._generate_primes_from_hash(self.seed_hash)
self.lcg_oracle = LCGOracle(m, c, n, self.seed_hash)
self.lcg_oracle.deploy_lcg_contract()
print('[SETUP] LCG Oracle is on-chain...')
self.xor_oracle = TripleXOROracle()
self.xor_oracle.deploy_triple_xor_contract()
print('[SETUP] Triple XOR Oracle is on-chain...')
print('[SETUP] Crypto backend initialized...')

def generate_rsa_key_from_lcg(self):
print('[RSA] Generating RSA key from on-chain LCG primes...')
lcg_for_rsa = LCGOracle(self.lcg_oracle.multiplier, self.lcg_oracle.increment, self.lcg_oracle.modulus, self.seed_hash)
lcg_for_rsa.deploy_lcg_contract()
primes_arr = []
rsa_msg_count = 0
iteration_limit = 10000
iterations = 0
while len(primes_arr) < 8 and iterations < iteration_limit:
candidate = lcg_for_rsa.get_next(rsa_msg_count)
rsa_msg_count += 1
iterations += 1
if candidate.bit_length() == 256 and isPrime(candidate):
primes_arr.append(candidate)
print(f'[RSA] - Found 256-bit prime #{len(primes_arr)}')
print('Primes Array: ', primes_arr)
if len(primes_arr) < 8:
error_msg = '[RSA] Error: Could not find 8 primes within iteration limit.'
print('Current Primes: ', primes_arr)
print(error_msg)
return error_msg
n = 1
for p_val in primes_arr:
n *= p_val
phi = 1
for p_val in primes_arr:
phi *= p_val - 1
e = 65537
if math.gcd(e, phi)!= 1:
error_msg = '[RSA] Error: Public exponent e is not coprime with phi(n). Cannot generate key.'
print(error_msg)
return error_msg
self.rsa_key = RSA.construct((n, e))
try:
with open('public.pem', 'wb') as f:
f.write(self.rsa_key.export_key('PEM'))
print('[RSA] Public key generated and saved to \'public.pem\'')
return 'Public key generated and saved successfully.'
except Exception as e:
print(f'[RSA] Error saving key: {e}')
return f'Error saving key: {e}'

def process_message(self, plaintext):
if self.conversation_start_time == 0:
self.conversation_start_time = time.time()
conversation_time = int(time.time() - self.conversation_start_time)
if self.super_safe_mode and self.rsa_key:
plaintext_bytes = plaintext.encode('utf-8')
plaintext_enc = bytes_to_long(plaintext_bytes)
_enc = pow(plaintext_enc, self.rsa_key.e, self.rsa_key.n)
ciphertext = _enc.to_bytes(self.rsa_key.n.bit_length(), 'little').rstrip(b'\x00')
encryption_mode = 'RSA'
plaintext = '[ENCRYPTED]'
else: # inserted
prime_from_lcg = self.lcg_oracle.get_next(self.message_count)
ciphertext = self.xor_oracle.encrypt(prime_from_lcg, conversation_time, plaintext)
encryption_mode = 'LCG-XOR'
log_entry = {'conversation_time': conversation_time, 'mode': encryption_mode, 'plaintext': plaintext, 'ciphertext': ciphertext.hex()}
self.chat_history.append(log_entry)
self.message_count += 1
self.save_chat_log()
return (f'[{conversation_time}s] {plaintext}', f'[{conversation_time}s] {ciphertext.hex()}')

def save_chat_log(self):
try:
with open('chat_log.json', 'w') as f:
json.dump(self.chat_history, f, indent=2)
except Exception as e:
print(f'Error saving chat log: {e}')

class ChatApp(tk.Tk):
def __init__(self):
super().__init__()
self.title('Chain of Demands - Secure Chat')
self.geometry('1000x800')
top_frame = tk.Frame(self, bd=5)
top_frame.pack(fill='x')
chat_frame = tk.Frame(self, bd=5)
chat_frame.pack(expand=True, fill='both')
input_frame = tk.Frame(self, bd=5)
input_frame.pack(fill='x')
tk.Label(top_frame, text='Connect to IP:').pack(side='left')
self.ip_entry = tk.Entry(top_frame, width=20)
self.ip_entry.insert(0, '127.0.0.1')
self.ip_entry.pack(side='left', padx=5)
self.connect_button = tk.Button(top_frame, text='Connect', command=self.connect_to_peer)
self.connect_button.pack(side='left')
self.load_files_button = tk.Button(top_frame, text='Last Convo', command=self.load_last_generated_files)
self.load_files_button.pack(side='left', padx=10)
self.web3_config_button = tk.Button(top_frame, text='Web3 Config', command=self.open_web3_config_window)
self.web3_config_button.pack(side='left')
self.status_label = tk.Label(top_frame, text='Status: Disconnected', fg='red')
self.status_label.pack(side='left', padx=10)
self.chat_box = scrolledtext.ScrolledText(chat_frame, state='disabled', wrap=tk.WORD, bg='#f0f0f0')
self.chat_box.pack(expand=True, fill='both')
self.msg_entry = tk.Entry(input_frame, width=60)
self.msg_entry.pack(side='left', expand=True, fill='x', padx=5)
self.msg_entry.bind('<Return>', self.send_message_event)
self.msg_entry.config(state='disabled')
self.send_button = tk.Button(input_frame, text='Send', command=self.send_message_event)
self.send_button.pack(side='left')
self.send_button.config(state='disabled')
self.super_safe_var = BooleanVar()
self.super_safe_check = Checkbutton(top_frame, text='Enable Super-Safe Encryption', variable=self.super_safe_var, command=self.toggle_super_safe)
self.super_safe_check.pack(side='right', padx=10)
self.logic = ChatLogic()

def open_web3_config_window(self):
config_window = Toplevel(self)
config_window.title('Web3 Configuration')
config_window.geometry('650x150')
config_window.resizable(False, False)
main_frame = tk.Frame(config_window, padx=10, pady=10)
main_frame.pack(expand=True, fill='both')
tk.Label(main_frame, text='RPC URL:').grid(row=0, column=0, sticky='w', pady=5)
rpc_entry = tk.Entry(main_frame, width=60)
rpc_entry.grid(row=0, column=1, sticky='ew')
rpc_entry.insert(0, SmartContracts.rpc_url)
tk.Label(main_frame, text='Private Key:').grid(row=1, column=0, sticky='w', pady=5)
pk_entry = tk.Entry(main_frame, width=60)
pk_entry.grid(row=1, column=1, sticky='ew')
pk_entry.insert(0, SmartContracts.private_key)

def save_and_close():
new_rpc_url = rpc_entry.get().strip()
new_pk = pk_entry.get().strip()
if new_rpc_url and new_pk:
SmartContracts.rpc_url = new_rpc_url
SmartContracts.private_key = new_pk
print(f'[CONFIG] Web3 RPC URL updated to: {new_rpc_url}')
print('[CONFIG] Web3 Private Key updated.')
messagebox.showinfo('Success', 'Web3 configuration has been updated.', parent=config_window)
config_window.destroy()
else: # inserted
messagebox.showerror('Error', 'Both fields are required.', parent=config_window)
save_button = tk.Button(main_frame, text='Save & Close', command=save_and_close)
save_button.grid(row=2, column=1, sticky='e', pady=10)
config_window.transient(self)
config_window.grab_set()
self.wait_window(config_window)
self.logic = ChatLogic()

def connect_to_peer(self):
ip = self.ip_entry.get()
if ip:
self.status_label.config(text=f'Status: Connected to {ip}', fg='green')
self.display_message_in_box('--- Welcome to Secure Chat ---', 'system')
self.display_message_in_box(f'[SYSTEM] Connection to {ip} established.\n', 'system')
self.display_message_in_box('You are now talking with the ransomware operator.', 'system')
self.display_message_in_box('--------------------------------------------------\n', 'system')
self.msg_entry.config(state='normal')
self.send_button.config(state='normal')

def display_message_in_box(self, message, tag):
self.chat_box.config(state='normal')
self.chat_box.insert(tk.END, message + '\n', tag)
self.chat_box.config(state='disabled')
self.chat_box.see(tk.END)
self.chat_box.tag_config('user', foreground='blue')
self.chat_box.tag_config('peer', foreground='green')
self.chat_box.tag_config('system', foreground='red')
self.chat_box.tag_config('error', foreground='orange')

def send_message_event(self, event=None):
msg = self.msg_entry.get()
if msg:
self.display_message_in_box(f'You: {msg}', 'user')
_, encrypted_msg_display = self.logic.process_message(msg)
self.display_message_in_box(f'Peer (Encrypted): {encrypted_msg_display}', 'peer')
self.msg_entry.delete(0, tk.END)

def toggle_super_safe(self):
if self.super_safe_var.get():
self.logic.super_safe_mode = True
self.display_message_in_box('[SYSTEM] Super-Safe mode enabled. Generating RSA key...', 'system')
Thread(target=self.generate_rsa_and_update_gui, daemon=True).start()
else: # inserted
self.logic.super_safe_mode = False
self.display_message_in_box('[SYSTEM] Super-Safe mode disabled. Reverting to standard LCG-XOR.', 'system')

def generate_rsa_and_update_gui(self):
result_msg = self.logic.generate_rsa_key_from_lcg()
self.display_message_in_box(f'[SYSTEM] {result_msg}', 'system')

def load_last_generated_files(self):
files_window = Toplevel(self)
files_window.title('Generated Files')
files_window.geometry('700x500')
tk.Label(files_window, text='chat_log.json', font=('Helvetica', 12, 'bold')).pack(pady=(10, 0))
json_text_area = scrolledtext.ScrolledText(files_window, wrap=tk.WORD, height=15)
json_text_area.pack(expand=True, fill='both', padx=10, pady=5)
try:
json_path = resource_path('chat_log.json')
with open(json_path, 'r') as f:
json_data = json.load(f)
pretty_json = json.dumps(json_data, indent=2)
json_text_area.insert(tk.END, pretty_json)
except FileNotFoundError:
json_text_area.insert(tk.END, 'chat_log.json not found.\n\nSend a message to generate it.')
except Exception as e:
json_text_area.insert(tk.END, f'Error reading chat_log.json:\n{e}')
json_text_area.config(state='disabled')
tk.Label(files_window, text='public.pem', font=('Helvetica', 12, 'bold')).pack(pady=(10, 0))
pem_text_area = scrolledtext.ScrolledText(files_window, wrap=tk.WORD, height=8)
pem_text_area.pack(expand=True, fill='both', padx=10, pady=(5, 10))
try:
pem_path = resource_path('public.pem')
with open(pem_path, 'r') as f:
pem_data = f.read()
pem_text_area.insert(tk.END, pem_data)
except FileNotFoundError:
pem_text_area.insert(tk.END, 'public.pem not found.\n\nEnable \'Super-Safe Encryption\' to generate it.')
except Exception as e:
pem_text_area.insert(tk.END, f'Error reading public.pem:\n{e}')
pem_text_area.config(state='disabled')

if __name__ == '__main__':
app = ChatApp()
app.mainloop()

是一个区块链题目,实际上给出了对话日志和RSA公钥:

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
[
{
"conversation_time": 0,
"mode": "LCG-XOR",
"plaintext": "Hello",
"ciphertext": "e934b27119f12318fe16e8cd1c1678fd3b0a752eca163a7261a7e2510184bbe9"
},
{
"conversation_time": 4,
"mode": "LCG-XOR",
"plaintext": "How are you?",
"ciphertext": "25bf2fd1198392f4935dcace7d747c1e0715865b21358418e67f94163513eae4"
},
{
"conversation_time": 11,
"mode": "LCG-XOR",
"plaintext": "Terrible...",
"ciphertext": "c9f20e5561acf172305cf8f04c13e643c988aa5ab29b5499c93df112687c8c7c"
},
{
"conversation_time": 13,
"mode": "LCG-XOR",
"plaintext": "Is this a secure channel?",
"ciphertext": "3ab9c9f38e4f767a13b12569cdbf13db6bbb939e4c8a57287fb0c9def0288e46"
},
{
"conversation_time": 16,
"mode": "LCG-XOR",
"plaintext": "Yes, it's on the blockchain.",
"ciphertext": "3f6de0c2063d3e8e875737046fef079d73cc9b1b7a4b7b94da2d2867493f6fc5"
},
{
"conversation_time": 24,
"mode": "LCG-XOR",
"plaintext": "Erm enable super safe mode",
"ciphertext": "787cf6c0be39caa21b7908fcd1beca68031b7d11130005ba361c5d361b106b6d"
},
{
"conversation_time": 30,
"mode": "LCG-XOR",
"plaintext": "Ok, activating now",
"ciphertext": "632ab61849140655e0ee6f90ab00b879a3a3da241d4b50bab99f74f169d456db"
},
{
"conversation_time": 242,
"mode": "RSA",
"plaintext": "[ENCRYPTED]",
"ciphertext": "680a65364a498aa87cf17c934ab308b2aee0014aee5b0b7d289b5108677c7ad1eb3bcfbcad7582f87cb3f242391bea7e70e8c01f3ad53ac69488713daea76bb3a524bd2a4bbbc2cfb487477e9d91783f103bd6729b15a4ae99cb93f0db22a467ce12f8d56acaef5d1652c54f495db7bc88aa423bc1c2b60a6ecaede2f4273f6dce265f6c664ec583d7bd75d2fb849d77fa11d05de891b5a706eb103b7dbdb4e5a4a2e72445b61b83fd931cae34e5eaab931037db72ba14e41a70de94472e949ca3cf2135c2ccef0e9b6fa7dd3aaf29a946d165f6ca452466168c32c43c91f159928efb3624e56430b14a0728c52f2668ab26f837120d7af36baf48192ceb3002"
},
{
"conversation_time": 249,
"mode": "RSA",
"plaintext": "[ENCRYPTED]",
"ciphertext": "6f70034472ce115fc82a08560bd22f0e7f373e6ef27bca6e4c8f67fedf4031be23bf50311b4720fe74836b352b34c42db46341cac60298f2fa768f775a9c3da0c6705e0ce11d19b3cbdcf51309c22744e96a19576a8de0e1195f2dab21a3f1b0ef5086afcffa2e086e7738e5032cb5503df39e4bf4bdf620af7aa0f752dac942be50e7fec9a82b63f5c8faf07306e2a2e605bb93df09951c8ad46e5a2572e333484cae16be41929523c83c0d4ca317ef72ea9cde1d5630ebf6c244803d2dc1da0a1eefaafa82339bf0e6cf4bf41b1a2a90f7b2e25313a021eafa6234643acb9d5c9c22674d7bc793f1822743b48227a814a7a6604694296f33c2c59e743f4106"
}
]

LCG使用主机名称生成,但无法猜测主机名称,查看公钥:

1
2
3
4
5
6
7
8
9
-----BEGIN PUBLIC KEY-----
MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQAHqNys8GuPfBCUueYLClzq
oNMGDpGWzr04InFmeDR6e/MDLsCVWU3xW/JnszG5L29yofjDqHPPYN08GX0qiZOg
3tX38YkWR4CLAQ7P7JKzpo5/rNRE8XMtyDJmwiYbamM+QQsaiXCPaqiTpljTiIZb
QnjS1AoKTzyAcuxrT2EJRHTKIOGPnG/k+r4ozVt4R++/X2sabmmjNAnbdRU0jEUJ
LcQlupnOXraJyUUEBC+paDETEHcbWVtOCaWnaUcSq0ICr9ZrGwfkb7YNI8ZsyJh1
AHNelaBhgQtXEYeF8pBIa718vXXNAB3hzpcLBUpRpUWHImWeWTV1nd9DoP6XQrGX
AgMBAAE=
-----END PUBLIC KEY-----

直接解密后转hex:

1
2
3
4
30820121300d06092a864886f70d01010105000382010e003082010902820100
07a8dcacf06b8f7c1094b9e60b0a5ceaa0d3060e9196cebd3822716678347a7bf3032ec095594df15bf267b331b92f6f72a1f8c3a873cf60dd3c197d2a8993a0ded5f7f1891647808b010ecfec92b3a68e7facd444f1732dc83266c2261b6a633e410b1a89708f6aa893a658d388865b4278d2d40a0a4f3c8072ec6b4f61094474ca20e18f9c6fe4fabe28cd5b7847efbf5f6b1a6e69a33409db7515348c45092dc425ba99ce5eb689c94504042fa968311310771b595b4e09a5a7694712ab4202afd66b1b07e46fb60d23c66cc8987500735e95a061810b57118785f290486bbd7cbd75cd001de1ce970b054a51a5458722659e5935759ddf43a0fe9742b197
0203
010001

得到

1
n=966937097264573110291784941768218419842912477944108020986104301819288091060794069566383434848927824136504758249488793818136949609024508201274193993592647664605167873625565993538947116786672017490835007254958179800254950175363547964901595712823487867396044588955498965634987478506533221719372965647518750091013794771623552680465087840964283333991984752785689973571490428494964532158115459786807928334870321963119069917206505787030170514779392407953156221948773236670005656855810322260623193397479565769347040107022055166737425082196480805591909580137453890567586730244300524109754079060045173072482324926779581706647

n在factordb上已有分解记录:

1
2
3
4
5
6
7
8
9
n = 
62826068095404038148338678434404643116583820572865189787368764098892510936793 *
68446593057460676025047989394445774862028837156496043637575024036696645401289 *
69802783227378026511719332106789335301376047817734407431543841272855455052067 *
72967016216206426977511399018380411256993151454761051136963936354667101207529 *
75395288067150543091997907493708187002382230701390674177789205231462589994993 *
79611551309049018061300429096903741339200167241148430095608259960783012192237 *
82836473202091099900869551647600727408082364801577205107017971703263472445197 *
88790251731800173019114073860734130032527125661685690883849562991870715928701

直接解密即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import *
n = 0x07a8dcacf06b8f7c1094b9e60b0a5ceaa0d3060e9196cebd3822716678347a7bf3032ec095594df15bf267b331b92f6f72a1f8c3a873cf60dd3c197d2a8993a0ded5f7f1891647808b010ecfec92b3a68e7facd444f1732dc83266c2261b6a633e410b1a89708f6aa893a658d388865b4278d2d40a0a4f3c8072ec6b4f61094474ca20e18f9c6fe4fabe28cd5b7847efbf5f6b1a6e69a33409db7515348c45092dc425ba99ce5eb689c94504042fa968311310771b595b4e09a5a7694712ab4202afd66b1b07e46fb60d23c66cc8987500735e95a061810b57118785f290486bbd7cbd75cd001de1ce970b054a51a5458722659e5935759ddf43a0fe9742b197
c1 = bytes_to_long(bytes.fromhex("680a65364a498aa87cf17c934ab308b2aee0014aee5b0b7d289b5108677c7ad1eb3bcfbcad7582f87cb3f242391bea7e70e8c01f3ad53ac69488713daea76bb3a524bd2a4bbbc2cfb487477e9d91783f103bd6729b15a4ae99cb93f0db22a467ce12f8d56acaef5d1652c54f495db7bc88aa423bc1c2b60a6ecaede2f4273f6dce265f6c664ec583d7bd75d2fb849d77fa11d05de891b5a706eb103b7dbdb4e5a4a2e72445b61b83fd931cae34e5eaab931037db72ba14e41a70de94472e949ca3cf2135c2ccef0e9b6fa7dd3aaf29a946d165f6ca452466168c32c43c91f159928efb3624e56430b14a0728c52f2668ab26f837120d7af36baf48192ceb3002")[::-1])
c2 = bytes_to_long(bytes.fromhex("6f70034472ce115fc82a08560bd22f0e7f373e6ef27bca6e4c8f67fedf4031be23bf50311b4720fe74836b352b34c42db46341cac60298f2fa768f775a9c3da0c6705e0ce11d19b3cbdcf51309c22744e96a19576a8de0e1195f2dab21a3f1b0ef5086afcffa2e086e7738e5032cb5503df39e4bf4bdf620af7aa0f752dac942be50e7fec9a82b63f5c8faf07306e2a2e605bb93df09951c8ad46e5a2572e333484cae16be41929523c83c0d4ca317ef72ea9cde1d5630ebf6c244803d2dc1da0a1eefaafa82339bf0e6cf4bf41b1a2a90f7b2e25313a021eafa6234643acb9d5c9c22674d7bc793f1822743b48227a814a7a6604694296f33c2c59e743f4106")[::-1])
p = [62826068095404038148338678434404643116583820572865189787368764098892510936793,
68446593057460676025047989394445774862028837156496043637575024036696645401289,
69802783227378026511719332106789335301376047817734407431543841272855455052067,
72967016216206426977511399018380411256993151454761051136963936354667101207529,
75395288067150543091997907493708187002382230701390674177789205231462589994993,
79611551309049018061300429096903741339200167241148430095608259960783012192237,
82836473202091099900869551647600727408082364801577205107017971703263472445197,
88790251731800173019114073860734130032527125661685690883849562991870715928701]
e = 0x10001
phin = 1
for i in range(len(p)): phin *= (p[i]-1)
d = inverse(e, phin)
print(long_to_bytes(pow(c1, d, n)), long_to_bytes(pow(c2, d, n)))

W3b3_i5_Gr8@flare-on.com

#7 The Boss Needs Help

初始分析

程序主要函数均被加上混淆,分析流量包,发现格式主要有以下几种:

192.168.56.103:第一次是Bearer在twelve.flare-on.com:8000上发送GET请求,之后被一个奇怪的Host theannualtraditionofstaringatdisassemblyforweeks.torealizetheflagwasjustxoredwiththefilenamethewholetime.com:8080劫持,如果有额外消息会被加密后用json格式传递

1
2
3
4
5
6
7
GET /good HTTP/1.1
User-Agent: Mozilla/5.0 (Avocado OS; 1-Core Toaster) AppleWebKit/537.36 (XML, like Gecko) FLARE/1.0
Authorization: Bearer e4b8058f06f7061e8f0f8ed15d23865ba2427b23a695d9b27bc308a26d
Accept-Encoding:
Connection: close
Accept: */*
Host: twelve.flare-on.com:8000
1
2
3
4
5
6
GET /get HTTP/1.1
Accept-Encoding:
Connection: close
Accept: */*
Host: theannualtraditionofstaringatdisassemblyforweeks.torealizetheflagwasjustxoredwiththefilenamethewholetime.com:8080
User-Agent: rustc-hyper/0.25.0
1
2
3
4
5
6
7
8
9
10
POST / HTTP/1.1
Content-Type: application/json
Accept-Encoding:
Connection: close
Content-Length: 341
Accept: */*
Host: theannualtraditionofstaringatdisassemblyforweeks.torealizetheflagwasjustxoredwiththefilenamethewholetime.com:8080
User-Agent: rustc-hyper/0.25.0

{"d":"密文","msg":"sysi"}

192.168.56.117:接受get和post请求,也按密文发送

1
2
3
4
5
6
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.10.11
Date: Wed, 20 Aug 2025 06:12:07 GMT
Content-type: application/json

{"d": "密文"}

流量包中有一个518KB的hex消息,猜测是某种文件,此外劫持的host名称意为“每年的惯例都是往往花了几周逆向结果发现flag只是和文件名xor了一下而已”,可能是某种提示

程序中数据段有SHA-256的常量表和AES的S盒/逆S盒,其中AES的S盒、逆S盒在两个AES-256-CBC加密/解密函数 0x7FF6B5C70D20 0x7FF6B5C70DC0 中使用(基地址0x7FF6B5C20000),且这个函数被两个混淆函数 0x7FF6B5CF3E60 0x7FF6B5D57960 所调用,猜测为关键逻辑

反混淆

方法一:提高反编译阈值,强制反编译

此方法可以反编译main函数,对于所有能够反编译出来的函数,使用arg追踪法:

如果一个函数有参数,那么统计每个参数被xref到的行,从最小的行开始,只统计过程中遇到的有关变量,无关变量不做统计。

注意到地址0x7FF6B608A540的位置的AES逆S盒有一个被混淆的xref:

1
2
3
4
5
6
7
8
9
10
11
12
void __fastcall sub_7FF6B5C78A00(__int64 a1, __int64 a2, unsigned __int64 a3)
{
if ( a3 )
{
for ( i = 0; i < a3; ++i )
{
v10 = a1[i] ^ 0x5A;
v15 = i + 1 + v10;
a2[i] = AES_INV_SBOX_[v15];
}
}
}

方法二:尝试解混淆

此处可以看出来是一个魔改AES,在S盒替换的步骤有着额外的异或操作,观察汇编发现,似乎只有rax、rcx、rdx大量参与了控制流混淆过程,r8、r9、r10似乎也参与了,nop前后函数体不变,编写脚本nop所有相关汇编指令(不包含call):

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
import idc

START_EA = 0x7FF6B5C78A42
END_EA = 0x7FF6B5C7E952

TARGET_REGS = {
'r11', 'r11d', 'r11w', 'r11b',
'r12', 'r12d', 'r12w', 'r12b',
'r13', 'r13d', 'r13w', 'r13b',
'r14', 'r14d', 'r14w', 'r14b',
'r15', 'r15d', 'r15w', 'r15b',
'rbx', 'ebx', 'bx', 'bl',
'rsi', 'esi', 'si', 'sil',
'rdi', 'edi', 'dil',
}

def should_preserve(ea):
disasm = idc.GetDisasm(ea)
if disasm is None or disasm == "???": return False
disasm_lower = disasm.lower().strip()
if disasm_lower.startswith('call'): return True
for reg in TARGET_REGS:
if reg in disasm_lower: return True
return False

def nop_range(start_ea, end_ea):
ea = start_ea
while ea < end_ea:
insn_len = idc.get_item_size(ea)
if not should_preserve(ea):
nop_bytes = b"\x90" * insn_len
idaapi.patch_bytes(ea, nop_bytes)
print(f"NOPed instruction at {ea:#x} (len={insn_len})")
ea += insn_len

def main():
print(f"Processing range: [{START_EA:#x}, {END_EA:#x})")
nop_range(START_EA, END_EA)
print("Done.")

if __name__ == "__main__":
main()

该脚本只能对一条链(一个case)使用,否则会破坏跳转,分析更大的函数(如main)发现关键逻辑均不会受到混淆部分代码影响,因此可以直接nop整个范围,对main使用该脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import idc

START_EA = [0x7FF6B5E2BA33, 0x7FF6B5E30EF2, 0x7FF6B5E33B92, 0x7FF6B5E39279, 0x7FF6B5E3BF02, 0x7FF6B5E3E7C8, 0x7FF6B5E41180]
END_EA = [0x7FF6B5E30EB6, 0x7FF6B5E33AEE, 0x7FF6B5E3920D, 0x7FF6B5E3BE7C, 0x7FF6B5E3E783, 0x7FF6B5E41158, 0x7FF6B5E46476]

def nop_range(start_ea, end_ea):
ea = start_ea
while ea < end_ea:
insn_len = idc.get_item_size(ea)
nop_bytes = b"\x90" * insn_len
idaapi.patch_bytes(ea, nop_bytes)
print(f"NOPed instruction at {ea:#x} (len={insn_len})")
ea += insn_len

def main():
for i in range(len(START_EA)):
print(f"Processing range: [{START_EA[i]:#x}, {END_EA[i]:#x})")
nop_range(START_EA[i], END_EA[i])
print("Done.")

if __name__ == "__main__":
main()

之后反编译发现main中还有一个try…catch0x7FF6B60829A4,把这块也处理一下(0x7FF6B60829B1 ~ 0x7FF6B6084EC4,成功解混淆main,且依然可以调试:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
memset_w(v37, 320, envp);
sub_7FF6B5C4E020(v37, p_); //获取用户名
if ( !(unsigned __int8)BigFunc1((__int64)v37) ) //某个校验
{
v29[0] = 0;
v28 = 0;
v3 = sub_7FF6B5E46560(v29);
v4 = sub_7FF6B5E4E3D0(v3);
v5 = sub_7FF6B5EEF1B0(v31, v4);
v6 = sub_7FF6B5E464D0(&v28);
v7 = sub_7FF6B5E50290(v6);
v8 = sub_7FF6B5EEF1B0(v30, v7);
sub_7FF6B5C74C50(v8, v5);
}
sub_7FF6B5C56CF0(v37, v36);
v27 = 0;
v9 = sub_7FF6B5E46640(&v27);
v10 = sub_7FF6B5E4C510(v9);
v11 = sub_7FF6B5EEF1B0(v32, v10);
v12 = sub_7FF6B604F6E0(v36, v11);
`std::locale::global'::`1'::dtor$2(v32);
if ( v12 )
{
v26 = 0;
v25 = 0;
v13 = sub_7FF6B5E46700(&v26);
v14 = sub_7FF6B5E48790(v13);
v15 = sub_7FF6B5EEF1B0(v34, v14);
v16 = sub_7FF6B5E466A0(&v25);
v17 = sub_7FF6B5E4A650(v16);
v18 = sub_7FF6B5EEF1B0(v33, v17);
sub_7FF6B5C74C50(v18, v15);
}
memset_with_0(v35, 8);
try
{
sub_7FF6B5C2CF30(v35, v36);
if ( (unsigned __int8)BigFunc2(v35, v37) ) //这两个函数是调用AES加解密函数的大函数
Call_BigFunc3(v35, v37);
sub_7FF6B5C2D630(v35);
}
catch ( const std::exception *&v29[3] )
{
v20 = (*(__int64 (__fastcall **)(_QWORD))(**(_QWORD **)&v29[3] + 8LL))(*(_QWORD *)&v29[3]);
sub_7FF6B5EEF1B0(&v29[11], v20);
v24 = 0;
v21 = sub_7FF6B5E46790(&v24);
v22 = sub_7FF6B5E468D0(v21);
v23 = sub_7FF6B5EEF1B0(&v34[32], v22);
sub_7FF6B5C74C50(v23, &v29[11]);
}
`std::locale::global'::`1'::dtor$2(v36);
sub_7FF6B5E467F0(v37);
return 0;
}

那么,现在可以用相同的方法解混淆其他函数,比如BigFunc1(0x7FF6B5CA1590):

1
2
START_EA = [0x7FF6B5CA15BF, 0x7FF6B5CA7974, 0x7FF6B5CAE1BF, 0x7FF6B5CB1603, 0x7FF6B5CB481B, 0x7FF6B5CB8037, 0x7FF6B5CBB8CE, 0x7FF6B5CBEC51, 0x7FF6B5CC219F, 0x7FF6B5CC54BE, 0x7FF6B5CC8F6F, 0x7FF6B5CCC244, 0x7FF6B5CCF593, 0x7FF6B5CD2CAF, 0x7FF6B5CD61A4, 0x7FF6B5CD9537, 0x7FF6B5CDCF8B, 0x7FF6B5CE0443, 0x7FF6B5CE38CD, 0x7FF6B5CE6D25, 0x7FF6B5CE9FDA, 0x7FF6B5CED27B, 0x7FF6B5CF0634]
END_EA = [0x7FF6B5CA7965, 0x7FF6B5CAE1A9, 0x7FF6B5CB15BB, 0x7FF6B5CB4805, 0x7FF6B5CB8021, 0x7FF6B5CBB8B0, 0x7FF6B5CBEC3C, 0x7FF6B5CC2167, 0x7FF6B5CC5498, 0x7FF6B5CC8C66, 0x7FF6B5CCC1BE, 0x7FF6B5CCF446, 0x7FF6B5CD2B7A, 0x7FF6B5CD60C2, 0x7FF6B5CD9512, 0x7FF6B5CDCE84, 0x7FF6B5CE039E, 0x7FF6B5CE382B, 0x7FF6B5CE6CC9, 0x7FF6B5CE9F74, 0x7FF6B5CED172, 0x7FF6B5CF0574, 0x7FF6B5CF37CC]

反编译出来的BigFunc1并调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall BigFunc1(__int64 a1)
{
Time = time64(0);
gmtime64_s(&Tm, &Time);
memset(&buf, 0, sizeof(buf));
v1 = sub_7FF7BED73850(&buf);
Format = (const char *)sub_7FF7BEF39E30(v1);
strftime(Buffer, 0xBu, Format, &Tm); //获取日期和UTC+0小时(10字节)
sub_7FF7BEF6F1B0(v140, Buffer);
sub_7FF7BECD7060(a1, v139); //获取用户名@主机名,截断到22字节
sub_7FF7BF0CF520(v138); //组合到时间后面,成为32字节
len = add_with_16(v138); //获取长度(0x20)
memset_with_0(v135, 24);
v3 = sub_7FF7BEF54AF0(v36);
sub_7FF7BEF54A90(v135, len, v3);
v4 = sub_7FF7BECA1F60(v135);
AES_INV_SBOX_REPLACE(v138, v4, len); //对上面的组合明文进行了替换,结果写入v4

到这里已经可以对消息的第一条中出现的Bearer e4b8058f06f7061e8f0f8ed15d23865ba2427b23a695d9b27bc308a26d进行解密了,解密结果为2025082006TheBoss@THUNDERNODE,这个数据不足32字节,暂时不知道有什么用,继续调试:

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
memset_with_0(v143, 64);
memset(buf__1, 0, 1u);
v5 = sub_7FF7BED738C0(buf__1);
v6 = sub_7FF7BEF37F70(v5);
v42 = sub_7FF7BEF6F1B0(v118, v6);
v43 = v42;
v47 = v42;
memset(&buf__2, 0, sizeof(buf__2));
v7 = sub_7FF7BED73930(&buf__2);
v8 = sub_7FF7BEF360B0(v7);
v44 = sub_7FF7BEF6F1B0(v112, v8);
v45 = v44;
v46 = v44;
sub_7FF7BF0D1970(v124, v47, v44);
memset(&buf__3, 0, sizeof(buf__3));
v9 = sub_7FF7BED73A20(&buf__3);
v10 = sub_7FF7BEF341F0(v9);
v48 = sub_7FF7BEF6F1B0(v113, v10);
v49 = v48;
v57 = v48;
memset(&buf__4, 0, sizeof(buf__4));
v11 = sub_7FF7BECA1F60(v135);
v50 = sub_7FF7BECFE990(v114, v11, jumpbuf_sp);
v52 = v50;
v12 = sub_7FF7BED73A90(&buf__4);
v13 = sub_7FF7BEF32330(v12);
v51 = sub_7FF7BEF6F1B0(v116, v13);
v53 = v51;
v54 = sub_7FF7BF0CF5F0(v117, v51, v52);
v41 = v54;
v56 = v54;
sub_7FF7BF0D1970(v125, v57, v54);
qmemcpy(dst, (const void *)unknown_libname_29(v111, v124, v126), 0x10u);
sub_7FF7BEF6E7B0(v143, dst);
`eh vector destructor iterator'(v124, 0x40u, 2u, sub_7FF7BECA4B50);
`std::locale::global'::`1'::dtor$2(v117);
`std::locale::global'::`1'::dtor$2(v116);
`std::locale::global'::`1'::dtor$2(v114);
`std::locale::global'::`1'::dtor$2(v113);
`std::locale::global'::`1'::dtor$2(v112);
`std::locale::global'::`1'::dtor$2(v118);
memset_with_0(v131, 8);
v58 = sub_7FF7BECD6CB0(a1, v110);

这部分构造了HTTP请求:

1
2
3
4
5
User-Agent
Mozilla/5.0 (Avocado OS; 1-Core Toaster) AppleWebKit/537.36 (XML, like Gecko) FLARE/1.0
Authorization
Bearer e4b8058f06e406052c3f7b0b7b3244c20b95ffce95a65466964c2e9508d9a285
twelve.flare-on.com

就是上面的第一次发送的请求,继续分析:

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
v59 = v58;
v37 = sub_7FF7BECD6CE0(a1);
v103 = sub_7FF7BECAD5D0(v131, v59, v37);
`std::locale::global'::`1'::dtor$2(v110);
memset_with_0(v142, 80);
v60 = v128;
memset(&buf__5, 0, sizeof(buf__5));
v61 = sub_7FF7BEF53BE0(v128, 0);
v65 = v61;
v62 = sub_7FF7BED73B00(&buf__5);
v63 = sub_7FF7BEF30470(v62);
v64 = sub_7FF7BEF6F1B0(v109, v63);
v66 = v64;
v104 = sub_7FF7BECAD790(v131, v142, v64, v143, v65);
`std::locale::global'::`1'::dtor$2(v109);
HasCapturedContext = Concurrency::details::_ContextCallback::_HasCapturedContext((Concurrency::details::_ContextCallback *)v142);
if ( !HasCapturedContext || (v67 = sub_7FF7BECA1F60(v142), *(_DWORD *)(v67 + 32) != 200) )
{
memset(&buf__6, 0, 1u);
v98 = sub_7FF7BED73DA0(&buf__6);
v99 = sub_7FF7BEF22D30(v98);
v100 = sub_7FF7BEF6F1B0(v123, v99);
v101 = v100;
v38 = sub_7FF7BECA1F70(v142);
exit_1(Code[0]);
}

这部分在抓包,没抓到包或者响应不是200就从这里退出了

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  v68 = v129;
v69 = sub_7FF7BEF53BE0(v129, 0);
v70 = v69;
v55 = sub_7FF7BECA1F60(v142);
LOBYTE(v14) = 1;
v105 = sub_7FF7BF0D19D0((unsigned int)v133, (int)v55 + 200, v70, v14, 0);
memset(&buf__7, 0, sizeof(buf__7));
v71 = sub_7FF7BED73B60(&buf__7);
v72 = sub_7FF7BEF2E5B0(v71);
v73 = sub_7FF7BEF6F1B0(v119, v72);
v74 = v73;
v24 = sub_7FF7BEF53ED0(v133, v73);
v25 = v24;
`std::locale::global'::`1'::dtor$2(v119);
if ( v25 )
{
memset_with_0(v136, 24);
v77 = v126;
memset(&buf__8, 0, sizeof(buf__8));
v75 = sub_7FF7BED73BC0(&buf__8);
v76 = sub_7FF7BEF2C6F0(v75);
v78 = sub_7FF7BEF6F1B0(v77, v76);
v79 = sub_7FF7BEF54000(v133, v78);
v106 = sub_7FF7BF0D1B20(v79, v108);
v102 = sub_7FF7BED06A60(v136, v108);
`std::locale::global'::`1'::dtor$2(v108);
dst[2] = sub_7FF7BED06BA0(v141, v136, v139);
v80 = v130;
v81 = sub_7FF7BEF53BE0(v130, 0);
LOBYTE(v15) = 1;
dst[3] = sub_7FF7BF0D19D0((unsigned int)v132, (unsigned int)v141, v81, v15, 0);
memset(&buf__9, 0, sizeof(buf__9));
v82 = sub_7FF7BED73C20(&buf__9);
v83 = sub_7FF7BEF2A830(v82);
v84 = sub_7FF7BEF6F1B0(v115, v83);
v85 = v84;
v28 = sub_7FF7BEF53ED0(v132, v84);
v29 = v28;
`std::locale::global'::`1'::dtor$2(v115);
if ( v29 )
{
v88 = &v127;
memset(&buf__10, 0, sizeof(buf__10));
v86 = sub_7FF7BED73C80(&buf__10);
v87 = sub_7FF7BEF28970(v86);
v89 = sub_7FF7BEF6F1B0(v88, v87);
v90 = sub_7FF7BEF54000(v132, v89);
dst[4] = sub_7FF7BF0D1B20(v90, v137);
memset(&buf__11, 0, sizeof(buf__11));
v91 = sub_7FF7BED73CE0(&buf__11);
v92 = sub_7FF7BEF26AB0(v91);
v93 = sub_7FF7BEF6F1B0(v120, v92);
v16 = (unsigned __int8 *)sub_7FF7BEF6EE40(v93, 0);
v40 = sub_7FF7BEF6EC30(v137, *v16, 0);
`std::locale::global'::`1'::dtor$2(v120);
if ( v40 != -1 )
{
v94 = sub_7FF7BEF6EBD0(v137, v121, 0, v40);
v95 = v94;
sub_7FF7BECD2650(a1, v94);
`std::locale::global'::`1'::dtor$2(v121);
v96 = sub_7FF7BEF6EBD0(v137, v122, v40 + 1, -1);
v97 = v96;
sub_7FF7BECD49D0(a1, v96);
`std::locale::global'::`1'::dtor$2(v122);
v32 = 1;
`std::locale::global'::`1'::dtor$2(v137);
sub_7FF7BEDCC680(v132);
`std::locale::global'::`1'::dtor$2(v141);
sub_7FF7BEF54A30(v136);
sub_7FF7BEDCC680(v133);
sub_7FF7BECA7B70(v142);
sub_7FF7BECAD630(v131);
sub_7FF7BECA1E30(v143);
sub_7FF7BEF54A30(v135);
`std::locale::global'::`1'::dtor$2(v138);
`std::locale::global'::`1'::dtor$2(v139);
`std::locale::global'::`1'::dtor$2(v140);
return v32;
}
`std::locale::global'::`1'::dtor$2(v137);
}
sub_7FF7BEDCC680(v132);
`std::locale::global'::`1'::dtor$2(v141);
sub_7FF7BEF54A30(v136);
}
sub_7FF7BEDCC680(v133);
sub_7FF7BECA7B70(v142);
sub_7FF7BECAD630(v131);
BYTE2(buf__6) = 0;
sub_7FF7BECA1E30(v143);
sub_7FF7BEF54A30(v135);
`std::locale::global'::`1'::dtor$2(v138);
`std::locale::global'::`1'::dtor$2(v139);
`std::locale::global'::`1'::dtor$2(v140);
return BYTE2(buf__6);
}

后面这部分处理了包的内容,提取其中有效的信息:

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
58
59
60
61
62
63
      memset(&buf__11, 0, sizeof(buf__11)); //初始化区域
v89 = sub_7FF6B5CF3CE0(&buf__11);
Source_10 = (char *)sub_7FF6B5EA6AB0(v89); //执行sub_7FF6B5FBA820(v89);
v91 = strcpy(Destination__7, Source_10);
v14 = (unsigned __int8 *)sub_7FF6B5EEEE40(v91, 0); //提取v91的第一个字节
Size = sub_7FF6B5EEEC30(v135, *v14, 0); //在字符串中查找分隔符并分隔两个部分
`std::locale::global'::`1'::dtor$2(Destination__7);
if ( Size != -1 )
{
v92 = sub_7FF6B5EEEBD0(v135, v119, 0, Size); //提取第一部分
v93 = v92;
sub_7FF6B5C52650(p_machine_data_struct, v92); //写入偏移量192
`std::locale::global'::`1'::dtor$2(v119);
v94 = sub_7FF6B5EEEBD0(v135, v120, Size + 1, 0xFFFFFFFFFFFFFFFFuLL); //提取第二部分
v95 = v94;
sub_7FF6B5C549D0(p_machine_data_struct, v94); //写入偏移量224
...
}

void __fastcall sub_7FF6B5FBA820(unsigned __int16 *a1) //如果a1的长度大于2,解密a1,截断a1到2字节
{
if (a1[2])
{
if (n5 < 5)
{
do
{
a1[n2] ^= 0xCBA5210905978DBFuLL >> (8 * (n2 & 7u));
++n2;
}
while ( n2 < 2 );
}
else
{
//...上文的SIMD指令集表达...
}
a1[2]= 0;
}
}

void **__fastcall sub_7FF6B5C52650(__int64 p_machine_data_struct, _QWORD *a2)
{
v2 = a2;
if ( (_QWORD *)(p_machine_data_struct + 192) != a2 )
{
if ( a2[3] > 0xFu )
a2 = (_QWORD *)*a2;
return sub_7FF6B5EEEEC0((void **)(p_machine_data_struct + 192), a2, v2[2]);
}
return result;
}

void **__fastcall sub_7FF6B5C549D0(__int64 p_machine_data_struct, _QWORD *a2)
{
v2 = a2;
if ( (_QWORD *)(p_machine_data_struct + 224) != a2 )
{
if ( a2[3] > 0xFu )
a2 = (_QWORD *)*a2;
return sub_7FF6B5EEEEC0((void **)(p_machine_data_struct + 224), a2, v2[2]);
}
return result;
}

服务器传回的信息中的密文会被切片后填入结构体的偏移量192和224的位置

继续反混淆func2和3(func3还有一个try…catch0x7FF6B607BA13),由于大函数均采用这种模式,所以可以进一步编写自动化脚本:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import idaapi
import idc
import re

START_EA = 0x7FF6B5CF3E60 # 0x7FF6B5D57960 & 0x7FF6B607BA13 for func3
END_EA = 0x7FF6B5D4C036 # 0x7FF6B5E1E490 & 0x7FF6B6081AE4 for func3

MAX_SEARCH_DEPTH = 0x100000

def is_mov_cs_n_symbol(insn):
if insn.get_canon_mnem() != "mov": return False, None
op0 = insn.ops[0]
op1 = insn.ops[1]
if op0.type != idaapi.o_reg: return False, None
disasm = idc.GetDisasm(insn.ea)
if not disasm: return False, None
match = re.search(r'cs:\s*n[a-zA-Z0-9_]', disasm)
if match: return True, op0.reg
return False, None

def find_xor_test_jnz_pattern(start_ea, target_reg, max_end_ea):
ea = start_ea
steps = 0
while ea < max_end_ea and steps < MAX_SEARCH_DEPTH:
insn = idaapi.insn_t()
if not idaapi.decode_insn(insn, ea):
ea += 1
continue
if insn.get_canon_mnem() == "xor":
op0, op1 = insn.ops[0], insn.ops[1]
if (op0.type == idaapi.o_reg and op1.type == idaapi.o_reg and
op0.reg == target_reg and op1.reg == target_reg):
ea += insn.size
steps += 1
break
ea += insn.size
steps += 1
else: return None
insn = idaapi.insn_t()
if not idaapi.decode_insn(insn, ea): return None
if insn.get_canon_mnem() != "test": return None
op0, op1 = insn.ops[0], insn.ops[1]
if not (op0.type == idaapi.o_reg and op1.type == idaapi.o_reg and op0.reg == target_reg and op1.reg == target_reg): return None
ea += insn.size
insn = idaapi.insn_t()
if not idaapi.decode_insn(insn, ea): return None
if insn.get_canon_mnem() != "jnz": return None
return ea

def nop_range(start, end):
ea = start
while ea <= end:
size = idc.get_item_size(ea)
if size <= 0: break
idaapi.patch_bytes(ea, b"\x90" * size)
# print(f"NOPed instruction at {ea:#x}")
ea += size

def main():
print(f"Searching for 'mov reg, cs:nxxx' -> 'xor/test/jnz' in [{START_EA:#x}, {END_EA:#x})")
ea = START_EA
while ea < END_EA:
insn = idaapi.insn_t()
if not idaapi.decode_insn(insn, ea):
ea += 1
continue
is_match, reg = is_mov_cs_n_symbol(insn)
if is_match:
print(f"[+] Found 'mov ..., cs:n...' at {ea:#x} (reg={reg})")
jnz_ea = find_xor_test_jnz_pattern(ea + insn.size, reg, END_EA)
if jnz_ea is not None:
print(f" -> Full pattern found! jnz at {jnz_ea:#x}")
nop_range(ea, jnz_ea)
ea = jnz_ea + idc.get_item_size(jnz_ea)
continue
else: print(f" -> Pattern tail not found.")
ea += insn.size
print("Done.")

if __name__ == "__main__":
main()

反编译出来的bigfunc2:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
__int64 __fastcall BigFunc2(__int64 a1, __int64 a2)
{
*(_DWORD *)v34 = 0;
Time = time64(0);
gmtime64_s(&Tm, &Time);
memset(&buf, 0, sizeof(buf));
v2 = sub_7FF64A6CC040((__int64)&buf);
Format = (const char *)sub_7FF64A81EFB0(v2);
strftime(Buffer, 3u, Format, &Tm);
strcpy((char *)time_H, Buffer);
memset_with_0(v161, 0x18u);
v57 = cp_arg2plus192_arg1(a2, v143);
v59 = v57;
v58 = (_QWORD *)cp_arg2plus160_arg1(a2, v140);
v60 = v58;
GEN_AES_KEY((unsigned __int64)v161, v58, v59, time_H);
`std::locale::global'::`1'::dtor$2(v140);
`std::locale::global'::`1'::dtor$2(v143);
*(_QWORD *)&v169 = 0x706050403020100LL;
*((_QWORD *)&v169 + 1) = 0xF0E0D0C0B0A0908LL;
sub_7FF64A5D73D0(a2, v159);
LOBYTE(v33) = 0;
LOBYTE(v4) = 32;
sub_7FF64A8540E0(v159, v163, 0xFFFFFFFFLL, v4, v33, 0);
memset_with_0(v154, 0x18u);
sub_7FF64A616E40(v154, v163);
v5 = sub_7FF64A5A1F60(v161);
sub_7FF64A5F0530((__int64)v167, v5, &v169);
dst_ = sub_7FF64A854A20(v154);
v6 = (_BYTE *)sub_7FF64A5A1F60(v154);
AES_CBC_ENC((__int64)v167, v6, dst_);
memset(buf_1, 0, 1u);
v7 = sub_7FF64A6CC0A0(buf_1);
p_Destination = (const char *)sub_7FF64A81D0F0(v7);
v62 = strcpy(Destination, p_Destination);
v63 = v62;
sub_7FF64A9D1C60(v122, v62);
memset(&buf_, 0, sizeof(buf_));
v9 = sub_7FF64A6CC100(&buf_);
Source_1 = (const char *)sub_7FF64A81B230(v9);
v64 = strcpy(Destination_, Source_1);
v65 = v64;
sub_7FF64A9D1C60(v123, v64);
qmemcpy(dst__1, (const void *)unknown_libname_29(v136, v122, v124), sizeof(dst__1));
sub_7FF64A819330(v124, dst__1);
memset(&buf__1, 0, sizeof(buf__1));
v11 = sub_7FF64A6CC160(&buf__1);
Source_2 = (const char *)sub_7FF64A819370(v11);
v66 = strcpy(Destination__1, Source_2);
v67 = v66;
sub_7FF64A9D1C60(v126, v66);
jumpbuf_sp = sub_7FF64A854A20(v154);
v13 = sub_7FF64A5A1F60(v154);
v55 = sub_7FF64A5FE990(v144, v13, jumpbuf_sp);
v70 = v55;
sub_7FF64A9D1C60(v127, v55);
qmemcpy(dst__2, (const void *)unknown_libname_29(v137, v126, &v128), sizeof(dst__2));
sub_7FF64A819330(v125, dst__2);
qmemcpy(dst__3, (const void *)unknown_libname_29(v138, v124, v126), sizeof(dst__3));
LOBYTE(v14) = 2;
LOBYTE(v15) = 1;
sub_7FF64A854740(v158, dst__3, v15, v14);
`eh vector destructor iterator'(v124, 24, 2, sub_7FF64A6CC680);
`eh vector destructor iterator'(v126, 24, 2, sub_7FF64A6CC680);
`std::locale::global'::`1'::dtor$2(v144);
`std::locale::global'::`1'::dtor$2(Destination__1);
`eh vector destructor iterator'(v122, 24, 2, sub_7FF64A6CC680);
`std::locale::global'::`1'::dtor$2(Destination_);
`std::locale::global'::`1'::dtor$2(Destination);
memset_with_0(v165, 0x50u);
v71 = v150;
memset(&buf__2, 0, sizeof(buf__2));
memset(&buf__3, 0, sizeof(buf__3));
v72 = sub_7FF64A853BE0(v150, 0);
v76 = v72;
v16 = sub_7FF64A6CC220(&buf__2);
Source_3 = (const char *)sub_7FF64A8155B0(v16);
v73 = strcpy(Destination__2, Source_3);
v77 = v73;
LOBYTE(v33) = 0;
LOBYTE(v18) = 32;
dst__5 = sub_7FF64A8540E0(v158, v142, 0xFFFFFFFFLL, v18, v33, 0);
dst__4 = dst__5;
v19 = sub_7FF64A6CC1C0(&buf__3);
Source_4 = (const char *)sub_7FF64A817470(v19);
v75 = strcpy(Destination__3, Source_4);
v79 = v75;
sub_7FF64A5AD820(a1, v165, v75, dst__4, v77, v76);
`std::locale::global'::`1'::dtor$2(Destination__3);
`std::locale::global'::`1'::dtor$2(v142);
`std::locale::global'::`1'::dtor$2(Destination__2);
if ( !(unsigned __int8)Concurrency::details::_ContextCallback::_HasCapturedContext(v165)
|| *(_DWORD *)(sub_7FF64A5A1F60(v165) + 32) != 200 )
{
memset(&buf__4, 0, sizeof(buf__4));
v26 = sub_7FF64A6CC4F0(&buf__4);
Source_5 = (const char *)sub_7FF64A809D30(v26);
v109 = strcpy(Destination__4, Source_5);
v110 = v109;
v28 = sub_7FF64A5A1F70(v165);
sub_7FF64A5F0E60(v110, v28);
}
v80 = v151;
v81 = sub_7FF64A853BE0(v151, 0);
v69 = v81;
v82 = sub_7FF64A5A1F60(v165);
LOBYTE(v33) = 0;
LOBYTE(v21) = 1;
v115 = sub_7FF64A9D19D0(v156, v82 + 200, v69, v21, v33);
memset(&buf__5, 0, sizeof(buf__5));
v84 = sub_7FF64A6CC2B0(&buf__5);
Source = (char *)sub_7FF64A8136F0(v84);
v86 = strcpy(Source_, Source);
v87 = v86;
v40 = sub_7FF64A853ED0(v156, v86);
v41 = v40;
`std::locale::global'::`1'::dtor$2(Source_);
if ( !v41 )
{
LABEL_17:
sub_7FF64A6CC680(v156);
memset(&buf__6, 0, sizeof(buf__6));
memset(buf__7, 0, 1u);
v29 = sub_7FF64A6CC5B0(&buf__6);
Source_6 = (const char *)sub_7FF64A805FB0(v29);
v111 = strcpy(Destination__5, Source_6);
v56 = v111;
v31 = sub_7FF64A6CC550(buf__7);
Source_7 = (const char *)sub_7FF64A807E70(v31);
v112 = strcpy(Destination__6, Source_7);
v83 = v112;
sub_7FF64A5F4C50(v112, v56);
}
memset_with_0(v157, 0x18u);
Destination_1 = &v148;
memset(&buf__8, 0, sizeof(buf__8));
v88 = sub_7FF64A6CC310(&buf__8);
Source_8 = (char *)sub_7FF64A811830(v88);
v91 = strcpy(Destination_1, Source_8);
v92 = sub_7FF64A854000(v156, v91);
v116 = sub_7FF64A9D1B20(v92, v129);
v117 = sub_7FF64A606A60(v157, v129);
`std::locale::global'::`1'::dtor$2(v129);
v22 = sub_7FF64A5A1F60(v161);
sub_7FF64A5F0530((__int64)v168, v22, &v169);
v93 = sub_7FF64A854A20(v157);
v23 = (unsigned __int8 *)sub_7FF64A5A1F60(v157);
AES_CBC_DEC((__int64)v168, v23, v93);
v118 = sub_7FF64A616F60(v164, v157);
v94 = v152;
v95 = sub_7FF64A853BE0(v152, 0);
LOBYTE(v33) = 0;
LOBYTE(v24) = 1;
v119 = sub_7FF64A9D19D0(v155, v164, v95, v24, v33);
memset(&buf__9, 0, sizeof(buf__9));
v96 = sub_7FF64A6CC370(&buf__9);
Source_9 = (char *)sub_7FF64A80F970(v96);
v98 = strcpy(Destination__7, Source_9);
v99 = v98;
*(_DWORD *)v34 |= 1u;
if ( !(unsigned __int8)sub_7FF64A853ED0(v155, v98) )
goto LABEL_7;
memset(&buf__10, 0, sizeof(buf__10));
v100 = sub_7FF64A6CC430(&buf__10);
Source_10 = (char *)sub_7FF64A80DAB0(v100);
v102 = strcpy(Destination__8, Source_10);
v103 = v102;
dst[2] = sub_7FF64A9D1BD0(dst, v102);
*(_DWORD *)v34 |= 6u;
Destination_2 = &v149;
memset(&buf__11, 0, sizeof(buf__11));
v104 = sub_7FF64A6CC3D0(&buf__11);
Source_11 = (char *)sub_7FF64A80F970(v104);
v107 = strcpy(Destination_2, Source_11);
v108 = sub_7FF64A854000(v155, v107);
if ( (unsigned __int8)sub_7FF64A853BF0(v108, dst) )
v54 = 1;
else
LABEL_7:
v54 = 0;
v47 = v54;
if ( (v34[0] & 4) != 0 )
{
*(_DWORD *)v34 &= ~4u;
sub_7FF64A6CC680(dst);
}
if ( (v34[0] & 2) != 0 )
{
*(_DWORD *)v34 &= ~2u;
`std::locale::global'::`1'::dtor$2(Destination__8);
}
if ( (v34[0] & 1) != 0 )
{
*(_DWORD *)v34 &= ~1u;
`std::locale::global'::`1'::dtor$2(Destination__7);
}
if ( !v47 )
{
sub_7FF64A6CC680(v155);
`std::locale::global'::`1'::dtor$2(v164);
sub_7FF64A854A30(v157);
goto LABEL_17;
}
v48 = 1;
sub_7FF64A6CC680(v155);
`std::locale::global'::`1'::dtor$2(v164);
sub_7FF64A854A30(v157);
sub_7FF64A6CC680(v156);
sub_7FF64A5A7B70(v165);
sub_7FF64A6CC680(v158);
sub_7FF64A854A30(v154);
`std::locale::global'::`1'::dtor$2(v163);
sub_7FF64A6CC680(v159);
sub_7FF64A854A30(v161);
`std::locale::global'::`1'::dtor$2(time_H);
return v48;
}

func2中定义了一个AES_CBC加密,iv为000102030405060708090a0b0c0d0e0f,Key的构造过程则比较复杂:

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
*(_DWORD *)v34 = 0;
Time = time64(0);
gmtime64_s(&Tm, &Time);
memset(&buf, 0, sizeof(buf));
v2 = sub_7FF64A6CC040((__int64)&buf);
Format = (const char *)sub_7FF64A81EFB0(v2); //时间格式
strftime(Buffer, 3u, Format, &Tm); //获取了某个时间类
strcpy((char *)time_H, Buffer);
memset_with_0(key, 0x18u);
v57 = cp_arg2plus192_arg1(a2, v143);
v59 = v57;
v58 = (_QWORD *)cp_arg2plus160_arg1(a2, v140);
v60 = v58;
GEN_AES_KEY((unsigned __int64)key, v58, v59, time_H); //生成AES密钥
`std::locale::global'::`1'::dtor$2(v140);
`std::locale::global'::`1'::dtor$2(v143);
*(_QWORD *)&iv = 0x706050403020100LL; //AES iv
*((_QWORD *)&iv + 1) = 0xF0E0D0C0B0A0908LL;
sub_7FF64A5D73D0(a2, v159);
LOBYTE(v33) = 0;
LOBYTE(v4) = 32;
sub_7FF64A8540E0(v159, v163, 0xFFFFFFFFLL, v4, v33, 0);
memset_with_0(msg_, 0x18u);
sub_7FF64A616E40(msg_, v163);
key_p = QwordPointer(key);
AES_KEY_STREAM_GEN_((__int64)key_stream, key_p, &iv); //扩展AES密钥
dst_ = sub_7FF64A854A20(msg_);
msg = (_BYTE *)QwordPointer(msg_);
AES_CBC_ENC((__int64)key_stream, msg, dst_); //AES加密

查看时间格式获取,发现是一个自解密(已去除混淆):

1
2
3
4
*(_DWORD *)(v2 + 520) = 0x103971E;
*a1 ^= 0x3Bu;
a1[1] ^= 0xDFu;
a1[2] ^= 3u;

解密之后是%H,也就是小时(依据流量包在这里是06),查看GEN_AES_KEY:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
__m128 **__fastcall GEN_AES_KEY(__m128 **key, char *a2, __int64 **a3, _QWORD *p_time_H)
{
n0x20 = 0;
*(_OWORD *)keya = 0;
v27 = 0;
sub_7FF64A9D4DA0(keya, 0x20u);
key_1 = (__m128i *)keya[0];
*(_OWORD *)keya[0] = 0;
key_1[1] = 0;
keya[1] = key_1[2].m128i_i8;
if ( *((_QWORD *)a2 + 3) <= 0xFu )
{
v10 = &a2[*((_QWORD *)a2 + 2)];
}
else
{
v10 = (char *)(*((_QWORD *)a2 + 2) + *(_QWORD *)a2);
a2 = *(char **)a2;
}
SHA256__(a2, v10, key_1, (__m128i *)key_1[2].m128i_i8);
Size = (size_t)a3[2];
Size_1 = p_time_H[2];
if ( 0x7FFFFFFFFFFFFFFFLL - Size < Size_1 )
sub_7FF64A5A1570();
if ( (unsigned __int64)a3[3] > 0xF )
a3 = (__int64 **)*a3;
if ( p_time_H[3] > 0xFu )
p_time_H = (_QWORD *)*p_time_H;
sub_7FF64A9D54A0(v28, Size_1, v11, a3, Size, p_time_H, Size_1);
*(_OWORD *)Block = 0;
v25 = 0;
sub_7FF64A9D4DA0(Block, 0x20u);
key_2 = (__m128i *)Block[0];
*(_OWORD *)Block[0] = 0;
key_2[1] = 0;
Block[1] = &key_2[2];
v15 = v28;
if ( n0xF > 0xF )
v15 = (char **)v28[0];
v16 = (char *)v15 + (unsigned __int64)v28[2];
v17 = (char *)v28;
if ( n0xF > 0xF )
v17 = v28[0];
SHA256__(v17, v16, key_2, (__m128i *)key_2[2].m128i_i8);
*(_OWORD *)key = 0;
*key = 0;
key[1] = 0;
key[2] = 0;
sub_7FF64A9D4DA0(key, 0x20u);
v18 = (__int64)*key;
*(_OWORD *)v18 = 0;
*(_OWORD *)(v18 + 16) = 0;
key[1] = (__m128 *)(v18 + 32);
key_4 = *key;
key_3 = &(*key)[1].m128_i8[15];
if ( (*key > (__m128 *)((char *)&key_1[1].m128i_u64[1] + 7) || key_3 < (char *)key_1)
&& (key_4 > (__m128 *)((char *)&key_2[1].m128i_u64[1] + 7) || key_3 < (char *)key_2)
&& (key_4 > (__m128 *)key || key_3 < (char *)key) )
{
*key_4 = _mm_xor_ps((__m128)_mm_loadu_si128(key_2), (__m128)_mm_loadu_si128(key_1));
key_4[1] = _mm_xor_ps((__m128)_mm_loadu_si128(key_2 + 1), (__m128)_mm_loadu_si128(key_1 + 1));
}
else
{
key_3 = (char *)((char *)key_2 - (char *)key_1);
do
{
(*key)->m128_i8[n0x20] = key_1->m128i_i8[n0x20] ^ key_2->m128i_i8[n0x20];
++n0x20;
}
while ( n0x20 < 0x20 );
}
if ( key_2 )
{
key_5 = key_2;
if ( (unsigned __int64)(v25 - (_QWORD)key_2) >= 0x1000 )
{
key_2 = (__m128i *)key_2[-1].m128i_i64[1];
if ( (unsigned __int64)((char *)key_5 - (char *)key_2 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(key_2);
}
sub_7FF64A9CD3B0(v28, key_3);
if ( key_1 )
{
key_6 = key_1;
if ( (unsigned __int64)(v27 - (_QWORD)key_1) >= 0x1000 )
{
key_1 = (__m128i *)key_1[-1].m128i_i64[1];
if ( (unsigned __int64)((char *)key_6 - (char *)key_1 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(key_1);
}
return key;
}

是把两个block取sha256后相互异或得到密钥,两个block分别来自main中的320字节结构体的偏移量160处的内容和192处的内容+两位小时时间戳(06),经过BigFunc1后,160的位置应该会变成2025082006TheBoss@THUNDERNODE,192处的字节未知

调试

在虚拟机上配置好用户名、机器名、hosts,编写服务器脚本接受程序响应并发送第一个包:

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
from http.server import HTTPServer, BaseHTTPRequestHandler

class CustomHandler(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.0'
def do_GET(self):
if self.path == '/good':
response_body = b'{"d": "085d8ea282da6cf76bb2765bc3b26549a1f6bdf08d8da2a62e05ad96ea645c685da48d66ed505e2e28b968d15dabed15ab1500901eb9da4606468650f72550483f1e8c58ca13136bb8028f976bedd36757f705ea5f74ace7bd8af941746b961c45bcac1eaf589773cecf6f1c620e0e37ac1dfc9611aa8ae6e6714bb79a186f47896f18203eddce97f496b71a630779b136d7bf0c82d560"}'
self.send_response(200, 'OK')
self.send_header("Server", "SimpleHTTP/0.6 Python/3.10.11")
self.send_header("Date", "Wed, 20 Aug 2025 06:12:07 GMT")
self.send_header("Content-type", "application/json")
self.send_header("Content-Length", str(len(response_body)))
self.end_headers()
self.wfile.write(response_body)
else:
self.send_error(404, "Not Found")

# def log_message(self, format, *args):
# return

if __name__ == "__main__":
port = 8000
server = HTTPServer(('127.0.0.1', port), CustomHandler)
print(f"Server listening on http://127.0.0.1:{port}")
print(" Access /good to get the fixed JSON response.")
server.serve_forever()

发现BigFunction1可以正常继续执行,192处的截断符是“@”,下断点查看解密的密文,是peanut@theannualtraditionofstaringatdisassemblyforweeks.torealizetheflagwasjustxoredwiththefilenamethewholetime.com:8080,所以192处的实际字符是peanut,动态调试发现在BigFunction2的AES密钥生成处,偏移量160的位置实际上回到了TheBoss@THUNDERNODE,动态调试发现key_1为c30b158c92ffbc95e02b222206cec9676eac6654144e5557a2d97569c11ed8df,key_2为56a49e85c98bd96ce5b6217abc02eb5f3eec3ff4a937e1ccc549d30bcbc3b549,密钥为95af8b095b7465f9059d0358bacc2238504059a0bd79b49b6790a6620add6d96,编写脚本发现可以直接解密,解密整个流量包:

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
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
import binascii

cipher_hex = "密文"
prefix = binascii.unhexlify(cipher_hex)

block160 = b"TheBoss@THUNDERNODE"
block192 = b"peanut06"

h1 = SHA256.new()
h1.update(block160)
A = h1.digest()

h2 = SHA256.new()
h2.update(block192)
B = h2.digest()

X = bytes(x ^ y for x, y in zip(A, B))
key24 = X[:32]
iv = bytes(range(16))

cipher = AES.new(key24, AES.MODE_CBC, iv)
pt = cipher.decrypt(prefix)
pad = pt[-1]
maybe_unpadded = pt
if 1 <= pad <= 16 and pt.endswith(bytes([pad])*pad): maybe_unpadded = pt[:-pad]
print(pt)

解密消息合集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
b'{"ci":"Architecture: x64, Cores: 2","cn":"THUNDERNODE","hi":"TheBoss@THUNDERNODE","mI":"6143 MB","ov":"Windows 6.2 (Build 9200)","un":"TheBoss"}\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
b'{"sta": "ok"}\x03\x03\x03'
b'{"msg": "no_op"}\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
b'{"msg": "cmd", "d": {"cid": 2, "line": "whoiam"}}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
b'{"op":""}\x07\x07\x07\x07\x07\x07\x07'
b'{"msg": "cmd", "d": {"cid": 2, "line": "whoami"}}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
b'{"op":"thundernode\\\\theboss\\n"}\x01'
b'{"msg": "cmd", "d": {"cid": 2, "line": "systeminfo | findstr /B /C:\\"OS Name\\" /C:\\"OS Version\\""}}\r\r\r\r\r\r\r\r\r\r\r\r\r'
b'{"op":"OS Name: Microsoft Windows 10 Pro\\nOS Version: 10.0.19045 N/A Build 19045\\n"}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
b'{"msg": "cmd", "d": {"cid": 2, "line": "dir /b C:\\\\Users\\\\%USERNAME%\\\\"}}\x07\x07\x07\x07\x07\x07\x07'
b'{"op":"3D Objects\\nContacts\\nDesktop\\nDocuments\\nDownloads\\nFavorites\\nLinks\\nMusic\\nOneDrive\\nPictures\\nSaved Games\\nSearches\\nVideos\\n"}\x06\x06\x06\x06\x06\x06'
b'{"msg": "cmd", "d": {"cid": 2, "line": "arp -a"}}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
b'{"op":"\\nInterface: 1.1.1.1 --- 0x1\\n Internet Address Physical Address Type\\n 224.0.0.22 static \\n 224.0.0.251 static \\n 224.0.0.252 static \\n 239.255.255.250 static \\n\\nInterface: 192.168.56.103 --- 0x7\\n Internet Address Physical Address Type\\n 192.168.56.100 08-00-27-ab-e1-14 dynamic \\n 192.168.56.117 08-00-27-93-a7-cc dynamic \\n 192.168.56.255 ff-ff-ff-ff-ff-ff static \\n 224.0.0.22 01-00-5e-00-00-16 static \\n 224.0.0.251 01-00-5e-00-00-fb static \\n 224.0.0.252 01-00-5e-00-00-fc static \\n 239.255.255.250 01-00-5e-7f-ff-fa static \\n 255.255.255.255 ff-ff-ff-ff-ff-ff static \\n"}\x04\x04\x04\x04'
b'{"msg": "cmd", "d": {"cid": 2, "line": "query user"}}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
b'{"op":" USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME\\n>theboss console 2 Active none 8/18/2025 8:30 AM\\n"}\x06\x06\x06\x06\x06\x06'
b'{"msg": "cmd", "d": {"cid": 2, "line": "dir /b C:\\\\Users\\\\%USERNAME%\\\\Desktop"}}\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
b'{"op":"Google Chrome.lnk\\nLyrics.lnk\\nnotes.txt\\nStudio_Masters_Vault.lnk\\n_DELETED_STUFF\\n"}\x03\x03\x03'
b'{"msg": "cmd", "d": {"cid": 2, "line": "dir /b C:\\\\Users\\\\%USERNAME%\\\\Documents"}}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
b'{"op":"boss_tech_notes.txt\\nE_Street_Band_Contacts.xlsx\\nLyrics\\nPersonal_Stuff\\nStudio_Masters_Vault\\nSweetScape\\nTour_Rider_2024.docx\\nVisual Studio 2022\\n"}\x01'
b'{"msg": "cmd", "d": {"cid": 5, "lp": "C:\\\\Users\\\\%USERNAME%\\\\Documents\\\\boss_tech_notes.txt"}}\x02\x02'
b'{"fc":"","sta":"error cnof"}\x04\x04\x04\x04'
b'{"msg": "cmd", "d": {"cid": 5, "lp": "C:\\\\Users\\\\TheBoss\\\\Documents\\\\boss_tech_notes.txt"}}\x05\x05\x05\x05\x05'
b'{"fc":"WWVhaCwgSSBnZXQgaXQuIFNvbWUgZ3V5cywgdGhleSdyZSBoYXBweSBqdXN0IHRvIHR1cm4gdGhlIGtleSBhbmQgZHJpdmUuIEJ1dCB5b3UuLi4geW91IGdvdHRhIHBvcCB0aGUgaG9vZC4gWW91IGdvdHRhIHRyYWNlIHRoZSB3aXJlcywgZmVlbCB0aGUgaGVhdCBjb21pbicgb2ZmIHRoZSBibG9jay4gWW91J3JlIG5vdCBsb29raW5nIHRvIHN0ZWFsIHRoZSBjYXIuLi4geW91J3JlIGp1c3QgdHJ5aW5nIHRvIHVuZGVyc3RhbmQgdGhlIHNvdWwgb2YgdGhlIGVuZ2luZS4gVGhhdCdzIGFuIGhvbmVzdCBuaWdodCdzIHdvcmsgcmlnaHQgdGhlcmUu","sta":"success"}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'

base64解密得到:Yeah, I get it. Some guys, they're happy just to turn the key and drive. But you... you gotta pop the hood. You gotta trace the wires, feel the heat comin' off the block. You're not looking to steal the car... you're just trying to understand the soul of the engine. That's an honest night's work right there.

1
b'{"msg": "cmd", "d": {"cid": 6, "dt": 20, "np": "TheBoss@THUNDERNODE"}}\n\n\n\n\n\n\n\n\n\n'

这里改变了block192处的值,变为了TheBoss@THUNDERNODE06,继续解密:

1
2
3
4
5
b'{"msg": "cmd", "d": {"cid": 2, "line": "dir /b /s C:\\\\Users\\\\%USERNAME%\\\\Documents\\\\Studio_Masters_Vault\\\\"}}\x03\x03\x03'
b'{"op":"C:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\The_Vault\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Live at the Bottom Line, NYC (1975-08-15)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Winterland Ballroom, San Francisco (1978-12-15)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Live at the Bottom Line, NYC (1975-08-15)\\\\01 - For You.flac\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Live at the Bottom Line, NYC (1975-08-15)\\\\02 - Tenth Avenue Freeze-Out.flac\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Live at the Bottom Line, NYC (1975-08-15)\\\\03 - She\'s the One.flac\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Winterland Ballroom, San Francisco (1978-12-15)\\\\01 - Good Rockin\' Tonight.flac\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Winterland Ballroom, San Francisco (1978-12-15)\\\\02 - Badlands.flac\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Live_Bootlegs\\\\Winterland Ballroom, San Francisco (1978-12-15)\\\\03 - Prove It All Night.flac\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born in the U.S.A. (1984)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born to Run (1975)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Darkness on the Edge of Town (1978)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Greetings from Asbury Park, N.J. (1973)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\The River (1980)\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born in the U.S.A. (1984)\\\\01 - Born in the U.S.A..wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born in the U.S.A. (1984)\\\\02 - Glory Days.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born in the U.S.A. (1984)\\\\03 - Dancing in the Dark.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born in the U.S.A. (1984)\\\\04 - My Hometown.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born to Run (1975)\\\\01 - Thunder Road.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born to Run (1975)\\\\02 - Tenth Avenue Freeze-Out.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born to Run (1975)\\\\03 - Born to Run.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Born to Run (1975)\\\\04 - Jungleland.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Darkness on the Edge of Town (1978)\\\\01 - Badlands.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Darkness on the Edge of Town (1978)\\\\02 -Racing in the Street.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Darkness on the Edge of Town (1978)\\\\03 - The Promised Land.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Greetings from Asbury Park, N.J. (1973)\\\\01 - Blinded by the Light.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Greetings from Asbury Park, N.J. (1973)\\\\02 - Growin\' Up.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\Greetings from Asbury Park, N.J. (1973)\\\\03 - Spirit in the Night.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\The River (1980)\\\\01 - Hungry Heart.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\The River (1980)\\\\02 - The River.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\Studio_Masters\\\\Released_Albums\\\\The River (1980)\\\\03 - Independence Day.wav\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\The_Vault\\\\Darkness_Acoustic.mp3\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\The_Vault\\\\rocknroll.zip\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\The_Vault\\\\The_River_Outtakes.zip\\n"}\n\n\n\n\n\n\n\n\n\n'
b'{"msg": "cmd", "d": {"cid": 2, "line": "dir /b C:\\\\Users\\\\%USERNAME%\\\\Documents\\\\Studio_Masters_Vault\\\\The_Vault"}}\r\r\r\r\r\r\r\r\r\r\r\r\r'
b'{"op":"Darkness_Acoustic.mp3\\nrocknroll.zip\\nThe_River_Outtakes.zip\\n"}\t\t\t\t\t\t\t\t\t'
b'{"msg": "cmd", "d": {"cid": 5, "lp": "C:\\\\Users\\\\TheBoss\\\\Documents\\\\Studio_Masters_Vault\\\\The_Vault\\\\rocknroll.zip"}}\n\n\n\n\n\n\n\n\n\n'

在65号对话导出一个zip,其中有flag.jpg但是有加密

1
b'{"msg": "cmd", "d": {"cid": 6, "dt": 25, "np": "miami"}}\x08\x08\x08\x08\x08\x08\x08\x08'

切换密码到miami

1
2
3
4
5
6
7
b'{"msg": "cmd", "d": {"cid": 2, "line": "dir /b /s C:\\\\Users\\\\%USERNAME%\\\\Documents\\\\Personal_Stuff"}}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
b'{"op":"C:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\Financial\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\passwords.txt\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\Photos\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\Financial\\\\tax_info_2023.pdf\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\Photos\\\\Asbury_Park_sunset.jpg\\nC:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\Photos\\\\old_guitar.jpg\\n"}\x05\x05\x05\x05\x05'
b'{"msg": "cmd", "d": {"cid": 6, "dt": 1, "np": "miami"}}\t\t\t\t\t\t\t\t\t'
b'{"msg": "cmd", "d": {"cid": 5, "lp": "C:\\\\Users\\\\TheBoss\\\\Documents\\\\Personal_Stuff\\\\passwords.txt"}}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
b'{"fc":"RW1haWw6IEJvcm5Ub1J1biE3NQ0KQmFuazogVGhlUml2ZXIjIzE5ODANCkNvbXB1dGVyTG9naW46IFRoZUJvc3NNYW4NCk90aGVyOiBUaGVCaWdNQG4xOTQyIQ0K","sta":"success"}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
b'{"msg": "cmd", "d": {"cid": 2, "line": "echo \\"BRUUUUUUUUUUUUUUUUUUUCCCCCEEEEEEEEEEEEEEEEEEEEEEEEE\\" > C:\\\\Users\\\\%USERNAME%\\\\Desktop\\\\thanks.txt"}}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
b'{"msg": "cmd", "d": {"cid": 3}}\x01'

解密得到:

1
2
3
4
Email: BornToRun!75
Bank: TheRiver##1980
ComputerLogin: TheBossMan
Other: TheBigM@n1942!

密码是TheBigM@n1942!

C4N7_ST4R7_A_FLAR3_WITHOUT_4_$PARK@FLARE-ON.com

#8 FlareAuthenticator

程序的11个主要函数存在大量间接跳转,0x14008F160的位置有一个32768字节的数组,其后不远在0x140097180有一个36字节的数组,0x14009773C有一个1573字节的数组,0x14009C0E0有一个21662大小的间接跳转地址计算表,且导致有大量代码未识别,先编写脚本识别代码:

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
import ida_bytes
import ida_ua
import ida_funcs
import ida_ida

MAX_INSN_SIZE = 16

def force_code_in_range(start_ea, end_ea):
ea = start_ea
while ea < end_ea:
flags = ida_bytes.get_flags(ea)
if not ida_bytes.is_code(flags):
ida_bytes.del_items(ea, ida_bytes.DELIT_SIMPLE, MAX_INSN_SIZE)
if ida_ua.create_insn(ea):
insn = ida_ua.insn_t()
if ida_ua.decode_insn(insn, ea): next_ea = ea + insn.size
else: next_ea = ea + 1
ea = next_ea
else: ea += 1
else:
insn = ida_ua.insn_t()
if ida_ua.decode_insn(insn, ea): ea += insn.size
else: ea += 1

def make_function_in_range(start_ea, size):
end_ea = start_ea + size
ida_bytes.del_items(start_ea, ida_bytes.DELIT_SIMPLE, 1)
if not ida_ua.create_insn(start_ea):
print(f"Cannot make code at 0x{start_ea:X}")
return False
func = ida_funcs.get_func(start_ea)
if func:
ida_funcs.del_func(func)
if ida_funcs.add_func(start_ea, end_ea):
print(f"Reload func: 0x{start_ea:X} - 0x{end_ea:X}")
return True
else:
print(f"Cannot make func at 0x{start_ea:X}")
return False

addrs = [0x140073000, 0x140074860, 0x140081760, 0x14007D290, 0x14000D1E0,
0x14002F8C0, 0x140012E50, 0x140001DE0, 0x1400202B0, 0x14005EF60, 0x140037160]
sizes = [0xB20, 0x19A7, 0x23B0, 0x2439, 0x3D4A,
0x645A, 0x73BE, 0xB08C, 0xDC63, 0x132ED, 0x24D6F]

for i in range(len(addrs)):
start = addrs[i]
size = sizes[i]
end = start + size
print(f"Parsing area [{i}]: 0x{start:X} - 0x{end:X} (size=0x{size:X})")
force_code_in_range(start, end)
make_function_in_range(start, size)

print("ALL Done")

然后动态调试(需设置环境变量QT_QPA_PLATFORM_PLUGIN_PATH=所在路径),在start下断点,启动instruction tracing,然后运行脚本,trace所有的间接跳转:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import idc
import ida_bytes
import ida_dbg
import ida_nalt
import os
import time

STATIC_BASE = 0x140000000
addrs = [0x140073000, 0x140074860, 0x140081760, 0x14007D290, 0x14000D1E0,
0x14002F8C0, 0x140012E50, 0x140001DE0, 0x1400202B0, 0x14005EF60, 0x140037160]
sizes = [0xB20, 0x19A7, 0x23B0, 0x2439, 0x3D4A,
0x645A, 0x73BE, 0xB08C, 0xDC63, 0x132ED, 0x24D6F]

OUTPUT_FILE = r"E:/CTF/temp/out.txt"

execution_log = []
func_stats = []
last_trace_time = None
last_timeout_mark = None
TIMEOUT_SECONDS = 5

class MyHooks(ida_dbg.DBG_Hooks):
def __init__(self, target_addrs):
super().__init__()
self.target_addrs = set(target_addrs)

def dbg_trace(self, tid, ip):
global last_trace_time, last_timeout_mark
now = time.time()
if (last_timeout_mark is None or now - last_timeout_mark >= TIMEOUT_SECONDS) and \
(last_trace_time is not None and now - last_trace_time >= TIMEOUT_SECONDS):
execution_log.append(("TIMEOUT", None))
last_timeout_mark = now
print("[!] 5s idle — inserted timeout marker")
if ip in self.target_addrs:
rax = ida_dbg.get_reg_val("RAX")
execution_log.append((ip, rax))
print(f"[TRACE] 0x{ip:X} -> RAX=0x{rax:X}")
last_trace_time = now
return 0

def dbg_process_exit(self, pid, tid, ea, exit_code):
print(f"[+] Process exited (code={exit_code})")
finish_logging()
return 0

def scan_jmp_call_rax_runtime(start_ea, end_ea):
results = []
ea = start_ea
while ea < end_ea:
b0 = ida_bytes.get_byte(ea)
b1 = ida_bytes.get_byte(ea + 1) if ea + 1 < end_ea else 0
if b0 == 0xFF and b1 == 0xE0:
results.append((ea, "jmp"))
ea += 2
elif b0 == 0xFF and b1 == 0xD0:
results.append((ea, "call"))
ea += 2
else: ea += 1
return results

def finish_logging():
hooks = getattr(main, 'hooks', None)
if hooks: hooks.unhook()
actual_base = ida_nalt.get_imagebase()
os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
with open(OUTPUT_FILE, "w") as f:
f.write(f"Runtime base: 0x{actual_base:X}\n\n")
f.write("=== Part 1: Counts (static scan) ===\n")
for i, (jmp, call) in enumerate(func_stats):
f.write(f"Func {i}: jmp={jmp}, call={call}\n")
f.write("\n=== Part 2: Execution Log (in order, runtime addresses) ===\n")
for item in execution_log:
if item[0] == "TIMEOUT":
f.write("=== Run To A Manual Part ===\n")
else:
ip, rax = item
f.write(f"0x{ip:X} -> RAX=0x{rax:X}\n")
print(f"[+] Saved to {OUTPUT_FILE}")

def main():
global func_stats, last_trace_time
actual_base = ida_nalt.get_imagebase()
print(f"[+] Runtime base: 0x{actual_base:X}")
all_jc = []
func_stats = []
for i in range(len(addrs)):
static_start = addrs[i]
size = sizes[i]
offset = static_start - STATIC_BASE
runtime_start = actual_base + offset
runtime_end = runtime_start + size
jc_list = scan_jmp_call_rax_runtime(runtime_start, runtime_end)
all_jc.extend(jc_list)
jmp_count = sum(1 for _, t in jc_list if t == "jmp")
call_count = sum(1 for _, t in jc_list if t == "call")
func_stats.append((jmp_count, call_count))
runtime_addrs = [ea for ea, _ in all_jc]
hooks = MyHooks(runtime_addrs)
hooks.hook()
last_trace_time = time.time()
print("[+] Hook installed.")
main.hooks = hooks

if __name__ == "__main__":
main()

此脚本记录了程序的整个流程中所有的间接跳转行为:加载GUI界面第一次按下按钮加载输入逻辑按下OK触发检验检验逻辑关闭窗口清零输入框关闭程序程序退出,其中橙色的是程序自己的逻辑,其中黄色的是用户的交互部分,这些交互部分持续时间长,过程中不会发生任何trace行为,所以以时间间隔作为切分标准,在输出中插入用户完成的部分,以此区分每个过程所用到的函数地址段.

分析得到的trace,发现有以下逻辑:

事件类型 地址范围 出现次数 对应函数
启动程序 0x140002D7B ~ 0x14007ED7E 972 main → sub_140037160 → sub_140001DE0 → sub_14000D1E0 → main
加载输入 0x140013280 ~ 0x1400831D8 2 sub_140012E50 & sub_140081760
触发校验 0x140021593 ~ 0x14002A500 16 sub_1400202B0
清空输入 0x14000D57E ~ 0x14002B29C 26 sub_140012E50
退出程序 0x1400730F3 ~ 0x140075C85 1 main

因此,sub_1400202B0就是最关键的校验函数之一:

发现校验函数是大量线性化的,其中有很大一部分代码被跳过了,可能是未通过某种校验,重复的部分大概有15轮多一些的循环和8轮多一些的循环,接下来先查看到地址发生跳变的位置:

1
2
3
4
5
.text:0000000140022113                 jmp     rax
.text:0000000140022115 mov al, [rbp+2950h+var_21C1]
.text:000000014002211B test al, 1
.text:000000014002211D jnz short loc_140022124
.text:000000014002211F jmp loc_14002795C

这里22113直接跳转到了下一行(22115),从[rbp+2950h+var_21C1]提取了某种校验结果,接下来jnz跳转到22124,否则直接跳到2795C,看起来这就是发生跳变的根本原因:此处的某种校验未通过,接下来动态调试,测试明文1234567890123456789012345,在函数开头断点,搜索内存,找到明文存储的位置:

1
debug308:000002598BC39660 a12345678901234 db '1234567890123456789012345',0

发现同一次启动过程中明文输入是被写入栈上的同一个位置的,在该位置下硬件断点,写入一个字符后发现在0x140015A47的call rax触发了检测:

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
__int64 __fastcall read_for_input_w(__int64 a1, const void *a2)
{
return read_for_input(a1, a2);
}

__int64 __fastcall read_for_input(__int64 a1, const void *a2)
{
v2 = strlen_w(a2);
Size = self(v2);
return input_pwd_nums(a1, a2, Size);
}

__int64 *__fastcall input_pwd_nums(__int64 *dest, void *inp, size_t Size_1)
{
v12 = &v11;
v11 = &v12;
Sizea = Size_1;
inp_ = inp;
dest_1 = dest;
v6 = dest[2];
if ( Size_1 > dest[3] - v6 )
return expand_buffer(dest, Sizea, v5[0], (__int64)inp_, Sizea);
dest[2] = Sizea + v6;
*(_QWORD *)&v5[2] = get_data(dest);
input_pwd_single_num((void *)(v6 + *(_QWORD *)&v5[2]), inp_, Sizea);
v5[1] = 0;
add_str_end((_BYTE *)(Sizea + v6 + *(_QWORD *)&v5[2]), &v5[1]);
return dest;
}

void *__fastcall input_pwd_single_num(void *dest, const void *input, size_t Size)
{
v6 = &v5;
v5 = &v6;
memmove(dest, input, Size);
return dest;
}

这里就是负责把输入写到栈上的几个函数,同理可以定位到call清空输入函数的位置0x14000FAC1,但是尝试在明文上下断点,发现从按下检验按钮到弹出密码错误的全程根本没有被断到.

来到22113的位置,jnz改jz,发现下面有:

1
2
3
4
5
.text:00007FF7E57F22DE movaps  xmm0, xmmword ptr [rcx+rdx]
.text:00007FF7E57F22E2 movaps xmmword ptr [rax+10h], xmm0
.text:00007FF7E57F22E6 mov rdx, 5497F002705654CFh
.text:00007FF7E57F22F0 movaps xmm0, xmmword ptr [rcx+rdx]
.text:00007FF7E57F22F4 movaps xmmword ptr [rax], xmm0

此处恰好用xmm寄存器加载了之前的36字节数组的前32项,存入了栈上,也打上硬件断点

之后动态调试发现明文的部分被调用,在0x140060BF1(属于sub_14005EF60,这个函数在之前的trace记录中未出现)处的call rax中调用了strlen并求了明文的长度,猜测之前的test比较实际上是反调试,结果运行发现程序居然直接弹出了success,虽然结果是乱码,但这似乎说明检验逻辑还是前面的结果,后面的function应该是32字节密文的解密函数,这说明,实际上输入值的校验逻辑很少(只有从按下按钮到jnz那个位置的一点点),尝试将sub_1400202B0的所有jmp rax全nop掉,发现并不影响后续的解密,因此全部nop后反编译函数,查看前半段:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void __fastcall sub_1400202B0(__int64 a1)
{
...
v1 = ((jmptable[14940] + 0x5E44FBCB4F243E53LL))(v1062);
v2 = ((jmptable[10247] - 0x28BFB5DCDC279D57LL))(*v1);
((jmptable[19728] + 0x1721AB0F45842383LL))(*v2) = 7499;
v3 = ((jmptable[4536] + 0x54BD4163F2F0F7D9LL))(v984);
v4 = ((jmptable[19738] - 0x7DFD7B17745710ABLL))(*v3);
((jmptable[15289] + 0x47DDB7E8B57507C1LL))((*v4) + 16LL) = 7499;
v5 = ((jmptable[6718] + 0x3C26E6D0F570B54CLL))(v954);
v6 = ((jmptable[17102] - 0x6FEBE53F68431F97LL))(*v5);
v7 = ((jmptable[12227] + 0x6CD614C4B188819LL))(*v6);
v945 = 758585089LL * ((jmptable[4914] - 0x65C3C1DE9FABF6C4LL))(*v7) + 16LL);
v947 = 4185932529LL;
v946 = ((2 * (v945 % 0xF98042F1 - ((v945 % 0xF98042F1) | 0xFFFFFFFF09A45D6EuLL)) - 0x1ECB74524LL) | (((v945 % 0xF98042F1) | 0xFFFFFFFF09A45D6EuLL) - ((v945 % 0xF98042F1) & 0x9A45D6E))) + ((2 * (v945 % 0xF98042F1 - ((v945 % 0xF98042F1) | 0xFFFFFFFF09A45D6EuLL)) - 0x1ECB74524LL) & (((v945 % 0xF98042F1) | 0xFFFFFFFF09A45D6EuLL) - ((v945 % 0xF98042F1) & 0x9A45D6E)));
v948 = v946 % 0xF98042F1;
v8 = ((jmptable[15535] + 0x2AE89C655B35F75BLL))(v969);
((jmptable[19543] + 0x5F08A30C9C4FC1B7LL))((*v8) + 16LL) = v946 % 0xF98042F1;
v9 = ((jmptable[16704] + 0x265E77A3981E8F61LL))(v984);
v10 = ((jmptable[16883] + 0x19EDD615D994E6BELL))(*v9);
v871 =((jmptable[16290] - 0x219A8B3006EE333ALL))((*v10) + 16LL);
v11 = ((jmptable[11402] + 0x21A1833C9081862ALL))(v967);
v12 = ((jmptable[18239] - 0x38FECD2087696090LL))(*v11);
v13 = ((jmptable[8781] - 0x1A7B67F12AE10F0FLL))(*v12);
((jmptable[2953] + 0x2152BD9F7F962926LL))(*v13);
n48 = 48;
v14 = alloca(48);
v864 = v659;
v15 = alloca(48);
v865 = v659;
v866 = 2785373956LL * ((jmptable[19680] + 0x722A61684B591457LL))((v1021) + 16LL);
v868 = 4185932529LL;
v867 = ((2 * (v866 % 0xF98042F1 - ((v866 % 0xF98042F1) | 0xFFFFFFFF176DB5CCuLL)) - 0x1D1249468LL) | (((v866 % 0xF98042F1) | 0xFFFFFFFF176DB5CCuLL) - ((v866 % 0xF98042F1) & 0x176DB5CC))) + ((2 * (v866 % 0xF98042F1 - ((v866 % 0xF98042F1) | 0xFFFFFFFF176DB5CCuLL)) - 0x1D1249468LL) & (((v866 % 0xF98042F1) | 0xFFFFFFFF176DB5CCuLL) - ((v866 % 0xF98042F1) & 0x176DB5CC)));
v869 = v867 % 0xF98042F1;
v16 = ((jmptable[2445] - 0x629324896A4A015BLL))(v984);
v17 = ((jmptable[12409] + 0x4938D72D4DC4A24LL))(*v16);
v18 = ((jmptable[16925] - 0x4F4DA3387B568DLL))(*v17);
*(*v18 + 16LL) = v869;
v19 = ((jmptable[4495] - 0x33B035FF570E4A43LL))(v969);
v870 =((jmptable[8273] + 0x13DA12590CFFE6D0LL))((*v19) + 16LL);
v20 = ((jmptable[4155] - 0xBCA0AB10DB2CC04LL))(v1009);
v21 = ((jmptable[9050] - 0x4AE74C311B803BCELL))(*v20);
v22 = ((jmptable[6793] - 0x6968DD1D9962AB96LL))(*v21);
v23 = ((jmptable[3692] - 0x509873FC4867468ALL))(*v22);
v25 = 0xD5175A4653DE77E3uLL;
if ( **v23 != v870 )
v25 = 0x430CB33E2C4B0D78LL;
v24 = ((jmptable[7632] + 8 * (**v23 == v870) - 0x6962C35F3447EFD6LL);
v26 = ((jmptable[7545] + 0x1873E89F58DFBBD3LL))(v969, 2 * (v24 - v25 - (-(__int64)v25 | v24)));
v862 =((jmptable[16337] - 0x298C4B603A684D41LL))(*v26) + 16LL);
v27 = ((jmptable[16878] - 0x2B7C07156816338ALL))(v1008);
((jmptable[8507] - 0x1FB10E1EF8EC55E9LL))(*v27);
v28 = ((jmptable[4136] + 0x75B72F62482D4F4ALL))(v986);
v29 = ((jmptable[20038] - 0x16B95CF58EA2CA9CLL))(*v28);
v30 = ((jmptable[3020] + 0x164FBC31A75BA5EALL))(*v29);
*(*v30 + 48LL) = v872;
v855 = v1121;
v856 = *(v1121 + 120) == 0xBC42D5779FEC401LL;
v31 = ((jmptable[6403] - 0x40A76D8541CD8A6LL))(v1002);
v857 = 2802593464LL * ((jmptable[7643] - 0x6A75E0623D0CEDABLL))((*v31) + 16LL);
v859 = 4185932529LL;
v858 = ((2 * (v857 % 0xF98042F1 - ((v857 % 0xF98042F1) | 0xFFFFFFFF3D44433FuLL)) - 0x185777982LL) | (((v857 % 0xF98042F1) | 0xFFFFFFFF3D44433FuLL) - ((v857 % 0xF98042F1) & 0x3D44433F))) + ((2 * (v857 % 0xF98042F1 - ((v857 % 0xF98042F1) | 0xFFFFFFFF3D44433FuLL)) - 0x185777982LL) & (((v857 % 0xF98042F1) | 0xFFFFFFFF3D44433FuLL) - ((v857 % 0xF98042F1) & 0x3D44433F)));
v860 = v858 % 0xF98042F1;
v32 = ((jmptable[3849] - 0x24D3127B7B323583LL))(v1077);
*(*v32 + 16LL) = v860;
v33 = ((jmptable[16342] - 0x41BC169EC90600F8LL))(v956);
v34 = ((jmptable[15610] - 0x12B6523533755E53LL))(*v33);
v35 = ((jmptable[20650] + 0x4077405EA6064E8LL))(*v34);
v36 = ((jmptable[5809] - 0x5A6B210EDE3F57D4LL))(*v35);
v861 =((jmptable[10381] + 0x56B6C7B34FF33CB1LL))(*v36) + 16LL);
v37 = ((jmptable[18073] + 0x776630BB17FA788LL))(v1062);
v38 = ((jmptable[15280] + 0x14D2F36BDEBCF074LL))(*v37);
((jmptable[11592] + 0x281A6DC4F2A43C68LL))(*v38);
if ( v856 )
{
... 解密逻辑 ...
}
else
{
... 错误逻辑 ...
}
...
}

可以发现其中有一些关键数据(比如7499)和一些比较逻辑:

1
2
if ( **v23 != v870 )
v25 = 0x430CB33E2C4B0D78LL;

这里左侧v23是一个定值7499(0x1D4B),右侧是一个随机值(很大,低16位是全零或者1,并且有小概率整个qword都是某固定的14字节乱码的一部分),几乎不可能和左侧相等,而且其生成过程似乎无法在校验函数中定位相关逻辑,校验函数也存在其他方面的难以理解的问题,推断是ida反编译错误,分析汇编(从跳转点开始往上分析):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:1400021E09 mov     rcx, [rbp+2950h+var_2148]
.text:1400021E10 add rsp, 20h
.text:1400021E14 mov rax, [rax]
.text:1400021E17 mov [rax+30h], rcx
.text:1400021E1B mov rax, [rbp+2950h+var_3C8]
.text:1400021E22 mov [rbp+2950h+var_21D0], rax
.text:1400021E29 mov rax, [rax+78h]
.text:1400021E2D mov rcx, 0BC42D5779FEC401h
.text:1400021E37 sub rax, rcx
.text:1400021E3A setz al
.text:1400021E3D mov [rbp+2950h+var_21C1], al
...
.text:1400022115 mov al, [rbp+2950h+var_21C1]
.text:140002211B test al, 1
.text:140002211D jnz short loc_7FF7E4E92124
.text:140002211F jmp loc_7FF7E4E9795C

发现要让al = 1,则必有[[rbp+2950h+var_3C8]+0x78] == 0x0BC42D5779FEC401

查看这个位置的值,双击两次后将地址加上0x78发现了一个神秘的int64,应该就是要比较的位置,实测改完之后通过了校验(虽然解密失败),说明这里就是校验位,在这里打上硬件断点,动态调试发现每输入1位这里就被改写一次,大概在0x140016AD4附近被修改:

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
.text:1400015A47 input_pwd:
.text:1400015A47 call rax ; 输入字符串的地方
...
.text:1400015E85 mov rax, cs:jmptable+0C6F0h
.text:1400015E8C mov r8, 0A2B91DB25EE5355Dh
.text:1400015E96 add rax, r8
.text:1400015E99 call rax ; 0x1400081760
.text:1400015E9B mov rcx, [rbp+0FC0h+var_948]
.text:1400015EA2 mov [rbp+0FC0h+var_C00], rax
...
.text:1400016752 mov rax, cs:jmptable+23D60h
.text:1400016759 mov r8, 64ED705730BC6591h
.text:1400016763 add rax, r8
.text:1400016766 call rax ; 0x1400081760
.text:1400016768 mov rcx, rax
.text:140001676B mov rax, [rbp+0FC0h+var_C00]
.text:1400016772 imul rax, rcx
.text:1400016776 mov [rbp+0FC0h+var_C78], rax
...
.text:1400016AC2 mov rax, [rbp+0FC0h+var_948] ; 结构体
.text:1400016AC9 mov r9, [rbp+0FC0h+var_C78] ; 来源值
.text:1400016AD0 mov rdx, [rax+78h] ; tag值
.text:1400016AD4 mov rcx, r9 ; 接下来整个是一个add
.text:1400016AD7 not rcx
.text:1400016ADA mov r8, rdx
.text:1400016ADD not r8
.text:1400016AE0 or r8, rcx
.text:1400016AE3 mov rcx, rdx
.text:1400016AE6 add rcx, r9
.text:1400016AE9 lea r8, [r8+rcx+1]
.text:1400016AEE or rdx, r9
.text:1400016AF1 sub rcx, rdx
.text:1400016AF4 mov rdx, rcx
.text:1400016AF7 or rdx, r8
.text:1400016AFA and rcx, r8
.text:1400016AFD add rcx, rdx
.text:1400016B00 mov [rax+78h], rcx ; 写回tag

发现tag的值是来源于每一位分两次带入函数0x1400081760的返回值相乘之后相加的总和,一共有25位 * 10种 = 250个数据

提取250个乘积数据后,编写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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from z3 import *

TARGET = 0x0BC42D5779FEC401
MASK64 = (1 << 64) - 1

matrix_hex = [
[0x19B3240445AA06,0xF2EB6684284AC,0xC38D14DF6D665,0x1D3A557CD3A980,0x26D8E6DC23319D,0x1C544F24D1B429,0x17F846E0621293,0x1B6E4030F71F3D,0x80F42A7139E80,0x1FCAC56F82739C],
[0x6F63394844DF78,0x3ED168087F3548,0x6391D7049DDE8,0x8114813C91A610,0xA8FA7B20F1E228,0x786666BE83B978,0x6AE188A0BC46F0,0x6FB9A153C78328,0x1B18D762CB44C8,0x921C4279B1A9A8],
[0x6DF6A4586E71C0,0x49DE34FFC63EC0,0x39FEE368E99380,0x7A11DE14C79E80,0xD8DC90E996E40,0x7146BFC66E2740,0x680673DB489E00,0x6E31309B7B8940,0x316C3AFCB92AC0,0x82DEEFE21A73C0],
[0x4EA15FC542C9C0,0x1CA2F34F18CD40,0xA614B2DC1C6980,0x60D84A48824900,0x9280B83FADD240,0x60801A9A7374C0,0x49FE057966CC00,0x579189ABC15440,0xB2F907A133B2C0,0x612F00F6EA6080],
[0x3AC57453ACE252,0x6229B366FE169,0xACB3FF351198AB,0x4C6C9CDBCE1FE5,0x7C9128173EA742,0x47ED36357FF53C,0x321B2C8DECC760,0x436E1CAD683194,0x97DE278C5E1226,0x489730C9AA7E6A],
[0x6402164C9FDB19,0x483F192B06217F,0x1E5CB35F54FA69,0x6E1E405C548974,0x137E71B08CA2AA,0x6928A14A143271,0x616EB297EDDB28,0x6431716B60DF88,0x2A4A823D6D822D,0x6E4F38A8434132],
[0x69B5253875B96,0x2BE31F155AB714,0x21AE09901BF552,0xB221597F1D3D8,0x1556DDAA385C92,0x7D821185844EC,0x462D157005089,0x6B104399CEDBC,0x1E79B0A36CDAFF,0xA266DD02D7453],
[0x9C0D47EAC35D2D,0x6FD7CB84FD4AD7,0x6255B824338303,0xAC26E207A0A6C5,0x23700DD18B0E3C,0xABD8D1A448644C,0x97F36052CAA668,0xA3F30CB6971D36,0x4F56BF7AFE673B,0x708E71FCEE988],
[0x30B9DA3C1BFE7,0x16F0557C6F1B97,0x105A5256455ED6,0x575F94A1E493B,0xC0C1389DB1C28,0x4D8773736490A,0x1DC3A46D3EB22,0x43AFA29A5046E,0x12101A50F4E1FB,0x737572378FC07],
[0x3A03C1D1D02F29,0x255E081F63BA07,0x1B8260C83DD73C,0x4188E0049C9EBA,0x527EEC073C111B,0x3DD8DF72E747E1,0x38195731A5D0AE,0x3A28381B02C813,0x162F7A6E9D784F,0x48C67301DAFECB],
[0x1D392355DF459C,0xBC35FAA41240,0x4D5BA7B7C6DB28,0x26C6DD80773E62,0x3C52C884071124,0x1FD5838C6C7F50,0x188898B9E96D66,0x1D66839EF1A1C0,0x58A12D4900A2FA,0x2DB81C4AE88D20],
[0x8484A22A795E4,0x5EB45F3E513AC,0x46247570894A4,0x924CE94DF0690,0x1D5530EA52210,0x920A24B9448D0,0x81027F2A79420,0x8B4887801A9FC,0x35E3DA634FB94,0x928D4D1040ED8],
[0xBE331DD3107AD,0x5E391DD240E89D,0x39874C7FFB294C,0x15E2AB5E23C478,0x312496AE1C4433,0x1356D94E3C824F,0x6FB9313A4228E,0x10CAFC6DD92AE3,0x409BC32BBA4E37,0x13B66FDE55B77C],
[0x19C7C11DA4E4A2,0x7FA2B9A827FAE,0x42193CC5270058,0x2043847B9EEDD8,0x2EE265CBAEA1AE,0x1D1555557808F2,0x1820C6CA831F20,0x19E699125F8740,0x3D81E379F6B82A,0x2062F49DA45AB4],
[0x1796E76685E997,0x5DC0C3E3261CDF,0x3A76362613DD95,0x201BB9EA8ED81C,0x33509B969E3741,0x19EB1C9C94E6A8,0x1368E07F2E794F,0x17BFD098C23630,0x448303CFD4DD31,0x1E41E65B7AFC35],
[0x9BDC1F78073127,0x75583891352145,0x4F18252739A5AC,0xC857C28BE5198,0x32C55970EA1358,0xC421F0A303C70,0x984973478DC5AC,0x56017C15FBDD1,0x5906717CF6C571,0x1A062A307BCABD],
[0xCCE53B2DF56140,0x926EC5880F9992,0x81FA523E38B793,0x481AF6CCB653B,0x39FC6F33CB770B,0xDB8605E2F4AA0E,0xC34699DB257145,0xD6872C5CC11542,0x6AD52B4C617CF3,0x12C1CA7EAE79A6],
[0x1DC6931C286DB2,0xED19AD19480BE,0x3A82193F6EF346,0x23394341031AC0,0x2F828795A6E6BE,0x208D625A5FD472,0x1C635AA6DF8398,0x1DE0F95CF827AE,0x3D2149D449636,0x28782F9453EFDE],
[0x139D946E9D6D82,0xB0E0C55A4D6238,0x97D9725BE8876E,0x26B598F9022C30,0x51C2FA15841D7E,0x18D5FBE4CDA4C8,0xA3F26DF601552,0x13F8DFF2FE8408,0x8A5498F0183BB0,0x3496095EF45282],
[0x72A31CFDE71EF6,0x46A5661935D9CA,0xBD4C34161B102,0x82A99A5DE4C84F,0xAE5A988393338F,0x825E0AE4D3D5F5,0x6E8EA108204593,0x7A7FE273C35A4C,0x172C91B106B2ED,0x82F69B85B1A2A1],
[0x40A5DB3578D586,0x22F572E6839826,0x1133B875BED53A,0x4A9B4A38CF1460,0x65C33E4F5A2FC0,0x481234127842A2,0x3BC3C87F8C0454,0x4588CE32DCF25A,0x573D341F00D72,0x48724D79B38078],
[0xC427156A9E2860,0x88B763CB6FB9F0,0x2F08D6E954E096,0xD9CCF65D9CD7A4,0x17C3E165050BF0,0xCF2B002B1AD140,0xBEA37CEF15E1FA,0xC48CEE7BAE850C,0x489420271C9606,0xDA31DC2C7FDBCE],
[0x537869C92A42D0,0x1DA1D095B7C0B4,0xBF7B1F10223116,0x6586F47FC6CF52,0x8E40676E4927E0,0x58692F2E100A24,0x4A9CA491835636,0x53CFDDBDD2F61C,0xB2B5098A832538,0x619C35508AEABA],
[0x8CC856E432BC50,0x62360169143A78,0x55333012D9DD23,0x9C4964E3F15005,0x18A07DDC589B2C,0x9BFE0FEDBF05DC,0x88D54339CF6CF1,0x9463624AACA2C6,0x42E7AF41E9BD7C,0xAB36B7C0777858],
[0x20CCD008AD41A,0x3684BCD9A0F789,0x2525F943737B70,0x86BB1A4A4AA7D,0x19CA34ADEE1B3A,0x6CC51DF5A1F44,0x4661D5224E5470,0x52CED44ED58CC,0x29A7CADA9C4CB1,0xD0CB5244CA7FD]
]

matrix = [[int(x) for x in row] for row in matrix_hex]
solver = Solver()
choices = [Int(f'c{i}') for i in range(25)]
for i in range(25):
solver.add(choices[i] >= 0)
solver.add(choices[i] <= 9)
sum_expr = BitVecVal(0, 64)
for i in range(25):
val = BitVecVal(matrix[i][0], 64)
for j in range(1, 10): val = If(choices[i] == j, BitVecVal(matrix[i][j], 64), val)
sum_expr = sum_expr + val
solver.add(sum_expr == TARGET)
print("Solving...")
if solver.check() == sat:
model = solver.model()
result_indices = []
total = 0
for i in range(25):
idx = model[choices[i]].as_long()
result_indices.append(idx)
val = matrix[i][idx]
total += val
print(f"Row {i}: selected index {idx}, value = 0x{val:X}")
print(f"\nTotal sum = 0x{total & MASK64:X}")
print(f"Target = 0x{TARGET:X}")
print("Match!" if (total & MASK64) == TARGET else "Mismatch!")
else:
print("No solution found.")

找到解:4498291314891210521449296

s0m3t1mes_1t_do3s_not_m4ke_any_s3n5e@flare-on.com

#9 10000

#9.1 主程序初始分析

程序中存在1GB的数据,分析主函数:

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
58
59
60
61
62
63
64
65
66
67
68
69
int __fastcall main(int argc, const char **argv, const char **envp)
{
main_0(argc, argv, envp);
string = load_string(buffer, "checking license file...");
((void (__fastcall *)(__int64))print_string)(string);
v4 = or(4, 2);
open_file((__int64)v17, (__int64)"license.bin", v4);
read_file((__int64)v18, v17);
Size = load_size((__int64)v18);
if ( Size == 340000 )
{
std::istream::seekg(v17, 0, 0);
file = operator_new(Size);
std::istream::read(v17, file, Size);
SHA_256((__int64)file, (__int64)file + 340000, (__int64)hash, (__int64)v17, 0x100000);
*(_QWORD *)&n[1] = file;
v23 = v19;
init_vector_int(v15, 10000, (__int64)v19);
for ( n[0] = 0; n[0] <= 9999; ++n[0] )
{
i = **(_WORD **)&n[1];
if ( i > 9999u )
goto FAIL;
set_vector_size((__int64)v19 + 1, (__int64 *)v15, i);
if ( is_zero((__int64)v19 + 1) )
goto FAIL;
set_vector_size((__int64)v20, (__int64 *)v15, i);
or_with((__int64)v20, 1);
*(_QWORD *)&n[1] += 2LL;
v24 = LOAD_DLL(i);
v7 = (__int64)(v24 + 1);
*(_QWORD *)&v22[1] = v22;
find_function((__int64)v21, (__int64)"_Z5checkPh", (__int64)v22);
check = (__int64 (__fastcall **)(_QWORD))get_addr_of_check(v7, (__int64)v21);
LODWORD(v7) = (*check)(*(_QWORD *)&n[1]) ^ 1;
next_index((__int64)v21);
if ( (_BYTE)v7 )
goto FAIL;
*(_QWORD *)&n[1] += 32LL;
free_obj(n[0]);
}
if ( memcmp(Output, Target, 40000u) )
{
FAIL:
v9 = load_string(buffer, "invalid license file");
((void (__fastcall *)(__int64))print_string)(v9);
v6 = 1;
goto EXIT;
}
v10 = load_string(buffer, "license valid!");
((void (__fastcall *)(__int64))print_string)(v10);
memset(&Src[4], 0, 80);
LODWORD(Size_1) = 80;
AES_256((__int64)&enc, 0x50u, (__int64)hash, 0x20u, (__int64)&iv, &Src[4], Size_1, Src);
v11 = load_string_((__int64 *)buffer, &Src[4]);
((void (__fastcall *)(__int64 *))print_string)(v11);
v6 = 0;
EXIT:
clear((__int64)v15);
}
else
{
v5 = load_string(buffer, "invalid license file");
((void (__fastcall *)(__int64))print_string)(v5);
v6 = 1;
}
std::ifstream::~ifstream(v17);
return v6;
}

程序首先获取目标license.bin,读到34万字节后,每34字节带入检验,前2字节作为dll的索引,后32字节作为输入,过了dll内check函数的校验之后还需要过外层memcmp校验(因为有顺序问题),查看负责加载程序中的10000个校验dll的函数:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
_OWORD *__fastcall LOAD_DLL(__int64 idx)
{
idx_ = idx;
idx__ = (unsigned __int16)idx;
v2 = sub_7FF6797BF750((__int64)&unk_7FF679822C80);
v3 = sub_7FF6797BF780((__int64)&unk_7FF679822C80);
v12 = sub_7FF6797123B1(v3, v2, idx__);
v13 = sub_7FF6797BF750((__int64)&unk_7FF679822C80);
if ( !sub_7FF679731500((__int64)&v12, (__int64)&v13) )
return *(_OWORD **)sub_7FF6797330F0((__int64)&v12);
v5 = sub_7FF6797D9F00(0x40u);
*v5 = 0;
v5[1] = 0;
v5[2] = 0;
v5[3] = 0;
sub_7FF67972D670((__int64)v5);
v11 = v5;
*(_WORD *)v5 = idx_;
hResInfo = FindResourceA(0, (LPCSTR)idx_, (LPCSTR)0xA);
hResData = LoadResource(0, hResInfo);
*(_QWORD *)&zipfile[1] = LockResource(hResData);
zipfile[0] = SizeofResource(0, hResInfo);
decompress_size = get_decompress_size(*(unsigned __int8 **)&zipfile[1], zipfile[0], 0);
sub_7FF6797D0C30((__int64)v10, decompress_size);
dllfile = sub_7FF6797397D0(v10, 0);
LZSS_decompress(*(unsigned __int8 **)&zipfile[1], dllfile, zipfile[0], decompress_size, 0);
v40 = *(int *)(dllfile + 60) + dllfile;
dllfile_1 = (char *)VirtualAlloc(0, *(unsigned int *)(v40 + 80), 0x3000u, 0x40u);
*((_QWORD *)v11 + 1) = dllfile_1;
v52 = (unsigned int *)(v40 + 24 + *(unsigned __int16 *)(v40 + 20));
for ( i = 0; i < *(unsigned __int16 *)(v40 + 6); ++i )
{
memcpy(&dllfile_1[v52[3]], (const void *)(dllfile + v52[5]), v52[4]);
v52 += 10;
}
for ( j = &dllfile_1[*(unsigned int *)(v40 + 144)]; *((_DWORD *)j + 3); j += 20 )
{
name_of_dll = &dllfile_1[*((unsigned int *)j + 3)];
v49 = (FARPROC *)&dllfile_1[*((unsigned int *)j + 4)];
if ( (unsigned int)(*name_of_dll - 48) > 9
|| (unsigned int)(name_of_dll[1] - 48) > 9
|| (unsigned int)(name_of_dll[2] - 48) > 9
|| (unsigned int)(name_of_dll[3] - 48) > 9 )
{
hModule = LoadLibraryA(name_of_dll);
while ( *v49 )
{
v19 = &dllfile_1[(_QWORD)*v49];
ProcAddress = GetProcAddress(hModule, v19 + 2);
*v49++ = ProcAddress;
}
}
else
{
n0x270F_3 = atoi(name_of_dll);
v23 = LOAD_DLL(n0x270F_3);
while ( *v49 )
{
v22 = &dllfile_1[(_QWORD)*v49];
v6 = (__int64)(v23 + 1);
v17 = v15;
find_function((__int64)v14, (__int64)(v22 + 2), (__int64)v15);
v21 = *(INT_PTR (__stdcall **)())get_addr_of_check(v6, (__int64)v14);
next_index((__int64)v14);
*v49++ = v21;
}
}
}
v38 = &dllfile_1[*(unsigned int *)(v40 + 136)];
v37 = &dllfile_1[*((unsigned int *)v38 + 8)];
v36 = &dllfile_1[*((unsigned int *)v38 + 9)];
v35 = &dllfile_1[*((unsigned int *)v38 + 7)];
for ( k = 0; k < *((_DWORD *)v38 + 6); ++k )
{
p__Z5checkPh = &dllfile_1[*(unsigned int *)&v37[4 * k]];
v27 = *(_WORD *)&v36[2 * k];
v26 = &dllfile_1[*(unsigned int *)&v35[4 * v27]];
v7 = v26;
v8 = (__int64)(v11 + 1);
*(_QWORD *)&v16[1] = v16;
find_function((__int64)v15 + 1, (__int64)p__Z5checkPh, (__int64)v16);
*(_QWORD *)get_addr_of_check(v8, (__int64)v15 + 1) = v7;
next_index((__int64)v15 + 1);
}
v34 = &dllfile_1[*(unsigned int *)(v40 + 176)];
v33 = *(_DWORD *)(v40 + 180);
v47 = (unsigned int *)v34;
v32 = &dllfile_1[-*(_QWORD *)(v40 + 48)];
while ( v47 < (unsigned int *)&v34[v33] )
{
for ( m = v47 + 2; m < (unsigned int *)((char *)v47 + v47[1]); m = (unsigned int *)((char *)m + 2) )
{
v29 = &dllfile_1[(*(_WORD *)m & 0xFFF) + (unsigned __int64)*v47];
switch ( *((_BYTE *)m + 1) & 0xF0 )
{
case 160:
*(_QWORD *)v29 += v32;
break;
case 48:
*(_QWORD *)v29 += (unsigned int)v32;
break;
case 16:
*(_QWORD *)v29 += WORD1(v32);
break;
case 32:
*(_QWORD *)v29 += (unsigned __int16)v32;
break;
}
}
v47 = m;
}
v31 = &dllfile_1[*(unsigned int *)(v40 + 40)];
v30 = ((__int64 (__fastcall *)(_DWORD *, __int64, _QWORD))v31)(Output, 1, 0);
sub_7FF6797BF7E0((__int64)&unk_7FF679822C80, (__int64)&v11);
v4 = v11;
sub_7FF679796660(v10);
return v4;
}

共计有10000个dll(0000~9999),以某种自定义格式压缩,每次运行时会递归解压缩所有主dll所需的dll并缓存在内存中,之后使用时直接调用,解压完毕后程序会查找主dll内的check函数,找到后用其检验license中的32字节,10000个全部通过且Output等于Target中的10000个dword即用dll的sha256作为AES256密钥解密密文并输出flag

#9.2 提取dll数据并解压

编写脚本提取所有dll:

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
import pefile
import mmap
import struct

pe = pefile.PE("10000.exe", fast_load=True)
rsrc_rva = pe.OPTIONAL_HEADER.DATA_DIRECTORY[2].VirtualAddress
rsrc_offset = pe.get_offset_from_rva(rsrc_rva)

with open("10000.exe", "rb") as f:
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
def read_dir_entry(offset):
data = mm[offset:offset+8]
name_id, offset_to_data = struct.unpack("<II", data)
return name_id, offset_to_data
def read_dir_header(offset):
data = mm[offset:offset+16] (Characteristics, TimeDateStamp, MajorVersion, MinorVersion, NumNamed, NumId) = struct.unpack("<IIHHHH", data)
return NumNamed, NumId
num_named, num_id = read_dir_header(rsrc_offset)
entries_offset = rsrc_offset + 16
for i in range(num_named + num_id):
entry_offset = entries_offset + i * 8
name_id, offset_to_data = read_dir_entry(entry_offset)
is_dir = offset_to_data & 0x80000000
offset_to_data &= 0x7FFFFFFF
type_id = name_id & 0xFFFF
if type_id == 0xA:
type_dir_offset = rsrc_offset + offset_to_data
break
num_named, num_id = read_dir_header(type_dir_offset)
entries_offset = type_dir_offset + 16
for i in range(num_named + num_id):
entry_offset = entries_offset + i * 8
name_id, offset_to_data = read_dir_entry(entry_offset)
dll_id = name_id & 0xFFFF
is_dir = offset_to_data & 0x80000000
offset_to_data &= 0x7FFFFFFF
lang_dir_offset = rsrc_offset + offset_to_data
num_named_lang, num_id_lang = read_dir_header(lang_dir_offset)
lang_entry_offset = lang_dir_offset + 16
_, data_entry_offset_raw = read_dir_entry(lang_entry_offset)
data_entry_offset = rsrc_offset + (data_entry_offset_raw & 0x7FFFFFFF)
data_rva, data_size, codepage, reserved = struct.unpack("<IIII", mm[data_entry_offset:data_entry_offset+16])
data_offset = pe.get_offset_from_rva(data_rva)
print(f"ID={dll_id}, RVA={hex(data_rva)}, size={data_size}, file_offset={hex(data_offset)}")
data = mm[data_offset:data_offset+data_size]
open(f"bins/dll_{dll_id}.bin", "wb").write(data)

提取后同构两个函数并解压所有dll,由于原解压函数十分复杂,所以可以直接粘贴ida的反编译代码,发现计算长度的函数不用改就能用,但是解压缩的函数会报错,所以可以采用直接粘贴字节码的方式:

1
2
3
4
5
6
7
8
code = bytes.fromhex("")
print("asm volatile (")
for i in range(len(code)):
print(f" \".byte {hex(code[i])}\\n\\t\"")
print(''' :
:
: "eax", "cc"
);''')

主程序:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
__attribute__((naked)) long long LZSS_decompress(
unsigned char *zipfile,
unsigned char *dllfile,
long long compressed_size,
long long decompress_size,
long long offset)
{
asm volatile (
".byte 0x55\n\t"
".byte 0x48\n\t"
".byte 0x81\n\t"
...
".byte 0x0\n\t"
".byte 0x5d\n\t"
".byte 0xc3\n\t"
:
:
: "eax", "cc"
);
}

void create_dir(const char *path) {
_mkdir(path);
}

int main() {
const char *input_dir = "bins";
const char *output_dir = "dlls";
create_dir(output_dir);
char search_path[256];
snprintf(search_path, sizeof(search_path), "%s\\dll_*.bin", input_dir);
struct _finddata_t file_info;
intptr_t handle = _findfirst(search_path, &file_info);
if (handle == -1) {
printf("No dll_*.bin files found in %s/\n", input_dir);
return 1;
}
char mismatch_list[1024] = {0};
do {
if (file_info.attrib & _A_SUBDIR)
continue;
char *start = strstr(file_info.name, "dll_");
if (!start) continue;
start += 4;
char *end = strstr(start, ".bin");
if (!end) continue;
char num_str[32] = {0};
strncpy(num_str, start, end - start);
num_str[end - start] = '\0';
int idx = atoi(num_str);
if (idx < 0) continue;
char input_path[256];
snprintf(input_path, sizeof(input_path), "%s\\%s", input_dir, file_info.name);
FILE *fp = fopen(input_path, "rb");
if (!fp) {
printf("Failed to open %s\n", input_path);
continue;
}
fseek(fp, 0, SEEK_END);
long long file_size = ftell(fp);
rewind(fp);
unsigned char *buffer = (unsigned char *)malloc(file_size);
if (!buffer) {
fclose(fp);
printf("Malloc failed for %s\n", input_path);
continue;
}
size_t read_bytes = fread(buffer, 1, file_size, fp);
fclose(fp);
if (read_bytes != (size_t)file_size) {
free(buffer);
printf("Read error for %s\n", input_path);
continue;
}
unsigned long long expected_size = get_decompress_size(buffer, file_size, 0);
printf("Processing %s -> expected size: 0x%llx\n", file_info.name, expected_size);
unsigned char *output = (unsigned char *)malloc(expected_size + 4096);
if (!output) {
free(buffer);
printf("Output malloc failed for %s\n", input_path);
continue;
}
long long actual_size = LZSS_decompress(buffer, output, file_size, expected_size, 0);
if (actual_size < 0) {
printf("Decompression failed for %s (error code %lld)\n", file_info.name, actual_size);
snprintf(mismatch_list + strlen(mismatch_list), sizeof(mismatch_list) - strlen(mismatch_list),
"%s (decompress error)\n", file_info.name);
} else if ((unsigned long long)actual_size != expected_size) {
printf("Size mismatch for %s: expected=0x%llx, actual=0x%llx\n",
file_info.name, expected_size, (unsigned long long)actual_size);
snprintf(mismatch_list + strlen(mismatch_list), sizeof(mismatch_list) - strlen(mismatch_list),
"%s\n", file_info.name);
} else {
char output_name[256];
snprintf(output_name, sizeof(output_name), "%s\\%04d.dll", output_dir, idx);
FILE *out_fp = fopen(output_name, "wb");
if (out_fp) {
fwrite(output, 1, expected_size, out_fp);
fclose(out_fp);
printf(" -> Written to %s\n", output_name);
} else {
printf("Failed to write %s\n", output_name);
}
}
free(buffer);
free(output);
} while (_findnext(handle, &file_info) == 0);
_findclose(handle);
if (strlen(mismatch_list) > 0) {
printf("\n=== Files with size mismatch or errors ===\n");
printf("%s", mismatch_list);
} else {
printf("\nAll files decompressed successfully with matching sizes.\n");
}
return 0;
}

提取后得到10000个dll

#9.3 初步分析dll check函数模式 && 自动化提取

发现其中每个dll均有数百个名为fxxxxxxxxxxxxxx的单个校验函数导出,check函数均遵循格式:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
_BOOL8 __fastcall check(unsigned __int8 *a1, __m128 a2, __int64 a3, __int64 a4, __int64 a5)
{
数百个fxxxxxxxxxxxxxxx混合,有的来自别的dll(a1);
22行密文,其中第46810行是0;
for ( n15 = 0; n15 <= 15; ++n15 )
{
v5 = v98[2 * n15 + 121];
v6 = &v98[2 * n15 + 120];
*v6 ^= *(_QWORD *)(8LL * (n15 % 4) + a5);
v6[1] = v5;
if ( *(_OWORD *)&v98[2 * n15 + 120] >= v123 )
return 0;
}
一些xmm验证函数的调用;
if ( *(_OWORD *)&a2 == 0 )
return 0;
Size[0] = 1;
memset(&Size[1], 0, 72);
Size[10] = 1;
memset(&Size[11], 0, 72);
Size[20] = 1;
memset(&Size[21], 0, 72);
Size[30] = 1;
Size[31] = 0;
memset(&v98[24], 0, 512);
for ( n15_1 = 0; n15_1 <= 15; ++n15_1 )
{
v44 = v98[2 * n15_1 + 121];
v45 = &v98[2 * n15_1 + 56];
*v45 = v98[2 * n15_1 + 120];
v45[1] = v44;
}
for ( n63 = 0; n63 <= 63; ++n63 )
{
if ( (v122 >> n63) & 1 )
{
for ( n3_2 = 0; n3_2 <= 3; ++n3_2 )
{
for ( n3_1 = 0; n3_1 <= 3; ++n3_1 )
{
v46 = &v98[8 * n3_2 + 24 + 2 * n3_1];
*v46 = 0;
v46[1] = 0;
n3 = 0;
while ( n3 <= 3 )
{
v47 = v98[8 * n3_2 + 24 + 2 * n3_1];
v48 = v98[8 * n3_2 + 25 + 2 * n3_1];
v49 = v98[8 * n3_2 + 88 + 2 * n3];
v50 = v98[8 * n3 + 56 + 2 * n3_1];
v51 = v50 * v98[8 * n3_2 + 89 + 2 * n3] + v49 * v98[8 * n3 + 57 + 2 * n3_1];
v52 = v50 * (unsigned __int128)v49;
*((_QWORD *)&v52 + 1) += v51;
v89 = v52;
v88 = v123;
*(double *)a2.m128_u64 = sub_3258D0990(v48, v47, &v88, &v89);
*(_QWORD *)&v53 = v47;
*((_QWORD *)&v53 + 1) = v48;
v54 = 16LL * (4 * n3_2 + n3_1) + 1328;
*(_OWORD *)((char *)&v98[-142] + v54) = *(_OWORD *)&a2 + v53;
v55 = (unsigned int)(4 * n3_2 + n3_1);
v89 = *(_OWORD *)&v98[8 * n3_2 + 24 + 2 * n3_1];
v88 = v123;
*(double *)a2.m128_u64 = sub_3258D0990(v54, v55, &v88, &v89);
*(__m128 *)&v98[2 * (int)v55 + 24] = a2;
++n3;
Buf2 = __PAIR128__(v98, v55);
}
}
}
for ( n15_2 = 0; n15_2 <= 15; ++n15_2 )
{
v56 = v98[2 * n15_2 + 25];
v57 = 16LL * n15_2 + 1328;
v58 = (_QWORD *)((char *)&v98[-78] + v57);
*v58 = *(_QWORD *)((char *)&v98[-142] + v57);
v58[1] = v56;
}
}
for ( n3_5 = 0; n3_5 <= 3; ++n3_5 )
{
for ( n3_4 = 0; n3_4 <= 3; ++n3_4 )
{
v59 = &v98[8 * n3_5 + 24 + 2 * n3_4];
*v59 = 0;
v59[1] = 0;
n3_3 = 0;
while ( n3_3 <= 3 )
{
v60 = v98[8 * n3_5 + 24 + 2 * n3_4];
v61 = v98[8 * n3_5 + 25 + 2 * n3_4];
v62 = v98[8 * n3_5 + 56 + 2 * n3_3];
v63 = v98[8 * n3_3 + 56 + 2 * n3_4];
v64 = v63 * v98[8 * n3_5 + 57 + 2 * n3_3] + v62 * v98[8 * n3_3 + 57 + 2 * n3_4];
v65 = v63 * (unsigned __int128)v62;
*((_QWORD *)&v65 + 1) += v64;
v89 = v65;
v88 = v123;
*(double *)a2.m128_u64 = sub_3258D0990(v61, v60, &v88, &v89);
*(_QWORD *)&v66 = v60;
*((_QWORD *)&v66 + 1) = v61;
v67 = 16LL * (4 * n3_5 + n3_4) + 1328;
*(_OWORD *)((char *)&v98[-142] + v67) = *(_OWORD *)&a2 + v66;
v68 = (unsigned int)(4 * n3_5 + n3_4);
v89 = *(_OWORD *)&v98[8 * n3_5 + 24 + 2 * n3_4];
v88 = v123;
*(double *)a2.m128_u64 = sub_3258D0990(v67, v68, &v88, &v89);
*(__m128 *)&v98[2 * (int)v68 + 24] = a2;
++n3_3;
Buf2 = __PAIR128__(v98, v68);
}
}
}
for ( n15_3 = 0; n15_3 <= 15; ++n15_3 )
{
v69 = v98[2 * n15_3 + 25];
v70 = 16LL * n15_3 + 1328;
v71 = (_QWORD *)((char *)&v98[-110] + v70);
*v71 = *(_QWORD *)((char *)&v98[-142] + v70);
v71[1] = v69;
}
}
一些数据
return memcmp(*((const void **)&Buf2 + 1), (const void *)Buf2, (size_t)Size) == 0;
}

通过外部调用的fxxxxxxxx函数名称,发现dll名字顺序与资源顺序相同,编写脚本提取所有dll的导出表:

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
import os
import pefile
import sqlite3
import json

DLL_FOLDER = r"dlls/"
DB_FILE = "exported_functions.db"

def get_f_exported_functions(dll_path):
try:
pe = pefile.PE(dll_path)
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
funcs = []
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
if exp.name:
try:
name = exp.name.decode('utf-8')
if name.startswith('_Z21f'):
funcs.append(name)
except UnicodeDecodeError:
continue
return sorted(funcs)
return []
except Exception:
return []

def main():
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS dll_exports (
id INTEGER PRIMARY KEY,
exported_functions TEXT NOT NULL
)
''')
conn.commit()

for i in range(10000):
print(f"Processing {i:04d}")
dll_path = os.path.join(DLL_FOLDER, f"{i:04d}.dll")
funcs = get_f_exported_functions(dll_path) if os.path.isfile(dll_path) else []
funcs_json = json.dumps(funcs, ensure_ascii=False)
cursor.execute(
"INSERT OR REPLACE INTO dll_exports (id, exported_functions) VALUES (?, ?)",
(i, funcs_json)
)
if i % 100 == 0: conn.commit()
conn.commit()
conn.close()
print(f"Data saved to {DB_FILE}")

if __name__ == "__main__":
main()

再编写能提取dll的函数调用顺序的脚本:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import r2pipe
import sys
import os
import time
import traceback
import json
from capstone import *
from capstone.x86 import *
import re

BACKTRACK_MAX = 16

def log(msg, fh=None):
t = time.strftime("%Y-%m-%d %H:%M:%S")
line = f"[{t}] {msg}"
if fh: fh.write(line + "\n")

def try_read_qword(r, addr):
try:
s = r.cmd(f"pv8 @ {hex(addr)}").strip()
if not s: return None
return s
except Exception:
return None

def try_read_ascii(r, addr):
try:
return r.cmd(f"ps @ {hex(addr)}").strip()
except Exception:
return None

def build_iat_map(r, debug_fh):
iat_map = {}
sections = r.cmdj("iSj")
idata = next((s for s in sections if s["name"] == ".idata"), None)
if not idata:
log("[!] .idata not found", debug_fh)
return iat_map
base = r.cmdj("ij")["bin"]["baddr"]
start = idata["vaddr"]
size = idata["size"]
log(f"[IAT] .idata VA start={hex(start)}, size={hex(size)}, image_base={hex(base)}", debug_fh)
for off in range(0, size, 8):
slot_va = start + off
raw = try_read_qword(r, slot_va)
if not raw: continue
try:
val = int(raw, 16)
except Exception: continue
if val == 0: continue
candidates = [base + (val & 0xffffffff) + 2, val + 2, (val & 0xffffffff) + 2, val]
resolved_name = None
for cand in candidates:
s = try_read_ascii(r, cand)
if not s: continue
if "_Z21f" in s and "Ph" in s:
idx = s.find("_Z21f")
sub = s[idx:]
end_idx = sub.find("Ph")
if end_idx != -1:
candidate_sub = sub[:end_idx+2]
if candidate_sub.startswith("_Z21f") and candidate_sub.endswith("Ph"):
resolved_name = candidate_sub
break
if resolved_name: iat_map[slot_va] = resolved_name
else: iat_map[slot_va] = None
return iat_map

def is_mem_rip_relative(mem, insn):
try:
if mem.base == X86_REG_RIP: return True
except Exception: pass
try:
if mem.base == 0 and 'rip' in insn.op_str: return True
except Exception: pass
return False

def find_memory_source_for_register(insn_list, call_idx, target_reg_name, max_back=BACKTRACK_MAX):
reg = target_reg_name
for j in range(call_idx - 1, max(call_idx - 1 - max_back, -1), -1):
insn = insn_list[j]
mnemonic = insn.mnemonic
ops = insn.operands if hasattr(insn, 'operands') else []
if mnemonic.startswith("mov") and len(ops) == 2:
dst = ops[0]
src = ops[1]
if dst.type == CS_OP_REG and insn.reg_name(dst.reg) == reg:
if src.type == CS_OP_MEM: return (src.mem, insn, j)
if src.type == CS_OP_REG:
reg = insn.reg_name(src.reg)
continue
return None
if mnemonic == "lea" and len(ops) == 2:
dst = ops[0]
src = ops[1]
if dst.type == CS_OP_REG and insn.reg_name(dst.reg) == reg and src.type == CS_OP_MEM: return (src.mem, insn, j)
if len(ops) >= 1 and ops[0].type == CS_OP_REG and insn.reg_name(ops[0].reg) == reg:
if not (mnemonic.startswith("mov") or mnemonic == "lea"): return None
return None

def disasm_and_extract_calls(r, check, va_to_num, iat_map, debug_fh, unresolved_fh):
call_order = []
try:
code_hex = r.cmd(f"p8 {check['size']} @ {check['addr']}").strip()
code_bytes = bytes.fromhex(code_hex)
except Exception as e:
log(f"[ERROR] failed to read check bytes: {e}", debug_fh)
return call_order, []
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
insn_list = list(md.disasm(code_bytes, check["addr"]))
for idx, insn in enumerate(insn_list):
if insn.mnemonic != "call": continue
func_id = None
if len(insn.operands) == 1 and insn.operands[0].type == CS_OP_IMM:
target = insn.operands[0].imm
func_id = va_to_num.get(target)
if not func_id:
try:
sym = r.cmd(f"afn @ {hex(target)}").strip()
func_id = sym if sym else f"sub_{target:x}"
except Exception:
func_id = f"sub_{target:x}"
elif len(insn.operands) == 1 and insn.operands[0].type == CS_OP_REG:
regname = insn.reg_name(insn.operands[0].reg)
if idx > 0:
prev_insn = insn_list[idx - 1]
prev_text = f"{prev_insn.mnemonic} {prev_insn.op_str}".strip()
import re
if prev_text.startswith(f"mov {regname}, qword ptr [rip +"):
m = re.search(r"\[rip \+ (0x[0-9a-fA-F]+)\]", prev_text)
if m:
disp_val = int(m.group(1), 16)
slot_guess = (insn.address + disp_val) & 0xffffffffffffffff
func_name = iat_map.get(slot_guess)
if func_name: func_id = func_name
if func_id: call_order.append(func_id)
else:
ctx_start = max(0, idx - 4)
ctx_end = min(len(insn_list), idx + 4)
ctx = "\n".join(f"{hex(i.address)}:\t{i.mnemonic}\t{i.op_str}" for i in insn_list[ctx_start:ctx_end])
if unresolved_fh: unresolved_fh.write(f"--- Unresolved call at {hex(insn.address)} ---\n{ctx}\n\n")
return call_order, []

def main(dll_path):
prefix = os.path.splitext(os.path.basename(dll_path))[0]
os.makedirs("calls", exist_ok=True)
calls_out = os.path.join("calls", prefix + "_calls.txt")
debug_fh = None
unresolved_fh = None

try:
print(f"[*] Processing {dll_path}")
r = r2pipe.open(dll_path, ["-e", "bin.relocs.apply=true", "-e", "asm.symbol=true", "-e", "asm.demangle=false"])
r.cmd("aaa")
funcs = r.cmdj("aflj")
check = next((f for f in funcs if "check" in f.get("name", "")), None)
if not check:
print("[-] check function not found! abort")
r.quit()
return
print(f"[+] Found check: {check['name']} @ {hex(check['addr'])}")
va_to_num = {}
syms = r.cmdj("isj")
for sym in syms:
name = sym.get("name", "")
va = sym.get("vaddr", 0)
if va and name.startswith("_Z21f") and name.endswith("Ph"):
va_to_num[va] = name
iat_map = build_iat_map(r, debug_fh)
all_calls, _ = disasm_and_extract_calls(r, check, va_to_num, iat_map, debug_fh, unresolved_fh)
final_call_order = []
for name in all_calls:
if isinstance(name, str) and name.startswith("_Z21f") and name.endswith("Ph"):
final_call_order.append(name)
with open(calls_out, "w", encoding="utf-8") as f:
json_output = json.dumps(final_call_order, separators=(',', ':'), ensure_ascii=False)
f.write(json_output)
print(f"[+] Extracted {len(final_call_order)} calls (filtered) in strict order -> {calls_out}")
r.quit()
except Exception as e:
print(f"[CRITICAL ERROR] An error occurred: {e}")
if 'r' in locals():
r.quit()

if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: python {os.path.basename(__file__)} <dll_file>")
sys.exit(1)
main(sys.argv[1])

#9.4 模拟程序逻辑

然后实现程序的加载dll逻辑,加入更多调试打印,优化dll加载方式,并使校验函数可重复利用:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <map>
#include <algorithm>
#include <iomanip>
#include <filesystem>
#include <limits>
#include <windows.h>
#include <winnt.h>

namespace fs = std::filesystem;
struct LoadedDllInfo {
std::string filePath;
HMODULE hModule = nullptr;
size_t imageSize = 0;
std::map<std::string, LPVOID> exports;
};

static unsigned char g_output_buffer[40000] = {0};
std::map<unsigned short, LoadedDllInfo> loadedDllCache;
typedef BOOL(__stdcall *CheckFunc)(unsigned char *data);

bool hexToBytes(const std::string& hex_str, unsigned char* output) {
if (hex_str.length() != 64) {
std::cerr << "[!]: Length of HEX must be 64 chars (32 bytes)." << std::endl;
return false;
}
for (size_t i = 0; i < 32; ++i) {
try {
output[i] = (unsigned char)std::stoul(hex_str.substr(i * 2, 2), nullptr, 16);
} catch (...) {
std::cerr << "[!]: Invalid hex char." << std::endl;
return false;
}
}
return true;
}

LoadedDllInfo* LoadCustomDll(unsigned short dllIndex) {
if (loadedDllCache.count(dllIndex)) {
// std::cout << "[*] Fetched: DLL " << std::setw(4) << std::setfill('0') << dllIndex << " loaded." << std::endl;
return &loadedDllCache[dllIndex];
}
std::string filename = std::to_string(dllIndex);
while (filename.length() < 4) {
filename.insert(0, "0");
}
std::string dllPath = "dlls/" + filename + ".dll";
// std::cout << "[*] Try to load: " << dllPath << std::endl;
std::ifstream file(dllPath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "[!]: Cannot open " << dllPath << "." << std::endl;
return nullptr;
}
size_t fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> dllData(fileSize);
if (!file.read(dllData.data(), fileSize)) {
std::cerr << "[!]: Cannot read " << dllPath << "." << std::endl;
return nullptr;
}
char* rawDll = dllData.data();
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)rawDll;
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
std::cerr << "[!]: File " << dllPath << " is invalid." << std::endl;
return nullptr;
}
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(rawDll + dosHeader->e_lfanew);
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
std::cerr << "[!]: File " << dllPath << " was corrupted." << std::endl;
return nullptr;
}
DWORD imageSize = ntHeaders->OptionalHeader.SizeOfImage;
LPVOID baseAddress = VirtualAlloc(NULL, imageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!baseAddress) {
std::cerr << "[!]: VirtualAlloc failed." << std::endl;
return nullptr;
}
DWORD headerSize = ntHeaders->OptionalHeader.SizeOfHeaders;
memcpy(baseAddress, rawDll, headerSize);
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i, ++sectionHeader) {
if (sectionHeader->SizeOfRawData > 0) {
LPVOID destination = (LPVOID)((char*)baseAddress + sectionHeader->VirtualAddress);
LPVOID source = (LPVOID)(rawDll + sectionHeader->PointerToRawData);
memcpy(destination, source, sectionHeader->SizeOfRawData);
}
}
PIMAGE_NT_HEADERS mappedNtHeaders = (PIMAGE_NT_HEADERS)((char*)baseAddress + dosHeader->e_lfanew);
ULONGLONG delta = (ULONGLONG)baseAddress - mappedNtHeaders->OptionalHeader.ImageBase;
PIMAGE_DATA_DIRECTORY importDir = &mappedNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir->Size > 0) {
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)((char*)baseAddress + importDir->VirtualAddress);
for (; importDesc->Name; ++importDesc) {
const char* dllName = (const char*)baseAddress + importDesc->Name;
HMODULE hImportedDll = nullptr;
bool isCustomDllIndex = (strlen(dllName) >= 4) && std::all_of(dllName, dllName + 4, ::isdigit);
if (isCustomDllIndex) {
unsigned short dependencyIndex = (unsigned short)std::atoi(dllName);
// std::cout << "[*] Depends to DLL: " << dllName << ", load index " << dependencyIndex << "..." << std::endl;
LoadedDllInfo* dependencyInfo = LoadCustomDll(dependencyIndex);
if (!dependencyInfo) {
std::cerr << "[!]: Load DLL " << dllName << " failed." << std::endl;
VirtualFree(baseAddress, 0, MEM_RELEASE);
return nullptr;
}
hImportedDll = dependencyInfo->hModule;
PIMAGE_THUNK_DATA64 firstThunk = (PIMAGE_THUNK_DATA64)((char*)baseAddress + importDesc->FirstThunk);
while (firstThunk->u1.AddressOfData) {
const char* funcName = (const char*)((char*)baseAddress + firstThunk->u1.AddressOfData + 2);
std::string funcNameStr(funcName);
if (dependencyInfo->exports.count(funcNameStr)) {
firstThunk->u1.Function = (ULONGLONG)dependencyInfo->exports[funcNameStr];
} else {
std::cerr << "[!]: DLL " << dllName << " has no function: " << funcNameStr << std::endl;
firstThunk->u1.Function = 0;
}
++firstThunk;
}
} else {
hImportedDll = LoadLibraryA(dllName);
if (!hImportedDll) {
DWORD errorCode = GetLastError();
std::cerr << "[!]: LoadLibraryA loads DLL " << dllName << " failed. ERR CODE: " << errorCode << std::endl;
VirtualFree(baseAddress, 0, MEM_RELEASE);
return nullptr;
}
PIMAGE_THUNK_DATA64 firstThunk = (PIMAGE_THUNK_DATA64)((char*)baseAddress + importDesc->FirstThunk);
while (firstThunk->u1.AddressOfData) {
LPCSTR funcName = nullptr;
if (IMAGE_SNAP_BY_ORDINAL64(firstThunk->u1.AddressOfData)) {
funcName = (LPCSTR)IMAGE_ORDINAL64(firstThunk->u1.AddressOfData);
} else {
funcName = (LPCSTR)((char*)baseAddress + firstThunk->u1.AddressOfData + 2);
}
FARPROC procAddr = GetProcAddress(hImportedDll, funcName);
if (!procAddr) {
std::cerr << "[!]: System DLL " << dllName << " has no func: " << (IMAGE_SNAP_BY_ORDINAL64(firstThunk->u1.AddressOfData) ? "Ordinal" : (const char*)funcName) << std::endl;
firstThunk->u1.Function = 0;
} else {
firstThunk->u1.Function = (ULONGLONG)procAddr;
}
++firstThunk;
}
}
}
}
PIMAGE_DATA_DIRECTORY exportDir = &mappedNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
LoadedDllInfo& info = loadedDllCache[dllIndex];
info.hModule = (HMODULE)baseAddress;
info.imageSize = imageSize;
info.filePath = dllPath;
if (exportDir->Size > 0) {
PIMAGE_EXPORT_DIRECTORY exportDesc = (PIMAGE_EXPORT_DIRECTORY)((char*)baseAddress + exportDir->VirtualAddress);
DWORD* nameRvas = (DWORD*)((char*)baseAddress + exportDesc->AddressOfNames);
WORD* ordinalTable = (WORD*)((char*)baseAddress + exportDesc->AddressOfNameOrdinals);
DWORD* funcRvas = (DWORD*)((char*)baseAddress + exportDesc->AddressOfFunctions);
for (DWORD i = 0; i < exportDesc->NumberOfNames; ++i) {
const char* funcName = (const char*)((char*)baseAddress + nameRvas[i]);
DWORD funcRva = funcRvas[ordinalTable[i]];
LPVOID funcAddr = (LPVOID)((char*)baseAddress + funcRva);
info.exports[funcName] = funcAddr;
// if (strcmp(funcName, "_Z5checkPh") == 0) {
// std::cout << "[*] Find check function _Z5checkPh, addr: 0x" << std::hex << std::setw(16) << std::setfill('0') << (uintptr_t)funcAddr << std::dec << std::endl;
// }
}
}
PIMAGE_DATA_DIRECTORY baseRelocDir = &mappedNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (baseRelocDir->Size > 0 && delta != 0) {
PIMAGE_BASE_RELOCATION relocBlock = (PIMAGE_BASE_RELOCATION)((char*)baseAddress + baseRelocDir->VirtualAddress);
long long baseDelta = (long long)delta;
while (relocBlock->SizeOfBlock > 0) {
WORD* relocEntry = (WORD*)((char*)relocBlock + sizeof(IMAGE_BASE_RELOCATION));
for (size_t i = 0; i < (relocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); ++i) {
WORD typeOffset = relocEntry[i];
DWORD offset = typeOffset & 0x0FFF;
DWORD type = (typeOffset >> 12);
if (type == IMAGE_REL_BASED_ABSOLUTE) {
continue;
}
PULONG_PTR fixupAddr = (PULONG_PTR)((char*)baseAddress + relocBlock->VirtualAddress + offset);
switch (type) {
case IMAGE_REL_BASED_DIR64:
*fixupAddr += baseDelta;
break;
case IMAGE_REL_BASED_HIGHLOW:
*(PDWORD)fixupAddr += (DWORD)baseDelta;
break;
case IMAGE_REL_BASED_HIGH:
*(PUSHORT)fixupAddr += HIWORD(baseDelta);
break;
case IMAGE_REL_BASED_LOW:
*(PUSHORT)fixupAddr += LOWORD(baseDelta);
break;
default:
break;
}
}
relocBlock = (PIMAGE_BASE_RELOCATION)((char*)relocBlock + relocBlock->SizeOfBlock);
}
}

DWORD entryPointRVA = mappedNtHeaders->OptionalHeader.AddressOfEntryPoint;
if (entryPointRVA != 0) {
typedef __int64 (__fastcall *CustomDllEntryProc)(LPVOID OutputAddr, DWORD Reason, LPVOID Reserved);
CustomDllEntryProc DllMainFunc = (CustomDllEntryProc)((char*)baseAddress + entryPointRVA);
// std::cout << "[*] Trying to call DllMain (DLL_PROCESS_ATTACH)..." << std::endl;
__int64 callResult = DllMainFunc((LPVOID)g_output_buffer, DLL_PROCESS_ATTACH, NULL);
if (callResult == 0) {
std::cout << "[!]: DllMain returns 0." << std::endl;
} else {
// std::cout << "[*] Custom DllMain Called, res: " << callResult << std::endl;
}
} else {
std::cout << "[!] DLL has no entry point." << std::endl;
}
// std::cout << "[*] Sunccessfully loads DLL " << filename << " , base addr: 0x" << std::hex << std::setw(16) << std::setfill('0') << (uintptr_t)baseAddress << std::dec << std::endl;
return &info;
}

void cleanup_resources() {
std::cout << "\n[*] Cleaning up..." << std::endl;
for (auto const& [index, info] : loadedDllCache) {
if (info.hModule) {
VirtualFree(info.hModule, 0, MEM_RELEASE);
}
}
loadedDllCache.clear();
std::cout << "[*] Cleaned up." << std::endl;
}

void run_loader() {
std::cout << "--- INFINITY TIMES DLL LOADER ---" << std::endl;

if (!fs::exists("dlls")) {
std::cerr << "[!]: No 'dlls' dir." << std::endl;
return;
}
int run_count = 0;
while (true) {
std::cout << "\n=================================================" << std::endl;
std::cout << "[*] Now already loads " << loadedDllCache.size() << " DLLs." << std::endl;
std::cout << run_count + 1 << "# [*] Please input DLL index as 4 nums (eg. 1234) or 'R' to reset all: " << std::endl;
std::string input;
if (!(std::cin >> input)) {
std::cout << "\n[*] Meet EOF, quitting..." << std::endl;
break;
}
if (input == "R" || input == "r") {
// cleanup_resources();
std::fill(g_output_buffer, g_output_buffer + 40000, 0);
std::cout << "[*] Global buffer reset complete." << std::endl;
run_count = 0;
continue;
}
std::string inputDllIndex = input;
if (inputDllIndex.length() != 4 || !std::all_of(inputDllIndex.begin(), inputDllIndex.end(), ::isdigit)) {
std::cerr << "[!]: input format wrong. Must be 4 digits or 'R'." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
continue;
}
unsigned short mainDllIndex = (unsigned short)std::stoi(inputDllIndex);
std::fill(g_output_buffer, g_output_buffer + 40000, 0);
std::cout << "[*] Global Output Buffer has been cleared to zero." << std::endl;

std::cout << "\n[*] Start loading DLL: " << inputDllIndex << ".dll..." << std::endl;
LoadedDllInfo* mainDllInfo = LoadCustomDll(mainDllIndex);
if (!mainDllInfo) {
std::cerr << "\n[!]: Failed to load main DLL: " << inputDllIndex << ".dll." << std::endl;
continue;
}
std::cout << "\n[*] ALL DLL are loaded." << std::endl;
if (!mainDllInfo->exports.count("_Z5checkPh")) {
std::cerr << "\n[!]: No check function in main DLL." << std::endl;
continue;
}
CheckFunc checkFunction = (CheckFunc)mainDllInfo->exports.at("_Z5checkPh");
std::cout << "[*] Found _Z5checkPh. Calling checker..." << std::endl;
std::string hexInput;
std::cout << "\n[*] Please input hex form of 32 bytes (64 chars) as input: " << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
if (!std::getline(std::cin, hexInput)) {
std::cout << "\n[*] Meet EOF, quitting..." << std::endl;
break;
}
hexInput.erase(std::remove_if(hexInput.begin(), hexInput.end(), ::isspace), hexInput.end());
unsigned char checkData[32];
if (!hexToBytes(hexInput, checkData)) {
std::cerr << "[!]: HEX format wrong." << std::endl;
continue;
}
std::cout << "\n[*] Calling _Z5checkPh(0x" << hexInput << ")..." << std::endl;
BOOL checkResult = checkFunction(checkData);
std::cout << "----- Result -----" << std::endl;
std::cout << "Result (BOOL): " << checkResult << std::endl;
if (checkResult != 0) {
std::cout << "Result: Correct (True)" << std::endl;
} else {
std::cout << "Result: Wrong (False)" << std::endl;
}
std::cout << "------------------" << std::endl;
run_count++;
}
}

int main() {
SetConsoleOutputCP(CP_UTF8);
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
run_loader();
cleanup_resources();
std::cout << "\nPress Enter to quit..." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
return 0;
}

需要注意到,源程序中LOAD_DLL在尾部的调用v30 = ((__int64 (__fastcall *)(_DWORD *, __int64, _QWORD))v31)(Output, 1, 0);其实调用了DLLEntryPointHINSTANCE hinstDLL传入的是Output Buffer的地址,而非正常值,分析DLLmain函数,发现其在初始化时将内部的一个指针指向了Output Buffer,而Output Buffer的修改机制来源于以下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall free_obj(__int64 index)
{
idx = index;
v5 = &off_7FF75A602C80;
v3 = sub_7FF75A59F780((__int64)&off_7FF75A602C80);
v2 = sub_7FF75A59F750((__int64)&off_7FF75A602C80);
while ( !sub_7FF75A511500((__int64)&v3, (__int64)&v2) )
{
v4 = *(unsigned __int16 **)sub_7FF75A5130F0((__int64)&v3);
Output[*v4] += idx;
VirtualFree(*((LPVOID *)v4 + 1), 0, 0x8000u);
sub_7FF75A510560(&v3);
}
return sub_7FF75A59F7B0(&off_7FF75A602C80);
}

该函数会释放之前递归加载的dll链,同时如果有dll被释放,那么Output中该dll的索引处的值就会被加上当前所处的外层大循环的轮数,最终累加得到target,先写脚本提取直接调用:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import os
import re
import json
import sqlite3

DLLS_DIR = "dlls"
DB_PATH = "dll_relations.db"
TOTAL_IDS = 10000

DIGIT4_PATTERN = re.compile(rb'^[0-9]{4}$')

def find_related_ids_in_dll(dll_path):
try:
with open(dll_path, 'rb') as f:
data = f.read()
except (OSError, IOError) as e:
print(f"[ERROR] Failed to read {dll_path}: {e}")
return []

related = []
pos = len(data)

while True:
idx = data.rfind(b'.dll', 0, pos)
if idx == -1:
break

if idx < 4:
pos = idx
continue

prefix = data[idx - 4:idx]

if DIGIT4_PATTERN.match(prefix):
id_str = prefix.decode('ascii')
related.append(id_str)
pos = idx
else:
break

return related

def main():
if not os.path.isdir(DLLS_DIR):
print(f"[ERROR] Directory '{DLLS_DIR}' does not exist.")
return

conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()

cursor.execute('''
CREATE TABLE IF NOT EXISTS relations (
id TEXT PRIMARY KEY,
related_ids TEXT NOT NULL
)
''')
conn.commit()

records = []

for i in range(TOTAL_IDS):
id_str = f"{i:04d}"
dll_path = os.path.join(DLLS_DIR, f"{id_str}.dll")

if os.path.isfile(dll_path):
related = find_related_ids_in_dll(dll_path)
else:
related = []
related_json = json.dumps(related, separators=(',', ':'))
records.append((id_str, related_json))
if i % 100 == 0:
print(f"[INFO] Processed {i}/10000")
cursor.executemany('INSERT OR REPLACE INTO relations (id, related_ids) VALUES (?, ?)', records)
conn.commit()
conn.close()
print(f"[DONE] Wrote {len(records)} entries to {DB_PATH}")

if __name__ == "__main__":
main()

然后递归生成调用集合:

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
58
59
60
61
62
63
64
65
import sqlite3
import json
from collections import deque

INPUT_DB = "dll_relations.db"
OUTPUT_DB = "dll_closure.db"

def load_relations(conn):
cursor = conn.cursor()
cursor.execute("SELECT id, related_ids FROM relations")
relations = {}
for id_str, related_json in cursor.fetchall():
try:
related_list = json.loads(related_json)
relations[id_str] = set(str(x) for x in related_list if isinstance(x, str) and len(x) == 4 and x.isdigit())
except (json.JSONDecodeError, TypeError):
relations[id_str] = set()
return relations

def compute_closure(start_id, graph):
visited = set()
queue = deque([start_id])
visited.add(start_id)
while queue:
current = queue.popleft()
for neighbor in graph.get(current, set()):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return visited

def main():
with sqlite3.connect(INPUT_DB) as conn:
print(f"[INFO] Loading relations from {INPUT_DB}...")
graph = load_relations(conn)
all_ids = {f"{i:04d}" for i in range(10000)}
for id_str in all_ids:
if id_str not in graph:
graph[id_str] = set()
print(f"[INFO] Loaded {len(graph)} nodes. Computing transitive closure...")
closures = {}
for i, id_str in enumerate(sorted(all_ids)):
closure_set = compute_closure(id_str, graph)
closures[id_str] = sorted(closure_set)
if i % 500 == 0:
print(f"[INFO] Processed {i}/10000")
with sqlite3.connect(OUTPUT_DB) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS closure (
id TEXT PRIMARY KEY,
all_deps TEXT NOT NULL
)
''')
conn.commit()
records = [
(id_str, json.dumps(deps, separators=(',', ':')))
for id_str, deps in closures.items()
]
cursor.executemany('INSERT OR REPLACE INTO closure (id, all_deps) VALUES (?, ?)', records)
conn.commit()
print(f"[DONE] Wrote closure data to {OUTPUT_DB}")

if __name__ == "__main__":
main()

接下来提取target的值并解矩阵得到主dll的调用顺序:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import sqlite3
import json

conn = sqlite3.connect("dll_closure.db")
cursor = conn.cursor()
cursor.execute("SELECT id, all_deps FROM closure")
closure_by_id = {}
for id_str, deps_json in cursor.fetchall():
deps = set(int(x) for x in json.loads(deps_json) if str(x).isdigit() and 0 <= int(x) <= 9999)
closure_by_id[int(id_str)] = deps

for i in range(10000):
if i not in closure_by_id:
closure_by_id[i] = set()

with open("target.txt", "r") as f:
hex_str = ''.join(f.read().split())
data = bytes.fromhex(hex_str)
T = [int.from_bytes(data[i*4:(i+1)*4], 'little') for i in range(10000)]

S = [set() for _ in range(10000)]
for id_val in range(10000):
for j in closure_by_id[id_val]:
if 0 <= j < 10000: S[j].add(id_val)

p = [-1] * 10000
remaining_T = T[:]
known = 0

from collections import deque
queue = deque()

for j in range(10000):
if len(S[j]) == 1: queue.append(j)

while queue:
j = queue.popleft()
if len(S[j]) != 1: continue
id_val = next(iter(S[j]))
if p[id_val] != -1: continue
p[id_val] = remaining_T[j]
known += 1
print(f"Found p[{id_val:04d}] = {p[id_val]} from column {j}")

for k in closure_by_id[id_val]:
if 0 <= k < 10000:
if id_val in S[k]:
S[k].remove(id_val)
remaining_T[k] -= p[id_val]
if len(S[k]) == 1:
queue.append(k)

print(f"[INFO] Solved {known}/10000 variables")

if known == 10000:
if sorted(p) == list(range(10000)):
with open("order.txt", "w") as f:
for row_index in p:
pass
order = [0] * 10000
for id_val in range(10000):
row_idx = p[id_val]
if 0 <= row_idx < 10000:
order[row_idx] = id_val
else:
raise ValueError(f"Invalid row index {row_idx} for ID {id_val}")
with open("order.txt", "w") as f:
for id_val in order:
f.write(f"{id_val:04d}\n")
print("[SUCCESS] Solved!")
print("[INFO] Writing matrix.txt (10000x10000 binary matrix)...")
with open("matrix.txt", "w") as f:
for id_val in range(10000):
row = ['0'] * 10000
for j in closure_by_id[id_val]:
if 0 <= j < 10000:
row[j] = '1'
f.write(''.join(row) + '\n')
print("[SUCCESS] matrix.txt written.")
else:
print("[ERROR] Rresult is not a permutation")
else:
print("[ERROR] Cannot solve.")

import numpy as np
A = np.zeros((10000, 10000), dtype=np.uint8)
with open("matrix.txt", "r") as f:
for i, line in enumerate(f):
A[i] = np.frombuffer(line.strip().encode(), dtype='S1').astype(int)
with open("order.txt", "r") as f:
order = [int(line.strip()) for line in f]
M = A[order]
T_recon = np.dot(np.arange(10000, dtype=np.uint64), M)
with open("target.txt", "r") as f:
hex_str = ''.join(f.read().split())
data = bytes.fromhex(hex_str)
T_orig = np.frombuffer(data, dtype='<u4')

assert np.array_equal(T_recon, T_orig), "Verification failed!"
print("Verification passed!")

得到顺序表和二值矩阵,接下来同构加密过程

#9.5 正向同构check运算过程

#9.5.1 前半部分

分析check函数中形如_Z21fxxxxxxxxxxxxxxxxxPh函数的模式,发现一共有3种,分别对应类卷积乘法(const 31字节,间隔是var30和28,不可逆,记为C模式),打乱顺序(32字节,间隔是60和58,可逆,记为B模式)和sbox替换(256字节,可逆,记为A模式),编写脚本提取所有常量:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import os
import sys
import re
import ast
import subprocess
import r2pipe
import sqlite3
import json

os.makedirs("consts", exist_ok=True)

DB_FILE = "exported_functions.db"
DLL_FOLDER = "dlls"

def load_exported_functions_index():
func_to_dll = {}
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute("SELECT id, exported_functions FROM dll_exports")
for row in cursor:
id_num, funcs_json = row
try:
func_list = json.loads(funcs_json)
if not isinstance(func_list, list):
continue
dll_name = f"{id_num:04d}.dll"
for func in func_list:
if isinstance(func, str) and func.startswith('_Z21f') and func.endswith('Ph'):
if func in func_to_dll:
print(f"[!] Warning: function {func} appears in multiple DLLs!")
func_to_dll[func] = (id_num, dll_name)
except Exception as e:
print(f"[!] Failed to parse functions for id={id_num}: {e}")
continue
conn.close()
return func_to_dll

def analyze_function_in_dll(dll_path: str, func_name: str):
print(f" [+] Analyzing {func_name} in {dll_path}")
r2 = r2pipe.open(dll_path, flags=['-e', 'bin.relocs.apply=true'])
try:
exports = r2.cmdj("iEj")
target = next((e for e in exports if e.get("name") == func_name), None)
if not target:
print(f" [-] Not found in exports")
return None, None
vaddr = target["vaddr"]
r2.cmd(f"af @ {vaddr}")
disasm_lines = r2.cmd(f"pdr @ {vaddr}").splitlines()
if not disasm_lines or any("invalid" in line.lower() for line in disasm_lines):
return None, None
movabs_pattern = re.compile(r'movabs\s+(rax|rdx),\s*(0x[0-9a-fA-F]+)')
constants = []
for line in disasm_lines:
match = movabs_pattern.search(line)
if match:
constants.append(int(match.group(2), 16))
const_count = len(constants)
if const_count == 32:
return 'A', constants
elif const_count == 4:
disasm_text = "\n".join(disasm_lines)
if re.search(r'mov\s+qword\s+\[var_30h\]', disasm_text) and \
re.search(r'mov\s+qword\s+\[var_28h\]', disasm_text):
return 'B', constants
elif re.search(r'mov\s+qword\s+\[var_60h\]', disasm_text) and \
re.search(r'mov\s+qword\s+\[var_58h\]', disasm_text):
return 'C', constants
else:
print(f" [!] 4 constants but unknown pattern in {func_name}")
return 'ERROR', constants
else:
print(f" [!] Unexpected constant count: {const_count} in {func_name}")
return 'ERROR', constants
finally:
r2.quit()

def main():
if len(sys.argv) != 2:
print("Usage: python extract_by_calls.py <id> eg.0000")
sys.exit(1)
input_id = sys.argv[1]
if not input_id.isdigit() or len(input_id) > 4:
print("ID must be a numeric string (eg. 0000, 123)")
sys.exit(1)
id_num = int(input_id)
id_str = f"{id_num:04d}"
dll_path = os.path.join(DLL_FOLDER, f"{id_str}.dll")
calls_txt = os.path.join("calls", f"{id_str}_calls.txt")
output_path = os.path.join("consts", f"{id_str}.txt")
if not os.path.isfile(dll_path):
print(f"[-] DLL not found: {dll_path}")
sys.exit(1)
print(f"[+] Running export_calls.py {dll_path}")
result = subprocess.run([sys.executable, "export_calls.py", dll_path])
if result.returncode != 0:
print("[-] export_calls.py failed!")
sys.exit(1)
if not os.path.isfile(calls_txt):
print(f"[-] {calls_txt} not generated by export_calls.py")
sys.exit(1)
with open(calls_txt, 'r', encoding='utf-8') as f:
content = f.read().strip()
try:
target_funcs = ast.literal_eval(content)
if not isinstance(target_funcs, list):
raise ValueError("Not a list")
except Exception as e:
print(f"[-] Failed to parse {calls_txt}: {e}")
sys.exit(1)
print(f"[+] Loaded {len(target_funcs)} target functions from {calls_txt}")
func_to_dll = load_exported_functions_index()
results = []
for func in target_funcs:
if not (isinstance(func, str) and func.startswith('_Z21f') and func.endswith('Ph')):
continue
if func not in func_to_dll:
print(f" [!] Function {func} not found in database")
continue
id_num, dll_name = func_to_dll[func]
actual_dll_path = os.path.join(DLL_FOLDER, dll_name)
if not os.path.isfile(actual_dll_path):
print(f" [!] DLL {actual_dll_path} not found for function {func}")
continue
typ, consts = analyze_function_in_dll(actual_dll_path, func)
if typ is None or typ == 'ERROR':
print(f" [!] Skipping {func} due to analysis error")
continue
const_str = "[" + ", ".join(f"0x{c:016x}" for c in consts) + "]"
results.append(f"{func}\t{typ}\t{const_str}")
with open(output_path, 'w', encoding='utf-8') as out_f:
for line in results:
out_f.write(line + "\n")
print(f"[+] Results saved to {output_path}")

if __name__ == "__main__":
main()

同构前面的_Z21fPh函数,需要注意的是,A/B/C模式函数中在开头均有一行*a1 ^= *(_DWORD *)off_268936B6020[id];,说明输入的前四个字节会被异或Output Buffer中对应DLL id索引的dword(需要注意:如果函数来源于别的dll,xor的是别的dll的索引),此处同构可以先按0处理:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import sys
import os
import ast

def poly_mul(a, b):
res = bytearray(32)
carry = 0
for i in range(32):
total = carry
for j in range(i + 1):
total += a[j] * b[i - j]
res[i] = total & 0xFF
carry = total >> 8
return res

def n2ble(na, n):
b = bytearray()
for a in na: b.extend(a.to_bytes(n, byteorder='little'))
return b

def load_const(buf, offsets, na, n):
for a, offset in zip(na, offsets):
buf[offset:offset + n] = a.to_bytes(n, byteorder='little')
return buf

def func_A(inp, qkey):
sbox = n2ble(qkey, 8)
inp = bytearray([sbox[byte] for byte in inp])
return inp

def func_B(inp, qkey):
perm = n2ble(qkey, 8)
result = bytearray(32)
for i in range(32): result[i] = inp[perm[i]]
return result

def func_C(inp, qkey):
key = load_const(bytearray(32), [0, 8, 15, 23], qkey, 8)
tag = inp[0] & 1
inp[0] |= 1
v3 = bytearray(32)
v3[0] = 1
v2 = bytearray(64)
v2[32:] = inp
for byte_idx in range(32):
byte_val = key[byte_idx]
for bit in range(8):
if (byte_val >> bit) & 1:
v3 = poly_mul(v3, v2[32:])
v2[32:] = poly_mul(v2[32:], v2[32:])
result = v3
result[0] ^= tag ^ 1
return result

def apply_config_from_file(config_id, initial_inp_hex):
filepath = os.path.join("consts", f"{config_id}.txt")
inp = bytearray.fromhex(initial_inp_hex.strip())
with open(filepath, "r", encoding="utf-8") as f: lines = f.readlines()
for line_num, line in enumerate(lines, 1):
line = line.strip()
if not line or line.startswith("#"): continue
parts = line.split("\t")
func_type = parts[1].strip()
qkey_str = parts[2].strip()
try:
qkey = ast.literal_eval(qkey_str)
except Exception as e: raise ValueError(f"Failed to parse qkey at line {line_num}: {qkey_str}") from e
if not isinstance(qkey, list): raise TypeError(f"qkey must be a list at line {line_num}")
if func_type == "A": inp = func_A(inp, qkey)
elif func_type == "B": inp = func_B(inp, qkey)
elif func_type == "C": inp = func_C(inp, qkey)
else: raise ValueError(f"Unknown function type '{func_type}' at line {line_num}")
return inp

if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python script.py <id> <input(hex)>")
sys.exit(1)
config_id = sys.argv[1]
if not config_id.isdigit() or len(config_id) != 4:
print("Error: ID must be a 4-digit number (e.g., 0000)")
sys.exit(1)
initial_inp_hex = sys.argv[2]
try:
result = apply_config_from_file(config_id, initial_inp_hex)
print(result.hex())
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)

发现运行结果相同

#9.5.2 后半部分

继续查看check的后半段:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  tag1 = 0xC78F79F3C2DE8CA7LL;
tag2 = 0x900155FC1FE04EADLL;
v89[0] = 0x12B3A6E563A54C8AuLL;
v89[1] = 0x83D939BA98B1E883LL;
v89[2] = 0x7886CD454720813BuLL;
v89[3] = 0x7998EFD66FF8A530uLL;
v89[4] = 0x1A0FD2225B5649F3uLL;
v89[5] = 0x7FA833612DC9F78BuLL;
v89[6] = 0xBB0F8B983D445D10LL;
v89[7] = 0xFE710C8D73876CE2LL;
v89[8] = 0xE3DD29A65E3FE661LL;
v89[9] = 0xB67BDAB54A677B84LL;
v89[10] = 0x6320D9C38BAC5A6AuLL;
v89[11] = 0xF087EC1927E12C0uLL;
v89[12] = 0x5F0E2A1F46DBD2FFuLL;
v89[13] = 0x690726E48B96FA42uLL;
v89[14] = 0x86761ABD847AD9C9LL;
v89[15] = 0xF71197E894C6D650LL;
for ( i = 0; i <= 15; ++i )
{
v5 = *((_QWORD *)&v86[i + 64] + 1);
v6 = &v86[i + 64];
*(_QWORD *)v6 ^= *(_QWORD *)(8LL * (i % 4) + a5);
*((_QWORD *)v6 + 1) = v5;
if ( v86[i + 64] >= tag1 )
return 0;
}
v91 = 0u;
v8 = v89[0];
v9 = v89[5];
v85 = v89[15] * v89[10];
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&v89[5] + 1), *(_QWORD *)&v89[5], &v84, &v85);
v85 = *(_OWORD *)&a2 * v9;
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&v9 + 1), v9, &v84, &v85);
v80 = a2;
v10 = v89[6];
v85 = v89[13] * v89[11];
v84 = tag1;
...
v85 = *(_OWORD *)&a2 * v13;
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&v13 + 1), v13, &v84, &v85);
v14 = a2;
v15 = v89[15];
v85 = v89[6] * v89[9];
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&v89[15] + 1), *(_QWORD *)&v89[15], &v84, &v85);
v85 = *(_OWORD *)&a2 * v15;
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&v15 + 1), v15, &v84, &v85);
v85 = *(_OWORD *)&v14 + *(_OWORD *)&a2;
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&v15 + 1), v15, &v84, &v85);
v85 = *(_OWORD *)&v68 + *(_OWORD *)&a2;
v84 = tag1;
...
*(double *)a2.m128_u64 = sub_3B65C0290(*((_QWORD *)&Buf2 + 1), Buf2, &v84, &v85);
v91 = a2;
if ( *(_OWORD *)&a2 == 0 )
return 0;
Size[0] = 1;
memset(&Size[1], 0, 72);
Size[10] = 1;
memset(&Size[11], 0, 72);
Size[20] = 1;
memset(&Size[21], 0, 72);
Size[30] = 1;
Size[31] = 0;
memset(buf_, 0, sizeof(buf_));
for ( n15_1 = 0; n15_1 <= 15; ++n15_1 )
{
v40 = *((_QWORD *)&v86[n15_1 + 64] + 1);
v41 = &v86[n15_1 + 32];
*(_QWORD *)v41 = *(_QWORD *)&v86[n15_1 + 64];
*((_QWORD *)v41 + 1) = v40;
}
for ( n63 = 0; n63 <= 63; ++n63 )
{
if ( (tag2 >> n63) & 1 )
{
for ( n3_2 = 0; n3_2 <= 3; ++n3_2 )
{
for ( n3_1 = 0; n3_1 <= 3; ++n3_1 )
{
v42 = &v86[4 * n3_2 + 16 + n3_1];
*(_QWORD *)v42 = 0;
*((_QWORD *)v42 + 1) = 0;
n3 = 0;
while ( n3 <= 3 )
{
v43 = *(_QWORD *)&v86[4 * n3_2 + 16 + n3_1];
v44 = *((_QWORD *)&v86[4 * n3_2 + 16 + n3_1] + 1);
v45 = *(_QWORD *)&v86[4 * n3_2 + 48 + n3];
v46 = *(_QWORD *)&v86[4 * n3 + 32 + n3_1];
v47 = v46 * *((_QWORD *)&v86[4 * n3_2 + 48 + n3] + 1) + v45 * *((_QWORD *)&v86[4 * n3 + 32 + n3_1] + 1);
v48 = v46 * (unsigned __int128)v45;
*((_QWORD *)&v48 + 1) += v47;
v85 = v48;
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(v44, v43, &v84, &v85);
*(_QWORD *)&v49 = v43;
*((_QWORD *)&v49 + 1) = v44;
v50 = 16LL * (4 * n3_2 + n3_1) + 1328;
*(__int128 *)((char *)&v86[-67] + v50) = *(_OWORD *)&a2 + v49;
v51 = (unsigned int)(4 * n3_2 + n3_1);
v85 = v86[4 * n3_2 + 16 + n3_1];
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(v50, v51, &v84, &v85);
v86[(int)v51 + 16] = (__int128)a2;
++n3;
Buf2 = __PAIR128__(&v86[4], v51);
}
}
}
for ( n15_2 = 0; n15_2 <= 15; ++n15_2 )
{
v52 = *((_QWORD *)&v86[n15_2 + 16] + 1);
v53 = 16LL * n15_2 + 1328;
v54 = (_QWORD *)((char *)&v86[-35] + v53);
*v54 = *(_QWORD *)((char *)&v86[-67] + v53);
v54[1] = v52;
}
}
for ( n3_5 = 0; n3_5 <= 3; ++n3_5 )
{
for ( n3_4 = 0; n3_4 <= 3; ++n3_4 )
{
v55 = &v86[4 * n3_5 + 16 + n3_4];
*(_QWORD *)v55 = 0;
*((_QWORD *)v55 + 1) = 0;
n3_3 = 0;
while ( n3_3 <= 3 )
{
v56 = *(_QWORD *)&v86[4 * n3_5 + 16 + n3_4];
v57 = *((_QWORD *)&v86[4 * n3_5 + 16 + n3_4] + 1);
v58 = *(_QWORD *)&v86[4 * n3_5 + 32 + n3_3];
v59 = *(_QWORD *)&v86[4 * n3_3 + 32 + n3_4];
v60 = v59 * *((_QWORD *)&v86[4 * n3_5 + 32 + n3_3] + 1) + v58 * *((_QWORD *)&v86[4 * n3_3 + 32 + n3_4] + 1);
v61 = v59 * (unsigned __int128)v58;
*((_QWORD *)&v61 + 1) += v60;
v85 = v61;
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(v57, v56, &v84, &v85);
*(_QWORD *)&v62 = v56;
*((_QWORD *)&v62 + 1) = v57;
v63 = 16LL * (4 * n3_5 + n3_4) + 1328;
*(__int128 *)((char *)&v86[-67] + v63) = *(_OWORD *)&a2 + v62;
v64 = (unsigned int)(4 * n3_5 + n3_4);
v85 = v86[4 * n3_5 + 16 + n3_4];
v84 = tag1;
*(double *)a2.m128_u64 = sub_3B65C0290(v63, v64, &v84, &v85);
v86[(int)v64 + 16] = (__int128)a2;
++n3_3;
Buf2 = __PAIR128__(&v86[4], v64);
}
}
}
for ( n15_3 = 0; n15_3 <= 15; ++n15_3 )
{
v65 = *((_QWORD *)&v86[n15_3 + 16] + 1);
v66 = 16LL * n15_3 + 1328;
v67 = (_QWORD *)((char *)&v86[-51] + v66);
*v67 = *(_QWORD *)((char *)&v86[-67] + v66);
v67[1] = v65;
}
}
v86[0] = 0x1EFB0F7A200D7EFEuLL;
v86[1] = 0x628B24B42C976293uLL;
v86[2] = 0x433F2C4519786852uLL;
v86[3] = 0xAACB9CE48EECB631LL;
v86[4] = 0xA8324E2FA37A6B68LL;
v86[5] = 0xBC2191FD6C58F3A5LL;
v86[6] = 0xC19604E77EE4FE00LL;
v86[7] = 0x15E04D3A1DC1FA68uLL;
v86[8] = 0x5625BF80AD7B7CFEuLL;
v86[9] = 0xB2D8A8637CB92E09LL;
v86[10] = 0xA9ACEECB13AB347FLL;
v86[11] = 0x8E2553EF7F93165uLL;
v86[12] = 0xC0320352C3ED124ELL;
v86[13] = 0xBF1B7A608553A3B5LL;
v86[14] = 0xA2F294667D086695LL;
v86[15] = 0xC149DEB785270E1DLL;
return memcmp(*((const void **)&Buf2 + 1), (const void *)Buf2, (size_t)Size) == 0;
}

在check的下半段中,一共有16+16个oword作为密钥/密文,3个oword/qword作为tag类的标签,编写脚本提取check中的34个常量(有一个tag初始化为0):

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
import os
import sys
import re
import r2pipe

os.makedirs("consts_check", exist_ok=True)

def analyze_check_function(dll_path: str):
r2 = r2pipe.open(dll_path, flags=['-e', 'bin.relocs.apply=true'])
try:
exports = r2.cmdj("iEj")
target = next((e for e in exports if e.get("name") == "_Z5checkPh"), None)
if not target:
return []
vaddr = target["vaddr"]
r2.cmd(f"af @ {vaddr}")
disasm = r2.cmd(f"pdr @ {vaddr}")
movabs_pattern = re.compile(r'movabs\s+rax,\s*(0x[0-9a-fA-F]+)')
constants = []
for line in disasm.splitlines():
match = movabs_pattern.search(line)
if match:
constants.append(int(match.group(1), 16))
return constants
finally:
r2.quit()

def main():
if len(sys.argv) != 2:
print("Usage: python extract_const_check.py <id> e.g., 0000")
sys.exit(1)
input_id = sys.argv[1]
if not input_id.isdigit() or len(input_id) > 4:
print("ID must be a numeric string (e.g., 0000, 123)")
sys.exit(1)
id_str = f"{int(input_id):04d}"
dll_path = os.path.join("dlls", f"{id_str}.dll")
output_path = os.path.join("consts_check", f"{id_str}_c.txt")
if not os.path.isfile(dll_path):
print(f"[-] DLL not found: {dll_path}")
sys.exit(1)
constants = analyze_check_function(dll_path)
with open(output_path, 'w', encoding='utf-8') as f:
f.write("[" + ", ".join(f"0x{c:016x}" for c in constants) + "]\n")

if __name__ == "__main__":
main()

分析check后半段函数逻辑:

1
2
3
4
5
6
7
8
9
10
11
tag1 = 0xDC37C0E304978087uLL;
...
for ( i = 0; i <= 15; ++i )
{
v1 = v87[2 * i + 121];
v2 = &v87[2 * i + 120];
*v2 ^= *(_QWORD *)&a1[2 * (i % 4)];
v2[1] = v1;
if ( *(_OWORD *)&v87[2 * i + 120] >= tag1 )
return 0;
}

此部分中v1实际上是前16个oword的高位,v2是其低位,v2与a1进行循环xor,xor的结果写回v2,即a1中的任何值在xor运算后的结果均不能大于等于tag1的值,这里先patch中间值以通过校验,继续查看,下面是数轮循环:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
count = 0u;
v4 = cnt1[0];
v5 = cnt1[5];
v85 = cnt1[15] * cnt1[10];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v5;
tag1_1 = tag1;
out1 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v6 = cnt1[6];
v85 = cnt1[13] * cnt1[11];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v6;
tag1_1 = tag1;
out2 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v7 = cnt1[7];
v85 = cnt1[14] * cnt1[9];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v7;
tag1_1 = tag1;
out3 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v8 = cnt1[13];
v85 = cnt1[7] * cnt1[10];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v8;
tag1_1 = tag1;
out4 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v9 = cnt1[14];
v85 = cnt1[5] * cnt1[11];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v9;
tag1_1 = tag1;
out5 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v11 = cnt1[15];
v85 = cnt1[6] * cnt1[9];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v11;
tag1_1 = tag1;
v85 = *(_OWORD *)&out5 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&out4 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v12 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&out3 + tag1 - *(_OWORD *)&v12;
tag1_1 = tag1;
v85 = *(_OWORD *)&out2 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&out1 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v4;
tag1_1 = tag1;
v13 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&v13 + *(_OWORD *)&count;
tag1_1 = tag1;
count = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v14 = cnt1[1];
v15 = cnt1[4];
v85 = cnt1[15] * cnt1[10];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v15;
tag1_1 = tag1;
out1_1 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v16 = cnt1[6];
v85 = cnt1[12] * cnt1[11];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v16;
tag1_1 = tag1;
out2_1 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v17 = cnt1[7];
v85 = cnt1[14] * cnt1[8];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v17;
tag1_1 = tag1;
out3_1 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v18 = cnt1[12];
v85 = cnt1[7] * cnt1[10];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v18;
tag1_1 = tag1;
out4_1 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v19 = cnt1[14];
v85 = cnt1[4] * cnt1[11];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v19;
tag1_1 = tag1;
out5_1 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v21 = cnt1[15];
v85 = cnt1[6] * cnt1[8];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v21;
tag1_1 = tag1;
v85 = *(_OWORD *)&out5_1 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&out4_1 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v22 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&out3_1 + tag1 - *(_OWORD *)&v22;
tag1_1 = tag1;
v85 = *(_OWORD *)&out2_1 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&out1_1 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v14;
tag1_1 = tag1;
v23 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&count + tag1 - *(_OWORD *)&v23;
tag1_1 = tag1;
count = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v24 = cnt1[2];
v25 = cnt1[4];
v85 = cnt1[15] * cnt1[9];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v25;
tag1_1 = tag1;
v81 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v26 = cnt1[5];
v85 = cnt1[12] * cnt1[11];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v26;
tag1_1 = tag1;
v77 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v27 = cnt1[7];
v85 = cnt1[13] * cnt1[8];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v27;
tag1_1 = tag1;
v73 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v28 = cnt1[12];
v85 = cnt1[7] * cnt1[9];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v28;
tag1_1 = tag1;
v68 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v29 = cnt1[13];
v85 = cnt1[4] * cnt1[11];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v29;
tag1_1 = tag1;
v30 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v31 = cnt1[15];
v85 = cnt1[5] * cnt1[8];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v31;
tag1_1 = tag1;
v85 = *(_OWORD *)&v30 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&v68 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v32 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&v73 + tag1 - *(_OWORD *)&v32;
tag1_1 = tag1;
v85 = *(_OWORD *)&v77 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&v81 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v24;
tag1_1 = tag1;
v33 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&v33 + *(_OWORD *)&count;
tag1_1 = tag1;
count = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v34 = cnt1[3];
v35 = cnt1[4];
v85 = cnt1[14] * cnt1[9];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v35;
tag1_1 = tag1;
v82 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v36 = cnt1[5];
v85 = cnt1[12] * cnt1[10];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v36;
tag1_1 = tag1;
v78 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v37 = cnt1[6];
v85 = cnt1[13] * cnt1[8];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v37;
tag1_1 = tag1;
v74 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v38 = cnt1[12];
v85 = cnt1[6] * cnt1[9];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v38;
tag1_1 = tag1;
v69 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v39 = cnt1[13];
v85 = cnt1[4] * cnt1[10];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v39;
tag1_1 = tag1;
v40 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v41 = cnt1[14];
v85 = cnt1[5] * cnt1[8];
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v41;
tag1_1 = tag1;
v85 = *(_OWORD *)&v40 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&v69 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v70 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&v74 + tag1 - *(_OWORD *)&v70;
tag1_1 = tag1;
v85 = *(_OWORD *)&v78 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&v82 + *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
tag1_1 = tag1;
v85 = *(_OWORD *)&mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1) * v34;
tag1_1 = tag1;
v83 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
v85 = *(_OWORD *)&count + tag1 - *(_OWORD *)&v83;
tag1_1 = tag1;
count = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
if ( *(_OWORD *)&count == 0 )
return 0;

其中所用到的辅助函数是128位的mod运算,简化后是:

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

def b2nle(b, n):
return [int.from_bytes(b[i:i+n], byteorder='little', signed=False) for i in range(0, len(b), n)]

cnts = [0xdc37c0e304978087, 0x594b7f91f11228e5,
0x264f1c2a310e43aa, 0x06f62577ddb8f7c8, 0x2f5eef5c62186c64, 0x3b278b1ea0e08e88,
0x030b6b0678e48aee, 0x5857a70651b71bd1, 0x11328681bbf8806a, 0x46a52df6f08b2685,
0x5b5746a4910ca7fd, 0x04fce2f265662e21, 0x32a013dc0e0f538a, 0xfffec7ae2c6f8f79,
0x3b0ad6e24be21f00, 0xd285721394b26b6f, 0x49ff24112a0c1a2e, 0xf3a55fbbc4837e78,

0x65de31ef76b34c5e, 0xbf9224aa780960ba, 0x944c61fe664d8a46, 0x85ffaacd31f816d1,
0x5fe739de69b61b49, 0x4362ab9dfd8274e5, 0xc90b9e6ac29a84ec, 0x661807122a7615d7,
0x2367a1bf2b936d7c, 0x289e160527983def, 0xb0e4b274464c4bfd, 0x5222046dfef7b826,
0x6158769ed8530622, 0x056eabd584b51a70, 0xa5b7c08151fface8, 0xc7b8d0a6d71a6e00]

mode = cnts[0]
mcnt = cnts[2:18]
inp = bytearray.fromhex("ffac071f3fabda20d4f3a936840a1f9bf05e3650f645479145b99f6219357939")

qinp = b2nle(inp, 8)

for i in range(16):
mcnt[i] ^= qinp[i%4]
if mcnt[i] > mode: print("error"); exit(0)

tag = 0
out1 = mcnt[0xF] * mcnt[0xA] * mcnt[0x5]
out2 = mcnt[0xD] * mcnt[0xB] * mcnt[0x6]
out3 = mcnt[0xE] * mcnt[0x9] * mcnt[0x7]
out4 = mcnt[0x7] * mcnt[0xA] * mcnt[0xD]
out5 = mcnt[0x5] * mcnt[0xB] * mcnt[0xE]
out6 = mcnt[0x6] * mcnt[0x9] * mcnt[0xF]
tag += (out1 + out2 + out3 - out4 - out5 - out6) * mcnt[0x0]
out1 = mcnt[0xF] * mcnt[0xA] * mcnt[0x4]
out2 = mcnt[0xC] * mcnt[0xB] * mcnt[0x6]
out3 = mcnt[0xE] * mcnt[0x8] * mcnt[0x7]
out4 = mcnt[0x7] * mcnt[0xA] * mcnt[0xC]
out5 = mcnt[0x4] * mcnt[0xB] * mcnt[0xE]
out6 = mcnt[0x6] * mcnt[0x8] * mcnt[0xF]
tag -= (out1 + out2 + out3 - out4 - out5 - out6) * mcnt[0x1]
out1 = mcnt[0xF] * mcnt[0x9] * mcnt[0x4]
out2 = mcnt[0xC] * mcnt[0xB] * mcnt[0x5]
out3 = mcnt[0xD] * mcnt[0x8] * mcnt[0x7]
out4 = mcnt[0x7] * mcnt[0x9] * mcnt[0xC]
out5 = mcnt[0x4] * mcnt[0xB] * mcnt[0xD]
out6 = mcnt[0x5] * mcnt[0x8] * mcnt[0xF]
tag += (out1 + out2 + out3 - out4 - out5 - out6) * mcnt[0x2]
out1 = mcnt[0xE] * mcnt[0x9] * mcnt[0x4]
out2 = mcnt[0xC] * mcnt[0xA] * mcnt[0x5]
out3 = mcnt[0xD] * mcnt[0x8] * mcnt[0x6]
out4 = mcnt[0x6] * mcnt[0x9] * mcnt[0xC]
out5 = mcnt[0x4] * mcnt[0xA] * mcnt[0xD]
out6 = mcnt[0x5] * mcnt[0x8] * mcnt[0xE]
tag -= (out1 + out2 + out3 - out4 - out5 - out6) * mcnt[0x3]
tag %= mode
print(hex(tag))

但实际上这是在求mcnt这个矩阵的行列式并证明其线性无关,因此可以进一步简化:

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

def b2nle(b, n):
return [int.from_bytes(b[i:i+n], byteorder='little', signed=False) for i in range(0, len(b), n)]

cnts = [0xdc37c0e304978087, 0x594b7f91f11228e5,
0x264f1c2a310e43aa, 0x06f62577ddb8f7c8, 0x2f5eef5c62186c64, 0x3b278b1ea0e08e88,
0x030b6b0678e48aee, 0x5857a70651b71bd1, 0x11328681bbf8806a, 0x46a52df6f08b2685,
0x5b5746a4910ca7fd, 0x04fce2f265662e21, 0x32a013dc0e0f538a, 0xfffec7ae2c6f8f79,
0x3b0ad6e24be21f00, 0xd285721394b26b6f, 0x49ff24112a0c1a2e, 0xf3a55fbbc4837e78,

0x65de31ef76b34c5e, 0xbf9224aa780960ba, 0x944c61fe664d8a46, 0x85ffaacd31f816d1,
0x5fe739de69b61b49, 0x4362ab9dfd8274e5, 0xc90b9e6ac29a84ec, 0x661807122a7615d7,
0x2367a1bf2b936d7c, 0x289e160527983def, 0xb0e4b274464c4bfd, 0x5222046dfef7b826,
0x6158769ed8530622, 0x056eabd584b51a70, 0xa5b7c08151fface8, 0xc7b8d0a6d71a6e00]

mode = cnts[0]
mcnt = cnts[2:18]
inp = bytearray.fromhex("ffac071f3fabda20d4f3a936840a1f9bf05e3650f645479145b99f6219357939")
qinp = b2nle(inp, 8)

for i in range(16):
mcnt[i] ^= qinp[i%4]
if mcnt[i] > mode: print("error"); exit(0)

M = Matrix([mcnt[i:i+4] for i in range(0, 16, 4)])
tag = int(M.det() % mode)
print(hex(tag))

最后面的部分:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  v88[0] = 1;
memset(&v88[1], 0, 72);
v88[10] = 1;
memset(&v88[11], 0, 72);
v88[20] = 1;
memset(&v88[21], 0, 72);
v88[30] = 1;
v88[31] = 0;
memset(buf_, 0, sizeof(buf_));
for ( n15_1 = 0; n15_1 <= 15; ++n15_1 )
{
v42 = *((_QWORD *)&enc[n15_1 + 64] + 1);
v43 = &enc[n15_1 + 32];
*(_QWORD *)v43 = *(_QWORD *)&enc[n15_1 + 64];
*((_QWORD *)v43 + 1) = v42;
}
for ( n63 = 0; n63 <= 63; ++n63 )
{
if ( (tag2 >> n63) & 1 )
{
for ( n3_2 = 0; n3_2 <= 3; ++n3_2 )
{
for ( n3_1 = 0; n3_1 <= 3; ++n3_1 )
{
v44 = &enc[4 * n3_2 + 16 + n3_1];
*(_QWORD *)v44 = 0;
*((_QWORD *)v44 + 1) = 0;
for ( n3 = 0; n3 <= 3; ++n3 )
{
v45 = *(_QWORD *)&enc[4 * n3_2 + 16 + n3_1];
v46 = *((_QWORD *)&enc[4 * n3_2 + 16 + n3_1] + 1);
v47 = *(_QWORD *)&enc[4 * n3_2 + 48 + n3];
v48 = *(_QWORD *)&enc[4 * n3 + 32 + n3_1];
v49 = v48 * *((_QWORD *)&enc[4 * n3_2 + 48 + n3] + 1) + v47 * *((_QWORD *)&enc[4 * n3 + 32 + n3_1] + 1);
v50 = v48 * (unsigned __int128)v47;
*((_QWORD *)&v50 + 1) += v49;
v85 = v50;
tag1_1 = tag1;
v51 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
*(_QWORD *)&v52 = v45;
*((_QWORD *)&v52 + 1) = v46;
enc[4 * n3_2 + 16 + n3_1] = *(_OWORD *)&v51 + v52;
LODWORD(v45) = 4 * n3_2 + n3_1;
v85 = enc[(int)v45 + 16];
tag1_1 = tag1;
enc[(int)v45 + 16] = (__int128)mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
}
}
}
for ( n15_2 = 0; n15_2 <= 15; ++n15_2 )
{
v53 = *((_QWORD *)&enc[n15_2 + 16] + 1);
v54 = &enc[n15_2 + 48];
*(_QWORD *)v54 = *(_QWORD *)&enc[n15_2 + 16];
*((_QWORD *)v54 + 1) = v53;
}
}
for ( n3_5 = 0; n3_5 <= 3; ++n3_5 )
{
for ( n3_4 = 0; n3_4 <= 3; ++n3_4 )
{
v55 = &enc[4 * n3_5 + 16 + n3_4];
*(_QWORD *)v55 = 0;
*((_QWORD *)v55 + 1) = 0;
for ( n3_3 = 0; n3_3 <= 3; ++n3_3 )
{
v56 = *(_QWORD *)&enc[4 * n3_5 + 16 + n3_4];
v57 = *((_QWORD *)&enc[4 * n3_5 + 16 + n3_4] + 1);
v58 = *(_QWORD *)&enc[4 * n3_5 + 32 + n3_3];
v59 = *(_QWORD *)&enc[4 * n3_3 + 32 + n3_4];
v60 = v59 * *((_QWORD *)&enc[4 * n3_5 + 32 + n3_3] + 1) + v58 * *((_QWORD *)&enc[4 * n3_3 + 32 + n3_4] + 1);
v61 = v59 * (unsigned __int128)v58;
*((_QWORD *)&v61 + 1) += v60;
v85 = v61;
tag1_1 = tag1;
v62 = mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
*(_QWORD *)&v63 = v56;
*((_QWORD *)&v63 + 1) = v57;
enc[4 * n3_5 + 16 + n3_4] = *(_OWORD *)&v62 + v63;
LODWORD(v56) = 4 * n3_5 + n3_4;
v85 = enc[(int)v56 + 16];
tag1_1 = tag1;
enc[(int)v56 + 16] = (__int128)mod((unsigned __int64 *)&v85, (unsigned __int64 *)&tag1_1);
}
}
}
for ( n15_3 = 0; n15_3 <= 15; ++n15_3 )
{
v64 = *((_QWORD *)&enc[n15_3 + 16] + 1);
v65 = &enc[n15_3 + 32];
*(_QWORD *)v65 = *(_QWORD *)&enc[n15_3 + 16];
*((_QWORD *)v65 + 1) = v64;
}
}
enc[0] = 0x65DE31EF76B34C5EuLL;
enc[1] = 0xBF9224AA780960BAuLL;
enc[2] = 0x944C61FE664D8A46uLL;
enc[3] = 0x85FFAACD31F816D1uLL;
enc[4] = 0x5FE739DE69B61B49uLL;
enc[5] = 0x4362AB9DFD8274E5uLL;
enc[6] = 0xC90B9E6AC29A84ECuLL;
enc[7] = 0x661807122A7615D7uLL;
enc[8] = 0x2367A1BF2B936D7CuLL;
enc[9] = 0x289E160527983DEFuLL;
enc[10] = 0xB0E4B274464C4BFDuLL;
enc[11] = 0x5222046DFEF7B826uLL;
enc[12] = 0x6158769ED8530622uLL;
enc[13] = 0x56EABD584B51A70uLL;
enc[14] = 0xA5B7C08151FFACE8uLL;
enc[15] = 0xC7B8D0A6D71A6E00uLL;
return (unsigned int)((__int64 (__fastcall *)(__int128 *, _QWORD *, __int64))unk_268936AF608)(enc, v88, 256) == 0;
}

这部分很简单,注意到这是刚刚的4×4矩阵在tag1整数环上的tag2次幂的快速幂计算,直接同构就行,完整同构:

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

def b2nle(b, n):
return [int.from_bytes(b[i:i+n], byteorder='little', signed=False) for i in range(0, len(b), n)]

def n2ble(na, n):
b = bytearray()
for a in na: b.extend(a.to_bytes(n, byteorder='little'))
return b

cnts = [0xdc37c0e304978087, 0x594b7f91f11228e5,
0x264f1c2a310e43aa, 0x06f62577ddb8f7c8, 0x2f5eef5c62186c64, 0x3b278b1ea0e08e88,
0x030b6b0678e48aee, 0x5857a70651b71bd1, 0x11328681bbf8806a, 0x46a52df6f08b2685,
0x5b5746a4910ca7fd, 0x04fce2f265662e21, 0x32a013dc0e0f538a, 0xfffec7ae2c6f8f79,
0x3b0ad6e24be21f00, 0xd285721394b26b6f, 0x49ff24112a0c1a2e, 0xf3a55fbbc4837e78,

0x65de31ef76b34c5e, 0xbf9224aa780960ba, 0x944c61fe664d8a46, 0x85ffaacd31f816d1,
0x5fe739de69b61b49, 0x4362ab9dfd8274e5, 0xc90b9e6ac29a84ec, 0x661807122a7615d7,
0x2367a1bf2b936d7c, 0x289e160527983def, 0xb0e4b274464c4bfd, 0x5222046dfef7b826,
0x6158769ed8530622, 0x056eabd584b51a70, 0xa5b7c08151fface8, 0xc7b8d0a6d71a6e00]

mode = cnts[0]
power = 0x594b7f91f11228e5
mcnt = cnts[2:18]
encs = cnts[18:34]
inp = bytearray.fromhex("ffac071f3fabda20d4f3a936840a1f9bf05e3650f645479145b99f6219357939")
qinp = b2nle(inp, 8)

for i in range(16):
mcnt[i] ^= qinp[i%4]
if mcnt[i] > mode: print("error"); exit(0)

M = Matrix([mcnt[i:i+4] for i in range(0, 16, 4)])
if int(M.det() % mode) == 0: raise ValueError("Matrix is singular modulo mode!")

def matrix_pow_mod(matrix, power, modulus):
n = matrix.shape[0]
result = Matrix.eye(n)
base = matrix.applyfunc(lambda x: x % modulus)
while power > 0:
if power & 1: result = (result * base).applyfunc(lambda x: x % modulus)
base = (base * base).applyfunc(lambda x: x % modulus)
power >>= 1
return result

R = matrix_pow_mod(M, power, mode)
result = [int(x) for x in R]
t = n2ble(result,16).hex()
for i in range(16): print(t[32*i:32*i+32])
try:
assert result == encs
except:
print("Not equal")

接下来实现check的完整逆向函数:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import os
import sys
import ast
from sympy import Matrix
from math import gcd
import sqlite3
import ast

def load_func_name_to_dll_map(db_path="exported_functions.db"):
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("SELECT id, exported_functions FROM dll_exports")
mapping = {}
for dll_id, funcs_text in cur.fetchall():
try:
funcs = ast.literal_eval(funcs_text)
if not isinstance(funcs, list):
continue
except Exception:
continue
for f in funcs:
mapping[f] = dll_id
conn.close()
return mapping

def compute_func_tagkey(func_name, func_to_dll, matrix_lines, my_id, orders):
dll_id = func_to_dll.get(func_name)
if dll_id is None:
raise KeyError(f"Function {func_name} not found in exported_functions.db")
my_id_str = str(my_id).zfill(4)
try:
my_pos = orders.index(my_id_str)
except ValueError:
raise KeyError(f"Current DLL {my_id_str} not found in orders list")
col_idx = int(dll_id)
tagkey = 0
for weight, prev_id in enumerate(orders[:my_pos]):
row_idx = int(prev_id)
bit = matrix_lines[row_idx][col_idx]
if bit == '1':
tagkey += weight
return tagkey

def xor_first_dword(data, tagkey):
val = int.from_bytes(data[:4], byteorder='little')
val ^= (tagkey & 0xFFFFFFFF)
data[:4] = val.to_bytes(4, byteorder='little')

def poly_mul(a, b):
res = bytearray(32)
carry = 0
for i in range(32):
total = carry
for j in range(i + 1):
total += a[j] * b[i - j]
res[i] = total & 0xFF
carry = total >> 8
return res

def load_const(buf, offsets, na, n):
for a, offset in zip(na, offsets):
buf[offset:offset + n] = a.to_bytes(n, byteorder='little')
return buf

def func_A(inp, qkey):
sbox = n2ble(qkey, 8)
inp = bytearray([sbox[byte] for byte in inp])
return inp

def func_B(inp, qkey):
perm = n2ble(qkey, 8)
result = bytearray(32)
for i in range(32):
result[i] = inp[perm[i]]
return result

def func_C(inp, qkey):
key = load_const(bytearray(32), [0, 8, 15, 23], qkey, 8)
tag = inp[0] & 1
inp[0] |= 1
v3 = bytearray(32)
v3[0] = 1
v2 = bytearray(64)
v2[32:] = inp
for byte_idx in range(32):
byte_val = key[byte_idx]
for bit in range(8):
if (byte_val >> bit) & 1:
v3 = poly_mul(v3, v2[32:])
v2[32:] = poly_mul(v2[32:], v2[32:])
result = v3
result[0] ^= tag ^ 1
return result

def b2nle(b, n):
return [int.from_bytes(b[i:i+n], byteorder='little') for i in range(0, len(b), n)]

def n2ble(na, n):
b = bytearray()
for a in na: b.extend(int(a).to_bytes(n, byteorder='little'))
return bytes(b)

def read_cnts_c(config_id):
path = os.path.join("consts_check", f"{config_id}_c.txt")
if not os.path.exists(path):
raise FileNotFoundError(path)
txt = open(path, "r", encoding="utf-8").read().strip()
cnts = ast.literal_eval(txt)
if not isinstance(cnts, list) or len(cnts) != 34:
raise ValueError("Invalid _c.txt format, expected 34 ints")
mode = int(cnts[0])
power = int(cnts[1])
mcnt = [int(x) for x in cnts[2:18]]
encs = [int(x) for x in cnts[18:34]]
return mode, power, mcnt, encs

def read_config_txt(config_id):
path = os.path.join("consts", f"{config_id}.txt")
lines = []
with open(path, "r", encoding="utf-8") as f:
for l in f:
s = l.strip()
if not s or s.startswith("#"): continue
parts = s.split("\t")
if len(parts) < 3: raise ValueError("Invalid config line: " + l)
func_name = parts[0].strip()
typ = parts[1].strip()
qkey = ast.literal_eval(parts[2].strip())
lines.append((func_name, typ, qkey))
return lines


def matrix_pow_mod_sympy(A, power, modulus):
n = A.shape[0]
result = Matrix.eye(n)
base = A.copy()
base = base.applyfunc(lambda x: int(x) % modulus)
while power > 0:
if power & 1: result = (result * base).applyfunc(lambda x: int(x) % modulus)
base = (base * base).applyfunc(lambda x: int(x) % modulus)
power >>= 1
return result.applyfunc(lambda x: int(x) % modulus)

def solve_for_qs_direct(mode, power, mcnt_file, encs):
def mat_from_flat(flat, n=4): return [flat[i*n:(i+1)*n] for i in range(n)]

def mat_mul(X, Y, mod):
n, k, m = len(X), len(Y), len(Y[0])
Z = [[0]*m for _ in range(n)]
for i in range(n):
Xi, Zi = X[i], Z[i]
for t in range(k):
a = Xi[t] % mod
if a == 0: continue
Yt = Y[t]
for j in range(m): Zi[j] = (Zi[j] + a * (Yt[j] % mod)) % mod
return Z

def eye(n): return [[1 if i == j else 0 for j in range(n)] for i in range(n)]

def mat_pow(M, exp, mod):
n = len(M)
assert n == len(M[0])
R = eye(n)
B = [[x % mod for x in row] for row in M]
e = exp
while e > 0:
if e & 1: R = mat_mul(R, B, mod)
e >>= 1
if e: B = mat_mul(B, B, mod)
return R

p = mode
e = power
p2 = p * p
p3 = p2 * p
p4 = p3 * p
N = (p4 - 1) * (p4 - p) * (p4 - p2) * (p4 - p3)
if gcd(e, N) != 1: raise ValueError("cannot invert power with |GL(4, p)|")
d = pow(e, -1, N)
Enc = mat_from_flat(encs)
M_recovered = mat_pow(Enc, d, p)
M_flat = [M_recovered[r][c] for r in range(4) for c in range(4)]
q_candidates = [None] * 4
for i in range(16):
col = i % 4
q_val = M_flat[i] ^ mcnt_file[i]
if q_candidates[col] is None:
q_candidates[col] = q_val
elif q_candidates[col] != q_val: raise ValueError(f"q{col} not same: {q_candidates[col]}, {q_val}")

return q_candidates

def inv_func_C_bytes(output_bytes, qkey):
def u256_from_bytes_le(b): return int.from_bytes(b, 'little')
def u256_to_bytes_le(x): return (x & ((1 << 256) - 1)).to_bytes(32, 'little')
def build_key_u256_from_qkey(qkey_u64):
key = bytearray(32)
offs = [0, 8, 15, 23]
for v, off in zip(qkey_u64, offs): key[off:off+8] = int(v).to_bytes(8, 'little')
return int.from_bytes(key, 'little')

def pow_mod_2_256(base_u256, exp_u256): return pow(base_u256, exp_u256, 1 << 256)

if len(output_bytes) != 32: raise ValueError("output must be 32 bytes")

K = build_key_u256_from_qkey(qkey)
if (K & 1) == 0: raise ValueError("K (derived from qkey) is even; inversion requires K odd. ")
inv_exp_mod = 1 << 254
K_inv = pow(K, -1, inv_exp_mod)
out = bytearray(output_bytes)
candidates = []
for tag in (0, 1):
R = bytearray(out)
R[0] ^= (tag ^ 1)
R_int = u256_from_bytes_le(R)
X_int = pow_mod_2_256(R_int, K_inv)
X_bytes = u256_to_bytes_le(X_int)
inp = bytearray(X_bytes)
if tag == 0: inp[0] &= 0xFE
else: inp[0] |= 0x01
candidates.append(bytes(inp))

def hamming_weight(b): return sum(bin(byte).count('1') for byte in b)

cand0, cand1 = candidates
if hamming_weight(cand0) >= hamming_weight(cand1): return cand0
else: return cand1

def inv_func_A_bytes(out_bytes, qkey):
sbox = n2ble(qkey, 8)
inv = [0]*256
for i,v in enumerate(sbox): inv[v] = i
return bytes(inv[b] for b in out_bytes)

def inv_func_B_bytes(out_bytes, qkey):
perm = n2ble(qkey, 8)[:32]
inp = bytearray(32)
for i in range(32): inp[perm[i]] = out_bytes[i]
return bytes(inp)

def reverse_check_full(config_id, matrix_lines, my_id, func_to_dll, orders):
print("[*] Reading const files for id:", config_id)
mode, power, mcnt_file, encs = read_cnts_c(config_id)
qvals = solve_for_qs_direct(mode, power, mcnt_file, encs)
M_entries = [mcnt_file[i] ^ qvals[i % 4] for i in range(16)]
M = Matrix([M_entries[i:i+4] for i in range(0, 16, 4)])
R = matrix_pow_mod_sympy(M, power, mode)
if [int(x) for x in R] != encs: print("[!] verification failed: M^power != encs (mod mode)")
else: print("[*] matrix verification ok")
cur = n2ble(qvals, 8)
if len(cur) != 32: raise RuntimeError("constructed mid inp length != 32")
print("[*] Recovered intermediate bytes:", cur.hex())
cfg = read_config_txt(config_id)
print("[*] Loaded %d config steps" % len(cfg))
for step_idx, (func_name, typ, qkey) in enumerate(reversed(cfg)):
func_name = cfg[len(cfg) - 1 - step_idx][0]
tagkey = compute_func_tagkey(func_name, func_to_dll, matrix_lines, my_id, orders)
if typ == "A":
tmp = bytearray(inv_func_A_bytes(cur, qkey))
assert func_A(tmp.copy(), qkey) == cur
xor_first_dword(tmp, tagkey)
cur = tmp
elif typ == "B":
tmp = bytearray(inv_func_B_bytes(cur, qkey))
assert func_B(tmp.copy(), qkey) == cur
xor_first_dword(tmp, tagkey)
cur = tmp
elif typ == "C":
tmp = bytearray(inv_func_C_bytes(cur, qkey))
assert func_C(tmp.copy(), qkey) == cur
xor_first_dword(tmp, tagkey)
cur = tmp
else: raise ValueError("Unknown type in config: " + str(typ))
# print(cur.hex(), tagkey)
print("[*] Done.")
return cur

if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python re_check.py <id>")
sys.exit(1)
cfg_id = sys.argv[1].zfill(4)
my_id = int(cfg_id)
script_dir = os.path.dirname(os.path.abspath(__file__))
orders_path = os.path.join(script_dir, "order.txt")
matrix_path = os.path.join(script_dir, "matrix.txt")
with open(orders_path, "r") as f: orders = [line.strip() for line in f if line.strip()]
with open(matrix_path, "r") as f: matrix_lines = [line.strip() for line in f]
func_to_dll = load_func_name_to_dll_map("exported_functions.db")
res = reverse_check_full(cfg_id, matrix_lines, my_id, func_to_dll, orders)
print("Recovered (hex):", res.hex())
license_path = os.path.join(script_dir, "license.txt")
with open(license_path, "a", encoding="utf-8") as f: f.write(cfg_id + "\t" + res.hex() + "\n")

解得0000.dll在位置0的明文是27fccd48105201db4cf5fb642b9456be2db4016839d67ee6a1aee9330dfebcef,带入验证发现正确,即逆向成功

#9.6 自动化求解

编写总控脚本,先手动复制文件夹并提取所有常量(同路径会导致线程竞争):

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
import os
import sys
import subprocess
import platform

PSUTIL_AVAILABLE = False
try:
import psutil
PSUTIL_AVAILABLE = True
except ImportError:
pass

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
EXTRACT_SCRIPT = os.path.join(BASE_DIR, "extract_const_sqlite.py")

def set_high_priority():
system = platform.system()
try:
if system == "Windows" and PSUTIL_AVAILABLE:
p = psutil.Process()
p.nice(psutil.HIGH_PRIORITY_CLASS)
except (OSError, PermissionError, AttributeError) as e:
print(f"[WARN] Failed to set high priority: {e}", file=sys.stderr)

def run_single(id_str):
cmd = [sys.executable, EXTRACT_SCRIPT, id_str]
try:
proc = subprocess.Popen(
cmd,
cwd=BASE_DIR,
stdout=sys.stdout,
stderr=sys.stderr,
universal_newlines=True
)
proc.wait()
if proc.returncode != 0:
print(f"[ERROR] extract_const.py {id_str} exited with code {proc.returncode}", file=sys.stderr)
except Exception as e:
print(f"[EXCEPTION] Failed to run {id_str}: {e}", file=sys.stderr)

def main():
print("Starting single-threaded extract_const.py for IDs 0000–9999 with high CPU priority...", file=sys.stderr)
set_high_priority()

for i in range(范围):
id_str = f"{i:04d}"
print(f"\n[INFO] Processing ID: {id_str}", file=sys.stderr)
run_single(id_str)

if __name__ == "__main__":
main()

以及check中的常量:

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
import os
import sys
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from tqdm import tqdm

SCRIPT_NAME = "extract_const_check.py"
TOTAL = 10000
MAX_WORKERS = 32
TIMEOUT = 60

def run_single(id_int):
id_str = f"{id_int:04d}"
try:
result = subprocess.run(
[sys.executable, SCRIPT_NAME, id_str],
capture_output=True,
text=True,
timeout=TIMEOUT
)
return id_str, result.returncode == 0, None
except subprocess.TimeoutExpired:
return id_str, False, "timeout"
except Exception as e:
return id_str, False, str(e)

def main():
if not os.path.isfile(SCRIPT_NAME):
print(f"[!] Error: {SCRIPT_NAME} not found in current directory.")
sys.exit(1)
print(f"[+] Starting batch execution with {MAX_WORKERS} threads (IDs 0000–9999)...")
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
future_to_id = {
executor.submit(run_single, i): i for i in range(TOTAL)
}
with tqdm(total=TOTAL, desc="Processing", unit="id") as pbar:
for future in as_completed(future_to_id):
# print(future.result())
pbar.update(1)
print(f"\n[+] Batch finished!")

if __name__ == "__main__":
main()

然后再调用总求解脚本:

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
58
59
60
61
62
63
64
65
66
67
import os
import sys
import subprocess
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock

TOTAL_IDS = 10000
MAX_WORKERS = 32
SCRIPT = "re_check.py"
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONSTS_DIR = os.path.join(BASE_DIR, "consts")
progress_lock = Lock()
completed_count = 0

def process_id(id_str):
global completed_count
expected_input = os.path.join(CONSTS_DIR, f"{id_str}.txt")
if not os.path.isfile(expected_input):
with progress_lock: completed_count += 1
print(f"[SKIP] {id_str}: {expected_input} not found", file=sys.stderr)
return
try:
cmd = [sys.executable, os.path.join(BASE_DIR, SCRIPT), id_str]
proc = subprocess.Popen(
cmd,
cwd=BASE_DIR,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True
)
prefix = f"[{id_str}] "
for line in iter(proc.stdout.readline, ''): print(prefix + line.rstrip(), flush=True)
proc.wait()
with progress_lock:
completed_count += 1
current = completed_count
if proc.returncode == 0: print(f"[INFO] {id_str}: succeeded (progress: {current}/{TOTAL_IDS})", file=sys.stderr)
else: print(f"[ERROR] {id_str}: failed (rc={proc.returncode}, progress: {current}/{TOTAL_IDS})", file=sys.stderr)

except Exception as e:
with progress_lock:
completed_count += 1
print(f"[EXCEPTION] {id_str}: {e} (progress: {completed_count}/{TOTAL_IDS})", file=sys.stderr)
raise

def main():
ids = [f"{i:04d}" for i in range(TOTAL_IDS)]
print(f"Starting {MAX_WORKERS}-threaded processing of {len(ids)} IDs (0000–9999) using only '{SCRIPT}'")
print(f"Will skip any ID without existing {os.path.join('consts', '<id>.txt')}")
start_time = time.time()
global completed_count
completed_count = 0
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {executor.submit(process_id, id_str): id_str for id_str in ids}
for future in as_completed(futures):
id_str = futures[future]
try:
future.result()
except Exception: pass
elapsed = time.time() - start_time
print(f"\nAll {len(ids)} IDs processed in {elapsed:.2f} seconds.", file=sys.stderr)

if __name__ == "__main__":
main()

哈希:600abf28e03c73471a73bb909210dc2d2b4e98c7577d6b71299d2e54d693d14d

Its_l1ke_10000_spooO0o0O0oOo0o0O0O0OoOoOOO00o0o0Ooons@flare-on.com

结语

虽然自己已经经历过不少国际赛小众又独特的题目的洗礼,但Flare-On的不少题目仍让我感觉耳目一新,在传统单调的解密/约束型逆向的基础上融合了不少其他方向的知识或者出题人的小巧思(比如第三题的伪PDF),而不局限于传统的分析逻辑-正向同构-逆向解密的线性形式。大部分题目也都可以或多或少地学到新东西,练习新的思路和解题方式(比如第7题的自动解混淆和第9题的自动化逆向等)。9个题的难度梯度逐级递进,与闯关的比赛形式相得益彰,从前几个题的“分钟级”难度,到第4、5、7、8题的“小时级”难度,再到最后的第9题的“天级”难度,让我这种喜欢赤石的人觉得打起来是一种享受而非坐牢(真的吗?)。今年的难关已经悉数攻克,明年继续来战!