查看原文
其他

看雪.京东 2018 CTF 第十题 暗风吹雨 点评与解析

看雪CTF 看雪学院 2019-05-27


比赛快结束了,还未有人提交第十题的答案

在看雪CTF竞赛交流群中引发了大家广泛的讨论,

就在比赛即将结束之时!看雪CTF 熟悉的面孔 ccfer出现

拿下第一血


大家纷纷变身段子手,小编选几个截图给大家分享一下





防守方半道盲人以1血的成绩稳坐第一名

本题过后 风间仁 继续稳居首位,

acdxvfscd 继续稳步上升,从第三名上升至第二名。

poyoten 再进前十名。


风间仁曾在看雪2017 CTF 年中赛中取得第一名

此次究竟能否继续卫冕冠军呢?

让我们拭目以待!



看雪版主&评委 netwind 点评


本题作者运用了几种较为新颖的代码保护技巧,使其变得难以攻破。本题一共5个操作,分别运用了directx异构系统,侧信道,竞态条件,spectre漏洞以及神经网络的方法。同时此题也有缺点,比如验证正确的序列号时间较长,操作之间结合并不特别紧密等等。总体来看,此题构思精巧,特别有创意,希望能给大家带来不一样的破解体验。


看雪.京东 2018 CTF 第十题 作者简介


半道盲人

bbs.pediy.com/user-678397

第十题出题者简介:


看雪ID:半道盲人,一流大学末流学生,知名战队不知名队员。少时近视,其父尝哂之曰半盲,因以为号焉。兴趣广泛,皆驳杂不精。唯擅给大佬递茶而已。 



看雪.京东 2018 CTF 第十题 设计思路


序及瞎扯淡



本来之前说好,今年年中开源一个 VM 壳的,不使用密码学强度的算法同时,做到即使开源也难以破解... 但是由于这几个月打了几场国际性比赛,平常偷懒玩游戏,以及处理自己实验室的诸多事情,到现在也没写出来。正值此次看雪 CTF,在下选了几个思路,出到这个 CrackMe 里面,也算是在下对软件保护这个领域未来方向的一些思考和试探吧。



设计哲学



说哲学有点玄,但是这个的确是方向性的东西,而不是各种技巧之类的。


首先,这个 CrackMe 的目标不是为了难。如果单纯为了难度,在下可以使用各种技巧能把这个程序难度再拔高一个层次。恰恰相反,在下尽力把一些不必要的难度去除,比如使用清晰的结构,关闭编译器优化,避免高度内联化,循坏展开,避免使用向量指令,等等。


其次,这次逆向题避开了大多传统的代码保护技术,包括加壳、反调试、虚拟机保护、自修改程序、常量展开、花指令等等。其实,这个 CrackMe 专注于一些新的方法,可能效果不如传统方法,但是希望能给大家更广阔的思路,也是希望大家能多指点在下。



大致流程



本 CrackMe 按照惯例开源,源代码见附件,曾经某届看雪 CTF 在下开源过一些现在看起来非常糟糕的代码,如今回想起还十分愧疚... 这次依旧能力有限,不过代码质量应该勉强能入各位法眼。


这次题目有点俄罗斯套娃,实际上是有 5 个不同的操作拼接而成。而其中两个操作是利用了隐蔽信道的技术,所以这题叫做暗风吹雨,取 暗风吹雨入寒窗之意。

  1. 要求输入为 16 个数字字母组合。

  2. Base64 解码成 96 位. 每 32 位为一大组。

  3. 对数据进行 5 个操作,分别为 T0, C0, T1, C1, T2. T 的意思是 transform,数据的混淆主要靠 T 操作。C 意为 covert,即更为隐蔽的操作。

  4. 将变换后的结果与预设值比较,作为判断 Flag 的依据。


所有的操作设计上都可逆,故而此算法可逆。而每个操作都使用了一种代码保护思路:


T0

T0 里面用到了异构计算。微软于 2012 年左右提出的 C++ AMP,是一个基于 DirectX 的异构计算标准,2016年以后基本已经不在维护了。可是即使如此,其相对于其他标准如 OpenCL 和 CUDA 来说有着很多优势。为了兼容性以及代码体积考虑,故选择了 C++ AMP。


T0 实际上做了一次 GF(2) 上的矩阵乘法。矩阵由随机数生成器由固定种子生成。且其逆矩阵验算后存在,故而也可逆。实际上许多串行算法完全可以挪到 GPU 上,天然的异构指令集,稀缺的文档,是一个非常不错的代码保护方法。


C0

