看雪2022 KCTF 秋季赛 | 第三题设计思路及解析
2022-11-21 17:59:42 Author: mp.weixin.qq.com(查看原文) 阅读量:26 收藏

看雪 2022 KCTF秋季赛 已于11月15日中午12点正式开始!比赛延续上一届的模式并进行优化,对每道题设置了难度值、火力值、精致度等多类积分,用规则引导题目的难度和趣味度。大家请注意:签到题将持续开放,整个比赛期间均可提交答案,获得积分哦~
今日中午12点,第三题《水患猖獗》已截止答题,围观人数2000+,共3个战队攻破此题,看来还是有点难度的~
接下来和我一起来看看该赛题的设计思路和相关解析吧~

出题团队简介

第三题《水患猖獗》出题方 ArmVMP 战队,战队成员id:ArmVMP

赛题设计思路

题目信息:本题是安卓平台规则2的crackme
文件名:KCTF2022-autumn-android-crackme.apk
公开序列号如下:
name:KCTF
serial:42A4ECA067F54074C3EB2F177ACB06FE1379055CD4FB2211C3BD874FAD9E101D
第二组序列号:
name:5113BF890A692660
serial:FDF7C5A02A114C749970F516F1DB24FE9D6AC65C41B4EEEDD2EDA660D5EFB01E
(通过命令shasum -a 256 KCTF2022-autumn-android-crackme.apk计算apk得文件hash:
5113BF890A69266035BA4AE149F4E88183949F6E5F9B8201B404E0F5F98F29D3)

本题算法延续了KCTF2022-春季赛-第八题迷雾散去的设计方法:

1、对输入的name字符串通过SHA1算法计算得到16字节的hash值;
2、对hash值做rc4加密运算得到16字节value1值;
3、选取部分代码计算SHA256得到16字节的密钥Key值;
4、将输入的64字符长度password转为32字节的16进制表示(如:前8个字符42A4ECA0转换后为4字节的0x42A4ECA0)和Key做异或运算dedao value2;
5、当password不足64字节时提示错误;
6、比较value1 == value2时,则为正确的密钥对,提示输入正确;
7、为避免多解问题,对编辑框输入的小写字符统一进行了大写转换。
增加防护技巧:

1、题目设计为双进程模式,算法的校验分别放在两个进程中,并通过管道通信且父子进程使用相互ptrace防调试处理;

2、管道通信读写及部分操作如: open/read/getpid等系统调用通过中断svc指令实现;

3、父子进程均增加/proc/self/status调试检测;
4、算法及防护代码编译后的文件以二进制方式,对函数指令做了vm保护处理;

5、增加防frida检查;

6、程序中字符串做了加密处理及符号表中函数名清除。
本题属于高阶对抗题目,虽然在算法上与之前想似,故算法部分不是考察重点,此次增加了混淆保护的形态多样性,故保护后的关键函数并没有相同之处,加上防调试及防frida的手段。本题重在考察选手对于复杂指令形态下,如何思考了解函数间关系,抽丝拨缕梳理解决问题的技巧能力。
解题思路:
程序中保留了android_log_print符号调用,并且保留了将序列号中间结果通过参数传递给子函数的调用,攻击者只需要找到该子函数并且将调用android_log_print打印参数的指令填充后,通过日志即可查看到真实serial。
vm保护原理:
正常编译后的代码通过反编译工具,逻辑结构很清晰,门槛低的小白都可以拿来用f5分析,所以代码防护很重要,最起码能防部分小白。
最初级的加壳保护运行后会解密所以动态调试和dump内存也能获取到原代码,这时保护后勿需还原最终以保护态运行就会提高分析门槛。X86时期辉煌无数的vmprotect技术令大部分人闻风丧胆,该技术放到arm架构同样适用。
原理也大同小异:保护流程主要由三部分组成:反汇编器,解释器vmachine和链接器。
  • 反汇编器 负责解析输入二进制文件,输出函数指令和数据流

  • 解释器    负责对vm指令和数据流进行解释执行

  • 链接器    负责将vmachine嵌入新二进制文件中,并对地址相关的指令做重定位

反汇编器取汇编指令列表中的指令根据指令类型生成对应的模拟代码,如push/pop, ldr/str, bl, add, mov等,由于对指令进行了重新编码故称为指令模拟。
 
