题目给了一个带一个很滑稽的图标的程序。运行一下会发现它长得像某种安装器。strings一下可以看到一些像是
testsoft Install System v2.48
XXXXsofttestS.exehead
http:/tests.sf.net/test_Error
强行打了打码。Google 搜一下 "Install System v2.48" 即可得知打码之前应该是 Nullsoft Install System v2.48。
所以这看起来外面包了一层 NSIS,考虑到运行起来之后的那个输入的界面长的也很像 NSIS 本体,估计是写了一些 NSIS 脚本或者插件来执行验证。
NSIS 安装包 7-Zip 是可以解压的,不过直接打开会报错,先使用任意你最喜欢的十六进制编辑器打开 CrackMe.exe,观察一下,发现文件尾部附带的数据头部的 NullsoftInst 被改成了 XXXXsoftInst,改回来之后用 7-Zip 就可以打开啦,可以把包含 NSIS 脚本的反汇编结果和插件dll的所有内容提取出来。记得使用一个早一点版本的 7-Zip,例如 9.38,新版本移除了反汇编 NSIS 脚本的功能。
7-Zip 并没有把所有的函数偏移识别出来,不过这不大影响我们分析。可以看到显示 Page 0 的函数是 func_747,其调用 SetTimer 跑了一个反调试的 timer,每秒扫一次进程,kill 名字长得像调试器的,对于我们静态分析选手没有影响。
验证逻辑在按钮的 OnClick 函数里,如下,稍微手动整理一下之后也很清晰,其中 Bamer::P 之类的函数是在 Bamer.dll 里实现的,可以找一个 NSIS 插件的头文件对着看。
Bamer 中除 G 以外的函数大致功能如下,分析过程略去,都是看看代码就能看出来的:
可以看到在 NSIS 脚本中和插件中均有验证逻辑,梳理一下:
NSIS 脚本中的验证逻辑有两处,分别是:
这里就是简单的稍微写的别扭了一点的和固定串的比较,可以直接还原,答案是 2018TSCRCTF
(腾讯的人要哭了,TSCR 是什么?)。
另一处是:
乍一看吓人,仔细一看会发现是个线性方程组,可以解出答案是 WelcomeHave
。
考虑到要把输入喂回去,得能进行 Bamer::A 的反向操作(即加密),看一下其相比标准的 AES-128 改动的地方:
Key Schedule 的过程翻译成 Python 代码如下:
可以看到基本是改的面目全非,而且写在内存里的 layout 也是特意扭了一下,后面 AddRoundKey 的时候有专门处理,估计是为了防止别人直接把轮密钥 dump 出来用吧。另外这里面有个可能是 bug 的东西,Rcon and 上的值没有移到高位去,因此这里总是 0。将正常 layout 的轮密钥算出来备用,之后只要随便找一个实现改一下 ShiftRows 即可用来加密/解密:
接下来看 G 这个验证函数是在干啥,主要逻辑在 10002309 中。大致看一下会发现其先用传入的 16 字节作为 key 解密了 144 字节的数据:
然后根据解密出来的数据生成了三个 8 字节的 buffer,接下来把输入的另一个四进制数当作“指令”开始进行以下操作:
总结一下,就是棋盘上的八对字母之间要连起来,连线不能交叉,最后必须占满整个棋盘。这听上去就像是一种非常经典的逻辑谜题,几乎可以肯定不是这个 CrackMe 的作者自己想出来的,也几乎可以肯定只要我们知道它叫什么,就能搜到现成的求解器。于是我们去看看 Nikoli 出版过的著名 logic puzzle 列表,翻着翻着可以翻到一种叫 Numberlink 的东西,看起来跟上面描述的规则一模一样。搜一下 "Numberlink solver",可以找到一个 跑的比香港记者还快的求解器 和一个 能告诉我们解的个数的求解器。
这里我们用前者试试:
果然跑的比香港记者还快,接下来只要简单写几行代码将这个解转换为上文提到的“指令”即可:
运行即可得到答案 1113333333333000000333330033331113033111333030000222222111220000003333333333311111333333333111113112122222222221133333333000333333331111
。利用 Bamer.dll 自身将其转换为 36 进制,得到 32WSFUPIFV9TYJWWPH14NZZ85YDHXOLO37ATG4IYC4ZCDIKCA7EJ9
。
至此,我们已经得到构造序列号的所有要素了。
将之前得到的答案前面拼上 WelcomeHave
,加密,前面再拼上 2018TSCRCTF
,编码即可得到最后的序列号 MhAyOFQSR2JDUEb0OUopUQkh5Ax23nEnCLxoBT06JRd7EdLrwooWsXQG68wLcneAqDy3UU78AgdrYnabVL0M9vd852girNqF9a3F
。
由于 Bamer 中的 C 函数对于输入的大小写不敏感,因此翻转作为上面的小游戏答案中的字母之后前面的验证依然都能过,但问题在于魔改 Base64 编码后必须没有 +/
以及 FNV-1a hash 验证必须过。注意到小游戏答案中有 40 个可以翻转大小写的字母,因此这部分可能的解有 2 ** 40
个,不是很多,我们可以枚举一遍。由于 FNV-1a hash 有 32 位,假设它性质比较良好的话,不考虑 +/
的问题最后应该能得到接近 256 个答案,写个程序试一试:
开上 256 个核,跑个 20 分钟即可得到所有解,共 237 个,去掉包含 +/
的之后还剩下 24 个,如下,其中第一行的是“预期解",但其他的输入原来的程序也会得到 You win!
提示。
Function OnClick
System::Call user32::GetWindowText(p$_1_,t.s,i1024)
; Call Initialize_____Plugins
; File $PLUGINSDIR\System.dll
; SetDetailsPrint lastused
; Push user32::GetWindowText(p$_1_,t.s,i1024)
; CallInstDLL $PLUGINSDIR\System.dll Call
Pop $0
StrCpy $_3_ $0
Bamer::P $0 ;; Is Alphanumeric?
; Call Initialize_____Plugins
; AllowSkipFiles off
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $0
; CallInstDLL $PLUGINSDIR\Bamer.dll P
Pop $R0
StrCmp $R0 0 0 label_658
Goto boom
label_658:
StrLen $1 $0
IntCmp $1 100 0 boom boom ;; Input size should be 100
Bamer::B $0 $1 ;; Modified Base64 decode
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $1
; Push $0
; CallInstDLL $PLUGINSDIR\Bamer.dll B
Pop $2 ;; $2 = decoded data
StrCpy $3 $2 11 0 ;; $3 = first 11 char of decoded data
Push $3
Call CheckPart1 ;; Must return true
Pop $R0
StrCmp $R0 False 0 label_673
Goto boom
label_673:
Push $3
Call func_125 ;; $3 = $3 + "00010", pad 11 bytes key to 16 bytes
Pop $3
StrCpy $4 $2 64 11 ;; data[11:11+64]
StrLen $R0 $4
StrCmp $R0 64 label_680
Goto boom
label_680:
Bamer::A $4 64 $3 ;; ModifiedAESDecrypt(data[11:11+64], 64, $3)
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $3
; Push 64
; Push $4
; CallInstDLL $PLUGINSDIR\Bamer.dll A
Pop $R0
StrCmp $R0 0 0 label_690 ;; Must return 1
Goto boom
label_690:
Pop $R1 ;; Decrypted data[11:11+64]
StrCpy $5 $R1 11 0 ;; first 11 bytes
StrCpy $_6_ $5
Push $5
Call func_133 ;; CheckPart2First11Bytes, Answer: WelcomeHave
Pop $R0
StrCmp $R0 False 0 label_698
Goto boom
label_698:
StrCpy $6 $R1 53 11 ;; next 53 bytes
Bamer::C 36 4 $6
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $6
; Push 4
; Push 36
; CallInstDLL $PLUGINSDIR\Bamer.dll C
Pop $R2
Push $_6_ ;; $_6_ = "WelcomeHave"
Call func_615
Pop $5 ;; $5 = "WelcomeToHaveFun" (len = 16)
Bamer::G $5 $R2
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $R2
; Push $5
; CallInstDLL $PLUGINSDIR\Bamer.dll G
Pop $R0
StrCmp $R0 0 0 label_719
Goto boom
label_719:
Bamer::F $_3_ ;; FinalCheck: FNVHash(entire_input) == 0x400D49F3
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $_3_
; CallInstDLL $PLUGINSDIR\Bamer.dll F
Pop $R0
StrCmp $R0 0 0 win
Goto boom
win:
StrLen $R1 XX+2IHcragE= ;; You win!
Bamer::B XX+2IHcragE= $R1
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $R1
; Push XX+2IHcragE=
; CallInstDLL $PLUGINSDIR\Bamer.dll B
Pop $R2
MessageBox MB_OK $R2
Return
boom:
StrLen $R1 U0JtakdiZX6wc1UxIR==
Bamer::B U0JtakdiZX6wc1UxIR== $R1 ;; Wrong answer!
; Call Initialize_____Plugins
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $R1
; Push U0JtakdiZX6wc1UxIR==
; CallInstDLL $PLUGINSDIR\Bamer.dll B
Pop $R2
MessageBox MB_OK|MB_ICONINFORMATION $R2
FunctionEnd
Function CheckPart1
Exch $R8
; Push $R8
; Exch
; Pop $R8
StrLen $8 JTZmLD/8Sh6MOmd=
Bamer::B JTZmLD/8Sh6MOmd= $8
; Call Initialize_____Plugins
; AllowSkipFiles on
; File $PLUGINSDIR\Bamer.dll
; SetDetailsPrint lastused
; Push $8
; Push JTZmLD/8Sh6MOmd=
; CallInstDLL $PLUGINSDIR\Bamer.dll B
Pop $R9
StrLen $R7 $R8
IntCmp $R7 11 label_99 label_120 label_120 ;; input size must be 11
label_99:
IntOp $R6 0 + 0 ;; $R6 is loop index
label_100:
StrCpy $R1 $R8 1 $R6 ;; $R1 = input[$R6]
Push $R1
Call Ord
Pop $R1
IntOp $R2 $R1 ^ 0x17
IntOp $R3 $R2 - $R6 ;; (input[i] ^ 0x17) - i should equal to predefined answer
StrCpy $R4 $R9 1 $R6
Push $R4
Call Ord
Pop $R4
IntCmp $R3 $R4 0 label_120 label_120
StrCmp $R6 10 0 label_113
Goto label_115
label_113:
IntOp $R6 $R6 + 1
Goto label_100
label_115:
StrCpy $0 True
Exch $0
; Push $0
; Exch
; Pop $0
Return
label_120:
StrCpy $0 False
Exch $0
; Push $0
; Exch
; Pop $0
FunctionEnd
Function func_133
Exch $0
; Push $0
; Exch
; Pop $0
StrLen $1 $0
IntCmp $1 11 0 label_fail label_fail
StrCpy $1 $0 1 0
StrCpy $2 $0 1 1
StrCpy $3 $0 1 2
StrCpy $4 $0 1 3
StrCpy $5 $0 1 4
StrCpy $6 $0 1 5
StrCpy $7 $0 1 6
StrCpy $8 $0 1 7
StrCpy $9 $0 1 8
StrCpy $_4_ $0 1 9
StrCpy $_5_ $0 1 10
Push $1
Call Ord
Pop $1
Push $2
Call Ord
Pop $2
Push $3
Call Ord
Pop $3
Push $4
Call Ord
Pop $4
Push $5
Call Ord
Pop $5
Push $6
Call Ord
Pop $6
Push $7
Call Ord
Pop $7
Push $8
Call Ord
Pop $8
Push $9
Call Ord
Pop $9
Push $_4_
Call Ord
Pop $_4_
Push $_5_
Call Ord
Pop $_5_
IntOp $R2 $1 * 18334
IntOp $R3 $2 * 19371
IntOp $R2 $R2 + $R3
IntOp $R4 $3 * 15568
IntOp $R3 $4 * 19321
IntOp $R4 $3 * 17784
IntOp $R2 $R2 - $R4
IntOp $R5 $R2 * 21534
IntOp $R5 $4 * 21534
IntOp $R3 $4 * 18321
IntOp $R2 $R2 + $R5
IntOp $R4 $R4 * 11321
IntOp $R3 $9 * 16158
IntOp $R6 $5 * 23633
IntOp $R5 $_5_ * 18278
IntOp $R7 $6 * 16027
IntOp $R8 $7 * 18430
IntOp $R2 $R2 + $R3
IntOp $R4 $_4_ * 15917
IntOp $R9 $8 * 24544
IntOp $R2 $R2 - $R6
IntOp $R2 $R2 + $R7
IntOp $R3 $R3 * 25621
IntOp $R2 $R2 + $R5
IntOp $R2 $R2 - $R8
IntOp $R5 $R2 * 33321
IntOp $R2 $R2 + $R4
IntOp $R4 $R3 * 25321
IntOp $R2 $R2 - $R9
IntOp $R3 $R2 * 12345
IntOp $R2 $1 * 19292
IntOp $R4 $3 * 17677
IntOp $R5 $4 * 18327
IntOp $R9 $8 * 20472
IntOp $R6 $5 * 19344
IntOp $R3 $2 * 21770
IntOp $R7 $6 * 16593
IntOp $R8 $7 * 20094
IntOp $R2 $R2 - $R8
IntOp $R2 $R2 + $R9
IntOp $R2 $R2 + $R3
IntOp $R2 $R2 + $R4
IntOp $R2 $R2 - $R5
IntOp $R2 $R2 + $R6
IntOp $R2 $R2 + $R7
IntOp $R3 $9 * 19029
IntOp $R4 $_4_ * 16001
IntOp $R5 $_5_ * 20980
IntOp $R2 $R2 - $R3
IntOp $R2 $R2 + $R4
IntOp $R2 $R2 - $R5
IntCmp $R2 5295553 0 label_fail label_fail
... 略 ...
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)