C0 是一个利用 Cache 命中的侧信道来执行的一个 SBox。侧信道的第一个好处,就是隐蔽。如果不是如本程序刻意降低难度的话,对于数据的 cross reference 根本无法溯源到原始信息,加上侧信道甚至可以在进程之间通信,一个高手的设计可以让数据泥牛入海,无迹可寻。


其第二个好处,就是能破坏 Taint 分析。因为污点分析是基于数据流的,Covert Channel 则在显式的数据流之外打通了新的数据流,同理对于 Concolic Execution 也具有相当的抵抗力。


但是缺点也很严重,就是其不稳定性。侧信道很容易受外界因素影响,所以实际应用中会增加诸如 BCH 编码之类的纠错码。而且,难以避免的 RDTSC/RDTSCP 指令也使得这个方法在程序中十分显眼。


T1

T1 里面利用了多线程的 Race Condition,多个线程对同一个数赋值,只有最慢的线程的结果保留了下来,而类似于停机问题,得到任意程序的执行时间是一个不可解的问题。故而可以构造出不同速度的线程,对数据进行不同的操作,从而实现代码混淆。


T1 开了 4 个线程,每个线程的速度和数据的高两位有关。然后对数据的低 30 位进行循环移位和异或。


这种方法虽然是在下提出,但研究的也并不够成熟,具体效果看看实战吧。


C1

C1 实际上是利用了今年的 Spectre 漏洞。目前 Intel 和 AMD 似乎尚无硬件解决的意向,故而可以故意构造出这样一个信道,通过训练分支预测器,使得推测执行能够 touch 到目标内存区域。错误的输入数据无法训练好分支预测,通过检测目标区域是否缓存命中,故而可以做到代码混淆。


T2

T2 里面有很多浮点数运算,实际上是由两个简易的神经网络拼接而成。
第一个网络有32个输入,两个隐藏层都有32个神经元,激活函数是 Sigmoid,然后到8个输出,激活函数是round取整。


这个网络的作用主要是 permutation。


然后, 对于每一个输出,作为第二个小网络的输入。第二个网络有 16 个输出,相当于一个分类器,实际上是一个 SBox。


这个结构被串联在一起,不容易看出。如果注意到,round 函数作为激活函数可以说是非常之罕见,应该就能把两个网络分离开来。



破解思路



破解 T0 需要逆向 Shader 代码,这个得看 MSDN 上的文档。网络上也有相关工具。


T0 中的矩阵是可逆,计算出逆矩阵后乘以程序中的结果,可恢复 T0 的计算。


T1 根据头两位计算出实际有效的线程id,然后据此恢复。


T2 需要观察网络的输出,据此猜测网络的作用,小网络可以直接爆破。
C0 和 C1 实际上看懂了就能做出来,基本没有变换。



技术总结



其实程序无非就是两个方面,数据流和控制流。放在逆向 CrackMe 上面,就是要么跟着输入走,要么跟着代码走。


几个T操作,实际上混淆的是控制流,把代码变成 Direct X Shader Blob,或者变成神经网络的运算,都相当于异构。而 T2 这样,用并行的线程,同时读写一块内存,也是相当于打乱了控制流分析。


几个C操作,则实际上是隐藏了数据流,因为都不是通过 CPU 显式访存访问到的。


如果是多线程,通过 prime & probe 和 race condition 来访存呢?即打乱了代码执行顺序,也打乱了数据流向。这也大概是我那个尚未完成的 VM 代码保护的主要思路之一了。



尾声



首先,很感谢看雪论坛举办的这次比赛,能给我的一些不成熟的想法一个实践的机会,我也很喜欢这个技术交流的平台,衷心希望它越做越好。


其次,很感谢 @poyoten 在我学习逆向的过程中的很多指导,虽然素未蒙面,也算是在下的逆向导师了。还有感谢@swings 老师的亲自授课,虽然我啥都没学会... 但是好歹知道了大佬大概应该长什么样,以及上次 @渡 的信手一笔,给我的逆向学习指点了方向。


最后感谢 @netwind 的许多建议,这次题目一改再改,不断进步,很多都是在其忠告之下完善的。还有曾经 @linhanshi 于我的指点,以及看雪群里各位大佬的讨论,都给了我诸多启发,这里就不再一一感谢了。

 

在比赛开始之后看到后面 lelfei 有一道 NeuralCrackme,令我感到很惊喜,因为竟然有人也想到了使用神经网络来混淆代码。有一种道不孤矣的感觉。在下和 lelfei 之间并没有交流过这方面的技术,也希望这次比赛结束后多多探讨。这道题里神经网络的部分,也正好算作抛砖引玉了。


