首页
社区
课程
招聘
看雪CTF.TSRC 2018 团队赛 第九题 谍战
2018-12-19 03:21 3202

看雪CTF.TSRC 2018 团队赛 第九题 谍战

2018-12-19 03:21
3202

第九题 谍战

还是没有时间写,直接写一下结论,略去大部分分析过程。

观察

让我们首先观察出题方的队伍名字:GPUber,里面有三个十分扎眼的大写字母。

 

题目给了一个 32 位 Windows 应用程序,运行之后可以看到一个自绘并且在 HiDPI 屏幕上还画糊了的图形界面。
程序中有这么一个字符串 "Nana C++ Library",Google 搜一下可以发现是一个历史悠久的有趣的 C++ GUI 库。
程序本身的功能也是输入字符串,告诉你对不对,需要注意的看起来程序上没有按钮,应该是定时或者检测了文本框内容改变触发的验证。

分析

WinMain 里首先验证了系统版本,必须为 Windows 7 以上,然后验证了 DirectX 版本,接下来初始化了一个奇怪的正则表达式^([[:d:]]{10})([[:d:]]{10})([[:d:]]{10})$
接下来在栈上造了一大堆(可能有些是套起来的)C++ class,结构如下:

00000000 CheckContext    struc ; (sizeof=0xAC, align=0x4, mappedto_635)
00000000 hThread         dd ?                    
00000004 thread_id       dd ?                    
00000008 hReleaseEvent   dd ?                    
0000000C gapC            db 4 dup(?)             
00000010 hWnd            dd ?                    
00000014 gap14           db 32 dup(?)            
00000034 dword34         dd ?                    
00000038 dword38         dd ?                    
0000003C width           dd ?
00000040 height          dd ?                    
00000044 gap44           db 4 dup(?)             
00000048 double48        dq ?                    
00000050 double50        dq ?                    
00000058 double58        dq ?
00000060 double60        dq ?                    
00000068 double68        dq ?
00000070 double70        dq ?                    
00000078 double78        dq ?
00000080 double80        dq ?                    
00000088 lock_input_data _RTL_CRITICAL_SECTION ? 
000000A0 Input0          dd ?
000000A4 Input1          dd ?                    
000000A8 Input2          dd ?
000000AC CheckContext    ends

00000000 BGLogicThreadContext struc ; (sizeof=0x34, align=0x4, mappedto_638)
00000000 hThread         dd ?                    
00000004 thread_id       dd ?
00000008 hReleaseEvent   dd ?                    
0000000C gapC            db 4 dup(?)             
00000010 hWnd            dd ?                    
00000014 window_name     std::wstring ?          
0000002C process_id      dd ?
00000030 main_ctx        dd ?                    
00000034 BGLogicThreadContext ends

接下来构造了一些画画的玩意,主要是 nana 库里的东西,暂时没必要具体分析。
但值得关注的是构造的过程中它造了一个 vtable 的名字是 nana::basic_event<nana::arg_textbox>::docker::``vftable' 的东西,又把另一个带虚函数的对象挂在了上面。不妨大胆猜想这里是在挂 onchanged 事件,找到那另一个对象的虚表(0x4AA384)里最大的函数(…………),开头下个断点试一下输入,会发现确实断下了,走几步会发现获取到了刚刚改变的输入文本,看来八九不离十。
接下来再仔细看一下会发现这个 onchanged 函数里还是干了不少事情的,把拿到的输入对着之前的那个正则表达式匹配了一下(所以输入的格式是三组每组10位的十进制数字连起来),atoi 然后填进 CheckContext 里的 Input0, Input1, Input2。然后貌似对着这个结果做了一些验证,最后判断了一下验证的结果然后看上去是 toggle nana 里面两个 label 的 visibility,好像很真,然而改一下最后的判断可以发现程序并没有显示 Correct,说明要么其他地方还有检查,要么这里的检查根本就是假的。

 

回到 WinMain 里继续看,程序接下来创建了一个新的线程,把上面的两个Context结构体指针传了进去,里面的逻辑是先通过找特定名字的窗口的方法拿到刚刚 nana 造出来的窗体,然后起另一个线程做坏事。

 

这“另一个线程”里调用了一大堆 DX 的函数,众所周知的是,DirectX 的 API 是走 COM 那一套搞的,这里有一个小技巧是,注意到 d3d11.h 里其实是定义了 C 风格的手动 vtable 的函数结构的,如果能把这部分抠出来的话这个程序看起来会非常的方便。手动抠可能有点累,不过我们可以找一个纯 C 写成的 DirectX 程序的 demo (比如这个),然后自己拿 MSVC 编译一下,把得到的程序连同 pdb 一起扔进 IDA,在 Local Types 里选中所有 ID3D 和 IDXGI 开头的类型,右键 Export to headers,再在正在分析 CM 的 IDA 里 Ctrl+F9 加载,然后标一标类型,在 Hex-Rays 里就可以得到如下的看起来还可以的结果辣。

 