原始代码开始替换为一条b 指令跳转到新生成的模拟代码运行,这样做可以避免其 他函数调用该函数时能正常工作。其余位置可以填充随机字段或者清空。
解释器工作原理:
针对ARM平台ELF格式的二进制程序的代码保护,兼容Arm&Thumb指令集,首先反编译被保护的函数获取指令流 其次对指令流类型分别处理进行重定位 。和需要模拟的指令进行编码生成vmdata最后通过链接器将解释器和原二进制 文件进行patch生成新二进制由于编译后的汇编指令地址相关性,当把指令放到到千里之外,且不影响运行首要任务就是对地址相关指令做重定位。
ldr指令进行分析做重定位如下:
.text:000016C2 19 4C LDR R4, =(__stack_chk_guard_ptr - 0x16CC) .text:000016C4 C3 B0 SUB SP, SP, #0x10C .text:000016C6 01 AE ADD R6, SP, #0x120+var_11C .text:000016C8 7C 44 ADD R4, PC .text:000016CA 24 68 LDR R4, [R4] .text:00001728 A0 38 00 00 off_1728 DCD __stack_chk_guard_ptr - 0x16CC
Thumb指令16C2处指令为ldr r4, [pc, #0x64], 访问的数据地址为 (16C2&0xFFF4) + (PC+4) + 0x64 = 0x1728,意思是从0x1728中获取数据即0x38A0赋值给R4寄存器 。
R4寄存器在0x16C8地址处会跟PC寄存器相加即0x38A0+(PC+4)=0x4F6C 
结合0x16CA指令读内存操作,也就是说程序是在读取0x4F6C地址处的数据 
这段代码当移动到其他地址运行时PC寄存器会变为移动后的地址。 
例如这段代码移动到:
.text:00006000 19 4C LDR R4, =(__stack_chk_guard_ptr - 0x16CC) .text:00006002 C3 B0 SUB SP, SP, #0x10C .text:00006004 01 AE ADD R6, SP, #0x120+var_11C .text:00006006 7C 44 ADD R4, PC .text:00006008 24 68 LDR R4, [R4] .text:0000606C A0 38 00 00 off_1728 DCD __stack_chk_guard_ptr - 0x16CC
移动后0x6000地址指令读取0x606C数据为0x38A0到R4寄存器, 在0x6006地址时R4与PC相加结果为0x38A0+(0x6006+4) = 0x98AA。这与原来的0x4F6C是不相符的,为了正确读取0x4F6C地址就需要对0x606C处的数据做重定位即 0x4F6C = X+(0x6006+4)得出X=0x213C。将0x606C的0x38A0修改为0x213C这样才能确保访问正确。
对模拟运行的指令重新编码,如:.text:000016D8 01 F0 D2 FD BL 0x1BA8
BL指令是相对当前地址到跳转目标地址=0x16D8+0x1BA8 = 0x3280,当运行时如果通过POP {PC}模拟就需要将运行时的真实内存地址即基地址+偏移地址,假设基地址和偏移地址存储在变量中就可以通过如下方式进行:
 
LABEL_BASE: .word 0x62220000 LABEL_DISP: .word 0x3280 PUSH {R0-R15} LDR R0, LABEL_BASE LDR R1, LABEL_DISP ADD R0, R1 STR R0, [SP, #0x3C] POP {R0-R12} ADD SP, #0xC LDR PC, [SP, #-4]
这样重定位和指令模拟就实现了,通过同样的方法实现其他指令的模拟。

赛题解析

本赛题解析由看雪论坛会员 mb_mgodlfyn 给出:

VMP是当下最强的代码混淆,它的强度已无需再验证,而且目前并不存在成熟的开源的自动化的脱壳工具。
然而,很多恶意软件也采用了类似的代码混淆,这为安全研究人员分析恶意软件带来了巨大的困难,因此一个通用的自动化去混淆工具意义非凡。有一些探索性的研究项目,例如 VMProtect Devirtualization(https://github.com/JonathanSalwan/VMProtect-devirtualization) SATURN - Software Deobfuscation Framework Based On LLVM(https://dl.acm.org/doi/10.1145/3338503.3357721)等,但是距离成熟的工具还有很大的距离。
如果不考虑副作用,那么代码逻辑本质是接受输入,对输入做计算和变换,最后输出结果,与数学上的函数并无二致。VMP保护虽然把控制流变得面目全非,但数据流作为运算逻辑的本质仍然是贯穿于程序中的(当然也有很多针对数据流的混淆(如不透明谓词、MBA等),但与控制流还是相对独立的),因为无论代码如何混淆,原始程序的逻辑是不能被改变的。
最擅长数据流分析的工具是编译器。目前很多去混淆通用工具的研究项目,核心思想还是设法把被混淆的程序提升为 LLVM IR,然后利用编译器强大的数据流优化能力(主要是常量传播、活跃分析、死代码消除等)剥离大部分与核心逻辑无关的垃圾指令,同时引入SMT求解器处理某些数据流混淆。
不久前才刚知道SATURN这篇论文,感觉可行性是最高的,文章对设计思路的描述也相当详细,只可惜作者没有开源。
计算机行业的技术整体上还是开放共享的,SATURN论文虽然没有开源,但是可以相信一定有人会按照论文的思路实现这个项目(例如当年Google提出了MapReduce但没有开源,于是有了后来的Hadoop)。
另外如果某个技术成熟了,尽管会有一些人做闭源商业化创收,但一定也会有另一些人做开源共享(例如IDA是最强的反编译器,它是商业化的,而开源界也有Ghidra,虽然效果差了一些,但总是有的。(这是计算机行业的整体技术氛围,应该说目前互联网的很多基础设施都依赖于一些基础的开源项目,所以真正投入开源的人是最值得尊重的(开源困境:Log4j2 维护者发声:没有工资,还要挨骂https://zhuanlan.zhihu.com/p/447251986))

但是把代码混淆作为CTF题目考点来出的话,如果没有较为成熟的开源项目可以利用,个人感觉对攻击方是有些为难的。因为CTF的初衷是Hack for FUN,其次是技术交流。硬刚VMP的过程往往不太FUN,而从技术分享的角度看,此题的混淆强度4天时间如果从零开始写自动化工具基本也不大可能。


当然,防守方也许会考虑到攻击方有自己的私藏武器。但是如果思考一下这个问题
可以发现,如果以SATURN论文的工作量作为参考,可以想象实现这样一个去混淆框架至少要全力投入数月(甚至考虑前期对VMProtect的逆向分析,时间周期会更长),经过不断的迭代才能有一些初步的效果,可能只有本职工作在此的人才能有足够的精力投入其中。

而且,即使有了这样的工具,用来解题肯定也要做针对性适配,而这又是一个深坑:单凭一道题目搞清楚代码混淆方式就要花费大量时间,何况还要修改原有的工具,再重新测试,4天时间恐怕也不太现实。另外,考虑到计算机行业的技术开放程度,虽然业界最强的工具不一定开源,但基本上开源界总会有相同功能的工具,而且至少可以做到能用的程度(可以对比IDA和Ghidra)。

但是现在似乎还没有发现高调售卖的商业化VMP去混淆工具,而且现有的几个开源的VMP去混淆工具效果还不是非常好。不过,随着时间发展,还有能够相信未来会出现成熟的工具的。(其他CTF比赛也出现过以代码混淆作为考点的题目,不过题目为了考虑时限内的可解性(而不是为了单纯测试混淆的强度是否足够高到无人能解;如果真是这样的目的就应该搞付费悬赏,和SRC平台一样),强度往往被有意降低,重点在于展示思路,于是很好的兼顾了难度和技术交流)

不妨回顾下近几届KCTF所有以代码混淆为主要考点的题目,似乎没有一个人是真的靠自己写的自动化去混淆工具还原出原始逻辑进而解出题目的,基本上都是靠动态调试、trace跟踪、人为观察等方式,靠堆体力找出逻辑,而且,也没有任意一个出题人公开自己写的自动化去混淆工具,这样无论攻击方还是防守方,从技术交流的角度总感觉缺少了一点灵魂(p.s. 不过很多基于动态调试的解题方法也非常硬核而且能学到很多知识)。

而且这种工具对安全行业具有颠覆性的意义,即使闭源高价售卖也是有巨大市场的)(此外,回想自己作为某比赛出题人时,负责人给的硬性要求就是必须自己以做题人的视角解一遍确保可解性和做题体验,而且对于代码混淆的使用有严格的限制,必须保证自己有合理的解法(比如,有成熟工具可利用,或者从零开始能在赛期内写出工具,或者有足够的线索提示能够完全绕开混淆);而且到最后往往会发现,某些题目即使开源给做题者都不会降低难度)。

所以,基于以上推理,只能合理假设既然用了VMP来出题,那么就默认了出题人是希望攻击方寻找捷径,而不是硬刚代码混淆,因为对于本题的混淆强度从零开始的话4天的时间基本上不可能写出自动化的工具。

回到本题,对本人来说正常做是不可能做出来了,也只能想一想其他的路。

根据公开的serial可知合法的serial长64字节,每个字符取值[0-9A-F],实际上hexdecode后是32字节。


jadx逆向apk,发现java层什么都没有,只是把name和serial都传给so然后接受一个字符串并显示出来。

Android Studio 开一台 arm 架构的虚拟机,x64dbg 调试 qemu-system-armel.exe

输入正确的serial会提示"祝贺,闯关顺利",输入错误的serial会提示"不对!再探再报",而且从java层看到这两个字符串都是so返回的。
把这两个字符串utf8编码,在x64dbg里全内存搜索,只能找到一片内存。

保持name不变,改变输入的serial,观察附近内存的变换,发现以下区域:
000002892A9CE570  00 00 00 00 00 00 00 00 13 00 00 00 AB 00 00 00  ............«... 000002892A9CE580  02 EB FF FF A9 15 00 00 E7 97 8C 92 84 96 8C 92  .ëÿÿ©...ç....... 000002892A9CE590  E8 96 8C 92 50 00 00 00 23 00 00 00 10 C1 7B A2  è...P...#....Á{¢ 000002892A9CE5A0  EE DF DE 0E B8 CB 7B A2 00 00 00 00 1C 96 8C 92  îßÞ.¸Ë{¢........ 000002892A9CE5B0  15 FC 7D A2 9D 60 7C A2 D4 FF 7D A2 00 00 00 00  .ü}¢.`|¢Ôÿ}¢.... 000002892A9CE5C0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................ 000002892A9CE5D0  00 00 00 00 00 00 00 00 00 00 00 00 2C 3F 7C A2  ............,?|¢ 000002892A9CE5E0  01 00 00 00 E8 96 8C 92 E7 97 8C 92 23 C1 7B A2  ....è...ç...#Á{¢ 000002892A9CE5F0  E7 97 8C 92 00 00 00 00 E8 96 8C 92 50 00 00 00  ç.......è...P... 000002892A9CE600  FC 56 00 00 00 30 7B A2 FC 86 7B A2 23 00 00 00  üV...0{¢ü.{¢#... 000002892A9CE610  E8 96 8C 92 04 00 00 00 79 3F 7C A2 FC 86 7B A2  è.......y?|¢ü.{¢ 000002892A9CE620  20 00 00 00 84 96 8C 92 5C 96 8C 92 49 49 7B A2   .......\...II{¢ 000002892A9CE630  A9 15 00 00 00 00 00 00 00 00 00 00 20 0A 00 00  ©........... ... 000002892A9CE640  22 00 00 00 30 00 00 00 EF FE DD 00 30 C1 7B A2  "...0...ïþÝ.0Á{¢
观察到 0x2892A9CE580 处的 02 EB 会随着 serial 的变化而变化,而且错误的 serial 在此处的值都小于 0xEB02。
固定 name 为 KCTF,逐字节改变输入。每个字节有 16 种取值,找出使得 0x2892A9CE580 处的值最大的那种即为正确的输入。
剩下的就是体力活了,下面是花几个小时手动搞出来的表:
(初始serial是64个0;行标头表示改变serial的第几位,列标头表示改变为哪个值)       0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F 0  DB86 DB76 DB66 DB56  DBC6 DBB6 DBA6 DB96  DB06 DAF6 DAE6 DAD6  DB46 DB36 DB26 DB16 1  DB86 DB85 DB88 DB87  DB82 DB81 DB84 DB83  DB7E DB7D DB80 DB7F  DB7A DB79 DB7C DB7B 2  DB86 DB76 DBA6 DB96  DB46 DB36 DB66 DB56  DC06 DBF6 DC26 DC16  DBC6 DBB6 DBE6 DBD6 3    86   85   84   83    8A   89   88   87    7E   7D   7C   7B    82   81   80   7F 4   B8   B7   BA   B9    BC   BB   BE   BD    C0   BF   C2   C1    C4   C3   C6   C5 5    86   85   84   83    8A   89   88   87    8E   8D   8C   8B    92   91   90   8F 6   B8   B7   BA   B9    B4   B3   B6   B5    C0   BF   C2   C1    BC   BB   BE   BD 7    86   85   84   83    82   81   80   7F    7E   7D   7C   7B    7A   79   78   7742A4ECA0    // 这是使得 0x2892A9CE580 取值最大的输入(BD5B135F)    // 这是使得 0x2892A9CE580 取值最小的输入        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F 8   B8   B7   BA   B9    BC   BB   BE   BD    B0   AF   B2   B1    B4   B3   B6   B5 9    86   87   88   89    8A   8B   8C   8D    7E   7F   80   81    82   83   84   8510   B8   B9              BC                   C0                   C4             C711    86                   8A   8B        89    7E        7C         82       12   B8   B7   B6   B5    BC             B9    B0             AD    B4             B113    86                   82                   7E                   7A             7714   B8                   BC              BF   B0             B3    B4          15    86                   8A   89              7E   7D   7C   7B    82         67F54074(980ABF8B)        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F16   B8             B5    BC                   C0                   C4           17    86             89    82                   7E             81    7A             7D18   B8   B7        B9    BC                   C0                   C4   C3   C6   C519    86                   82   83   84         8E   8F        91    8A       20   B8   B7   BA                        B5    B0                   AC   AB        AD21    86             89    8A                   8E                   92             9522   B8   B9        B7    B4                   B0   B1   AE         AC   AD   AA     23    86                   8A             8D    7E                          C3EB2F17(3C14D0E8)        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F24   B8                   BC             BF    B0                   B4           25    86                   82   81        83    8E        90         8A         26   B8              B5                        C0                   C4              C127    86                   82   83              8E        90   91    8A           28   B8                   B4                   B0                   AC              A929    86                   8A   89   8C   8B    7E   7D                       30   B8              BB   BC                   C0                   C4    C5   C6   C731    86   85                   89                        90              91   94   937ACB06FE(8534F901)        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F32   B8   B9                   B5   B2   B3              AE                   AA   AB33    86   87        89         83                        80         7A        7C   7D34   B8                        BD        BF    B0        B2                        B735    86                        83   80              8F   8C                        8936   B8                        B3                        AE                        A937    86                        8B                        7C                   38   B8                        BD                        AE                        B339    86        84   83         89                        8C         92             8F1379055C(EC86FAA3)        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F40   B8        B6              BD                        BE              C5        C341    86                   8A   89   88                   7C   7B                   7F42   B8                        BD                        C2                        C743    86                   82   83        85              90   91                   8D44   B8        BA              B3                        B2         AC   AB        AD45    86        88              81                        80              79        7B46   B8   B9                   B5                        AE                   AA   AB47    86  87                    83                        7C                   78   79D4FB2211        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F48   B8             B5         BB                        BE         C4        C2   C149    86             89         83                        80         7A             7D50   B8                   B4   B5                        C2   C3                   BF51    86        84              8B                        8C              93        9152   B8                        B3        B1    C0        BE                        B953    86                        8B        8D    7E        80                        8554   B8                   BC   BB              B0        AE   AD                   B155    86                        8B                        90         92             95C3BD874F        0    1    2    3     4    5    6    7     8    9    A    B     C    D    E    F56   B8                        B3                        C2                  57    86        84              8B                        8C              93        9158   B8                   B4   B5   B2              C1   BE   BF                   BB59    86   85   88              89                   8D   90                   94   9360   B8   B9   B6              B5                        AE                   AA   AB61    86                        81                        7C                        7762   B8   B9                   B5                        AE                   AA   AB63    86        84              8B                        8C              93        91AD9E101D
做完才发现,其实每个字节尝试 1、2、4、8 四种情况就够了。如果某种情况使得 0x2892A9CE580 的值比输入取 0 时大,就加上这个值,否则不加。
另外自动化也是有可能的:通过 adb shell input 控制输入,ReadProcessMemory 读取内存;但是评估了下手动的工作量,感觉几个小时能搞完,所以还是手动搞的。以及第一次动手发现 Windows 上通过 ReadProcessMemory 读其他进程的内存竟然不需要权限。细思恐极,Windows上的恶意软件是不是随便读其他进程内存获取密码等敏感数据?
题目的最终答案:
name: KCTFserial: 42A4ECA067F54074C3EB2F177ACB06FE1379055CD4FB2211C3BD874FAD9E101D
关于动态调试,这两天才知道内核的 eBPF uprobe 机制(从Linux 3.5就存在了)。通过这个机制可以跟踪用户态程序、在任意地址处停下、dump寄存器等(原理也是插入int 3指令,但不走ptrace(?),应对反调试估计很有效),值得发掘)。
第四题《治水搁浅》比赛正在进行
https://ctf.pediy.com/game-season_fight-219.htm
欢迎参与和围观

- End -

球分享

球点赞

球在看

“阅读原文查看详情!

文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458484391&idx=1&sn=d0e43de6a60260cb4b405de8c0f165b6&chksm=b18e4e2d86f9c73b1e992a0965591122088e1e765166ab6049c19c2a28c3e93e126741761713#rd
如有侵权请联系:admin#unsafe.sh