附:(阅读原文查看)

main.cpp 是程序源码。
CrackMe.7z 是程序。
reverse.c 是逆向程序的代码。

 

Flag 为 ThisIsAnEasyFlag

程序在 Windows 7 x64 SP1 上, 安装 MSVC 2015 运行库, Universal CRT,.NET 4.7 后测试通过。

Windows 10 17868 x64 上同样测试通过,其他高于 Windows 7 的系统在安装运行库后应该能正常运行。

不支持 Windows XP。

可执行程序的 SHA-256 为 5c455acfb6c36bd5d271af208e4b51cee617814c7fb07f7b8adc780d834aaa72。



看雪.京东 2018 CTF 第十题解析


*本解析来自看雪论坛 ccfer



这题最大收获就是学习了一下DirectX HLSL,那些amp的函数我也没查到参数,只能无奈的随便猜猜了。


验证流程都在这里:

.text:004035C0                 call    sub_401DF0               //输入转换base64

.text:004035C5                 call    sub_401E80               //一些初始化(seed/表/矩阵)

.text:004035CA                 call    @T0@0                    //Shader Model 5 Assembly (DirectX HLSL)

.text:004035CF                 call    @C0@0                    //没分析,直接单字节穷举,然后查表

.text:004035D4                 call    @T1@0                    //这个fork函数调试难受,容易卡住,最后利用最近学到的输出反馈给输入,很快发现了周期性,然后就好办了

.text:004035D9                 call    @C1@0                    //没分析,直接单字节穷举,然后查表

.text:004035DE                 call    @T2@0                    //没分析透彻,仿佛看到了傅里叶变换的味道,直接晚上开机穷举,早上起来出结果了。


大部分时间都花在研究那个T0中那个HLSL验证上了。


执行文件中有两段HLSL,偏移40102Ch处长度4CCh,偏移401530h处长度714h。


可以直接用D3D反汇编:


void test_10_decompile(BYTE *dxbc_ptr,DWORD len)

{

        HMODULE hDxd;

        DWORD rv = 0;

        DWORD blob[0x400];

        DWORD D3DCreateBlob;

        DWORD D3DDisassemble;


        hDxd = LoadLibrary("D3dcompiler_47.dll");


        if (hDxd)

        {

                D3DCreateBlob = (DWORD)GetProcAddress(hDxd,"D3DCreateBlob");

                D3DDisassemble = (DWORD)GetProcAddress(hDxd,"D3DDisassemble");

                if (D3DDisassemble)

                {

                        rv = ((DWORD (__stdcall *)(DWORD,DWORD *))D3DCreateBlob)(0x400,&blob[0]);

                        rv = ((DWORD (__stdcall *)(BYTE *,DWORD,DWORD,char *,DWORD *))D3DDisassemble)(dxbc_ptr,len,0,"",&blob[0]);

                        //返回的反汇编数据在blob结构中,我直接内存抓出来了

                }

        }

}


偏移40102Ch处第一段:

//

// Generated by Microsoft (R) HLSL Shader Compiler 10.1

//

//

// Buffer Definitions: 

//

// cbuffer CB_1

// {

//

//   uint _const_buffer_1[28];          // Offset:    0 Size:   436

//

// }

//

//

// Resource Bindings:

//

// Name                                 Type  Format         Dim      HLSL Bind  Count

// ------------------------------ ---------- ------- ----------- -------------- ------

// _GV_buffer_rw_0                       UAV    byte         r/w             u0      1 

// _GV_buffer_rw_1                       UAV    byte         r/w             u1      1 

// CB_1                              cbuffer      NA          NA            cb1      1 

//

//

//

// Input signature:

//

// Name                 Index   Mask Register SysValue  Format   Used

// -------------------- ----- ------ -------- -------- ------- ------

// no Input

//

// Output signature:

//

// Name                 Index   Mask Register SysValue  Format   Used

// -------------------- ----- ------ -------- -------- ------- ------

// no Output

cs_5_0

dcl_globalFlags refactoringAllowed

dcl_constantbuffer CB1[24], immediateIndexed

dcl_uav_raw u0

dcl_uav_raw u1

dcl_input vThreadID.xyz

dcl_temps 2

dcl_thread_group 32, 8, 1

imul [precise(x)] null, r0.x, vThreadID.z, cb1[0].x

imad [precise(x)] r0.x, vThreadID.y, cb1[1].x, r0.x

iadd [precise(x)] r0.x, r0.x, vThreadID.x