蛤蛤

 

仔细分析一下这里面的逻辑,会发现里面造了三个 shader 在画画,其中第一个和第二个的结果跟输入没关系,输出会作为 texture resource 传给第三个。同时有一个线程,每两秒会去拿一下 CheckContext 里的 Input0, Input1, Input2,填进第三个 shader 的 constant buffer 里,然后画第三个 shader。
所以估计第三个里面是个什么验证逻辑,验证了输入的 Input0, Input1 和 Input2,然后根据验证结果选择把第一个或者第二个画的东西最后画出来吧。

 

既然我们怀疑验证逻辑藏在 shader 里面,那么把这个 shader 反汇编出来看看就行了。这个 shader 使用的还是老式的 DXBC 而不是 DXIL。
奇怪的是 D3DDisassemble 对这个 shader 会返回错误,不知道是版本问题还是啥情况(看起来作者用的是 Windows 8.1 SDK 里的 fxc 编译的 shader),打开 Google 闲逛可以找到果然有蛋疼人士已经写过一个手动反汇编的工具了:https://github.com/tgjones/slimshader/ ,本来想的是这个有代码,报了错的话也好对着看 bytecode 是不是哪里被动了手脚,但实际上能直接顺利反汇编出来:

//
// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
//
//
///
// Buffer Definitions: 
//
// cbuffer cb
// {
//   
//   float4x4 v;                        // Offset:    0 Size:    64 [unused]
//   float4x4 p;                        // Offset:   64 Size:    64 [unused]
//   float4x4 w;                        // Offset:  128 Size:    64 [unused]
//   float4 c1;                         // Offset:  192 Size:    16 [unused]
//   float4 c2;                         // Offset:  208 Size:    16 [unused]
//   uint4 val;                         // Offset:  224 Size:    16
//
// }
//
//
// Resource Bindings:
//
// Name                                 Type  Format         Dim Slot Elements
// ------------------------------ ---------- ------- ----------- ---- --------
// samLinear                         sampler      NA          NA    0        1
// tx0                               texture  float4          2d    0        1
// tx1                               texture  float4          2d    1        1
// cb                                cbuffer      NA          NA    0        1
//
//
//
// Input signature:
//
// Name                 Index   Mask Register SysValue  Format   Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_POSITION              0   xyzw        0      POS   float       
// TEXCOORD                 0   xy          1     NONE   float   xy  
// COLOR                    0   xyzw        2     NONE   float       
//
//
// Output signature:
//
// Name                 Index   Mask Register SysValue  Format   Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_Target                0   xyzw        0   TARGET   float   xyzw
//
ps_4_0
dcl_constantbuffer cb0[15], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_input_ps linear v1.xy
dcl_output o0.xyzw
dcl_temps 4
ine r0.xyz, cb0[14].xyzx, l(0, 0, 0, 0)
and r0.x, r0.y, r0.x
and r0.x, r0.z, r0.x
ult r0.y, l(0x3b9aca00), cb0[14].x
and r0.x, r0.y, r0.x
ult r0.yz, cb0[14].xxyx, cb0[14].yyzy
and r0.x, r0.y, r0.x
and r0.x, r0.z, r0.x
ult r0.y, cb0[14].z, l(-1)
and r0.x, r0.y, r0.x
udiv r0.yzw, null, cb0[14].zzxy, l(0, 0x000186a0, 0x000186a0, 0x000186a0)
imad r1.xyz, r0.zwyz, l(-100000, -100000, -100000, 0), cb0[14].xyzx
udiv r2.x, r3.x, r0.z, l(10)
udiv null, r1.w, r2.x, l(10)
udiv r2.xyzw, null, r0.zzzw, l(100, 1000, 10000, 100)
udiv null, r2.xyzw, r2.xyzw, l(10, 10, 10, 10)
imul null, r1.w, r1.w, l(1000)
imad r1.w, r3.x, l(10000), r1.w
imad r1.w, r2.x, l(100), r1.w
imad r1.w, r2.y, l(10), r1.w
iadd r1.w, r2.z, r1.w
ieq r1.x, r1.x, r1.w
and r0.x, r0.x, r1.x
udiv r1.x, r2.x, r0.w, l(10)
udiv null, r1.x, r1.x, l(10)
udiv r3.xyzw, null, r0.wwyy, l(1000, 10000, 100, 1000)
udiv null, r3.xyzw, r3.xyzw, l(10, 10, 10, 10)
imul null, r1.x, r1.x, l(1000)
imad r1.x, r2.x, l(10000), r1.x
imad r1.x, r2.w, l(100), r1.x
imad r1.x, r3.x, l(10), r1.x
iadd r1.x, r3.y, r1.x
ieq r1.x, r1.y, r1.x
and r0.x, r0.x, r1.x
udiv r1.x, r2.x, r0.y, l(10)
udiv r1.y, null, r0.y, l(10000)
udiv null, r1.xy, r1.xyxx, l(10, 10, 0, 0)
imul null, r1.x, r1.x, l(1000)
imad r1.x, r2.x, l(10000), r1.x
imad r1.x, r3.z, l(100), r1.x
imad r1.x, r3.w, l(10), r1.x
iadd r1.x, r1.y, r1.x
ieq r1.x, r1.z, r1.x
and r0.x, r0.x, r1.x
movc r0.yzw, r0.xxxx, r0.yyzw, cb0[14].zzxy
iadd r1.x, r0.w, r0.z
iadd r1.x, r0.y, r1.x
iadd r1.x, r1.x, l(0x0000374f)
ieq r1.x, r1.x, l(0x00017334)
and r0.x, r0.x, r1.x
imad r1.xy, l(3, 6, 0, 0), r0.zyzz, r0.wzww
iadd r1.xy, r1.xyxx, l(0x0000374f, 0x0000a5ed, 0, 0)
iadd r1.xy, -r0.ywyy, r1.xyxx
ieq r1.xy, r1.xyxx, l(0x0000d146, 0x00040ad5, 0, 0)
and r0.x, r0.x, r1.x
ishl r0.w, r0.w, l(1)
iadd r0.y, r0.w, r0.y
iadd r0.y, r0.y, l(0x00006e9e)
iadd r0.y, -r0.z, r0.y
ieq r0.y, r0.y, l(0x000182c1)
and r0.x, r0.y, r0.x
and r0.x, r1.y, r0.x
if_nz r0.x
  sample o0.xyzw, v1.xyxx, t0.xyzw, s0
  ret 