ult [precise(y)] r0.y, r0.x, cb1[2].x

if_nz r0.y

  udiv [precise(x)] r0.x, r1.x, r0.x, cb1[4].x

  iadd [precise(y)] r0.y, r1.x, cb1[21].x

  iadd [precise(y)] r0.y, r0.y, cb1[23].x

  ishl [precise(y)] r0.y, r0.y, l(2)

  ld_raw_indexable [precise(y)](raw_buffer)(mixed,mixed,mixed,mixed) r0.y, r0.y, u1.xxxx

  ushr [precise(y)] r0.y, r0.y, r0.x

  and [precise(y)] r0.y, r0.y, l(1)

  imad [precise(x)] r0.x, cb1[7].x, r0.x, cb1[11].x

  iadd [precise(x)] r0.x, r1.x, r0.x

  iadd [precise(x)] r0.x, r0.x, cb1[14].x

  ishl [precise(x)] r0.x, r0.x, l(2)

  store_raw u0.x, r0.x, r0.y

endif 

ret 

// Approximately 19 instruction slots used


偏移401530h处第二段:

//

// Generated by Microsoft (R) HLSL Shader Compiler 10.1

//

//

// Buffer Definitions: 

//

// cbuffer CB_1

// {

//

//   uint _const_buffer_1[41];          // Offset:    0 Size:   644

//

// }

//

//

// Resource Bindings:

//

// Name                                 Type  Format         Dim      HLSL Bind  Count

// ------------------------------ ---------- ------- ----------- -------------- ------

// _GV_buffer_rw_0                       UAV    byte         r/w             u0      1 

// _GV_buffer_rw_1                       UAV    byte         r/w             u1      1 

// _GV_buffer_rw_2                       UAV    byte         r/w             u2      1 

// CB_1                              cbuffer      NA          NA            cb1      1 

//

//

//

// Input signature:

//

// Name                 Index   Mask Register SysValue  Format   Used

// -------------------- ----- ------ -------- -------- ------- ------

// no Input

//

// Output signature:

//

// Name                 Index   Mask Register SysValue  Format   Used

// -------------------- ----- ------ -------- -------- ------- ------

// no Output

cs_5_0

dcl_globalFlags refactoringAllowed

dcl_constantbuffer CB1[37], immediateIndexed

dcl_uav_raw u0

dcl_uav_raw u1

dcl_uav_raw u2

dcl_input vThreadID.xyz

dcl_temps 3

dcl_indexableTemp x0[3], 4

dcl_thread_group 32, 8, 1

imul [precise(x)] null, r0.x, vThreadID.z, cb1[0].x

imad [precise(x)] r0.x, vThreadID.y, cb1[1].x, r0.x

iadd [precise(x)] r0.x, r0.x, vThreadID.x

ult [precise(y)] r0.y, r0.x, cb1[2].x

if_nz r0.y

  udiv [precise(x)] r0.x, r1.x, r0.x, cb1[4].x                          //r0.x=n/3,r1.x=n%3

  mov [precise(x)] x0[0].x, l(-257992108802048.000000)                  //0xD76AA478

  mov [precise(x)] x0[1].x, l(-7545063049677084100000000.000000)        //0xE8C7B756

  mov [precise(x)] x0[2].x, l(0.000000)                                 //0x242070DB,这几个常数貌似有问题,用https://github.com/James-Jones/HLSLCrossCompiler里的工具反编译对比后修正一下

  iadd [precise(y)] r0.y, r1.x, cb1[11].x                               //r

  iadd [precise(y)] r0.y, r0.y, cb1[14].x                               //r0.y=r

  iadd [precise(z)] r0.z, r0.x, cb1[24].x                               //d

  iadd [precise(z)] r0.z, r0.z, cb1[27].x                               //r0.z=d

  mov [precise(w)] r0.w, l(0)                                           //

  mov [precise(y)] r1.y, l(32)                                          //

  mov [precise(zw)] r1.zw, r0.yyyz                                      //r1.z=r,r1.w=d

  mov [precise(x)] r2.x, l(-1)                                          //

  loop 

    breakc_z r2.x

    ishl [precise(yz)] r2.yz, r1.wwzw, l(0, 2, 2, 0)                    //r2.y=4d,r2.z=4r

    ld_raw_indexable [precise(y)](raw_buffer)(mixed,mixed,mixed,mixed) r2.y, r2.y, u1.xxxx      //r2.y=u1[d]

    ld_raw_indexable [precise(z)](raw_buffer)(mixed,mixed,mixed,mixed) r2.z, r2.z, u0.xxxx      //r2.z=u0[r]

    imad [precise(w)] r0.w, r2.y, r2.z, r0.w                            //r0.w+=u1[d] * u0[r]

    iadd [precise(w)] r1.w, r1.w, cb1[20].x                             //r1.w += 0x20

    iadd [precise(z)] r1.z, r1.z, cb1[7].x                              //r1.z += 3

    iadd [precise(y)] r1.y, r1.y, l(-1)                                 //r1.y--

    ult [precise(x)] r2.x, l(0), r1.y                                   //

  endloop 

  ishl [precise(y)] r0.y, l(1), r0.x                                    //r0.y=1<<d

  mov [precise(z)] r0.z, x0[r1.x + 0].x                                 //r0.z=k[r]

  and [precise(y)] r0.y, r0.y, r0.z                                     //r0.y &= k[r]

  bfi [precise(x)] r0.x, l(1), r0.x, r0.w, l(0)                         //

  xor [precise(x)] r0.x, r0.x, r0.y                                     //

  iadd [precise(y)] r0.y, r1.x, cb1[34].x                               //

  iadd [precise(y)] r0.y, r0.y, cb1[36].x                               //

  ishl [precise(y)] r0.y, r0.y, l(2)                                    //

  imm_atomic_xor [precise(x)] r0.x, u2, r0.y, r0.x                      //