else 
  sample o0.xyzw, v1.xyxx, t1.xyzw, s0
  ret 
endif 
ret 
// Approximately 70 instruction slots used

最后果然是判断了一下决定输出哪个 texture。看起来验证逻辑就是把输入按十进制数位拆开打乱组装回去然后乱判判。

解决

懒得看具体怎么对应的,所以对着指令的文档直接把逻辑翻译一遍然后拿 Z3 跑就行了。

from z3 import *

i0, i1, i2 = [BitVec('i%d' % i, 32) for i in xrange(3)]
s = Solver()

s.add(i0 != 0)
s.add(i1 != 0)
s.add(i2 != 0)
s.add(ULT(0x3b9aca00, i0))
s.add(ULT(i0, i1))
s.add(ULT(i1, i2))
s.add(ULT(i2, 0xFFFFFFFF))

xV, yV, zV = i0, i1, i2

y0, z0, w0 = UDiv(zV, 100000), UDiv(xV, 100000), UDiv(yV, 100000)
x1, y1, z1 = xV - z0 * 100000, yV - w0 * 100000, zV - y0 * 100000
x2 = UDiv(z0, 10)
x3 = URem(z0, 10)
w1 = URem(x2, 10)
x2, y2, z2, w2 = URem(UDiv(z0, 100), 10), URem(UDiv(z0, 1000), 10), URem(UDiv(z0, 10000), 10), URem(UDiv(w0, 100), 10)
w1 = w1 * 1000
w1 = x3 * 10000 + w1
w1 = x2 * 100 + w1
w1 = y2 * 10 + w1
w1 = w1 + z2
s.add(x1 == w1)

x1, x2 = URem(UDiv(w0, 10), 10), URem(w0, 10)
x3, y3, z3, w3 = URem(UDiv(w0, 1000), 10), URem(UDiv(w0, 10000), 10), URem(UDiv(y0, 100), 10), URem(UDiv(y0, 1000), 10)
x1 *= 1000
x1 += x2 * 10000
x1 += w2 * 100
x1 += x3 * 10
x1 += y3
s.add(x1 == y1)

x1, x2 = UDiv(y0, 10), URem(y0, 10)
y1 = UDiv(y0, 10000)
x1, y1 = URem(x1, 10), URem(y1, 10)
x1 *= 1000
x1 += x2 * 10000
x1 += z3 * 100
x1 += w3 * 10
x1 += y1
s.add(x1 == z1)

y0, z0, w0 = y0, z0, w0
# y0, z0, w0 = zV, xV, yV
x1 = w0 + z0
x1 = y0 + x1
x1 = x1 + 0x0000374f
s.add(x1 == 0x00017334)

x1, y1 = 3 * z0 + w0, 6 * y0 + z0
x1, y1 = x1 + 0x0000374f, y1 + 0x0000a5ed
x1, y1 = x1 - y0, y1 - w0
s.add(x1 == 0x0000d146)
s.add(y1 == 0x00040ad5)

w0 <<= 1
y0 += w0
y0 += 0x00006e9e
y0 -= z0
s.add(y0 == 0x000182c1)

print s.check()
model = s.model()
print ''.join(map(str, [model[x] for x in (i0, i1, i2)]))

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-12-19 13:49
2
0
分析太透澈了!膜拜!!!
雪    币: 16152
活跃值: (5936)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2018-12-19 17:23
3
0
还是一如既往的强!!
游客
登录 | 注册 方可回帖
返回