endif 

ret 

// Approximately 39 instruction slots used


第一段HLSL就是把输入的12字节按位拆开,利用GPU的96个线程,每个处理一位,大概可以这样模拟:


void hlsl1(DWORD *t,BYTE *in)

{

        DWORD i;

        

        for (i=0;i<96;i++)

        {

                t[i] = (*(DWORD *)&in[(i%3)*4] >> i/3) & 1;;

        }

}


第二段HLSL也是利用GPU的96个线程,每个处理一位,实现的模2矩阵乘法,大概可以这样模拟:

void hlsl2(DWORD *t,DWORD *array,BYTE *out)

{

        DWORD d,r;

        DWORD m;

        DWORD i,j;

        DWORD b;

        DWORD k[3] = {0xD76AA478,0xE8C7B756,0x242070DB,0};


        for (i=0;i<96;i++)

        {

                m = 0;

                d = i / 3;

                r = i % 3;

                for (j=0;j<0x20;j++)

                {

                        m += t[r+j*4] * array[d+j*0x20];

                }

                b = k[r] & (1 << d);

                b ^= (m & 1) << d;

                *(DWORD *)&out[r*4] ^= b;

        }

}


正规解法应该可以求出逆矩阵,以前有个软件winiso的注册算法用的就是这种模2矩阵,因为3个DWORD可以单独处理,在穷举承受范围内,所以还是穷举半小时出结果。


最终结果:ThisIsAnEasyFlag




合作伙伴

京东集团是中国收入最大的互联网企业之一,于2014年5月在美国纳斯达克证券交易所正式挂牌上市,业务涉及电商、金融和物流三大板块。

 

京东是一家技术驱动成长的公司,并发布了“第四次零售革命”下的京东技术发展战略。信息安全作为保障业务发展顺利进行的基石发挥着举足轻重的作用。为此,京东信息安全部从成立伊始就投入大量技术和资源,支撑京东全业务线安全发展,为用户、供应商和京东打造强大的安全防护盾。

 

随着京东全面走向技术化,大力发展人工智能、大数据、机器自动化等技术,将过去十余年积累的技术与运营优势全面升级。面向AI安全、IoT安全、云安全的机遇及挑战,京东安全积极布局全球化背景下的安全人才,开展前瞻性技术研究,成立了硅谷研发中心、安全攻防实验室等,并且与全球AI安全领域知名的高校、研究机构建立了深度合作。

 

京东不仅积极践行企业安全责任,同时希望以中立、开放、共赢的态度,与友商、行业、高校、政府等共同建设互联网安全生态,促进整个互联网的安全发展。


CTF 旗帜已经升起,等你来战!

扫描二维码,立即参战!



看雪.京东 2018 CTF 





看雪2018安全开发者峰会

2018年7月21日,拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,倾力打造一场技术干货的饕餮盛宴——2018 安全开发者峰会,将在国家会议中心隆重举行。会议面向开发者、安全人员及高端技术从业人员,是国内开发者与安全人才的年度盛事。此外峰会将展现当前最新、最前沿技术成果,汇聚年度最强实践案例,为中国软件开发者们呈献了一份年度技术实战解析全景图。



戳下图↓,立即购票,享5折优惠!







戳原文,立刻加入战斗!
Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存