线索

作者:djh

前言

这道题是我比较用心出的一道题, 前前后后大概花了三四天, 目的主要是为了给大家枯燥无味的逆向过程中增加少许趣味性, 希望大家能喜欢. 题目本身并不难, 做出来的同学花的时间绝对比我出题用的时间要短, 可以说是非常失败的代码保护了2333333.

官方答案

我个人比较懒. 所以官方答案只说思路, 不会把每一步用到的脚本啊截图啊之类的都放出来 (这里祈祷一下那些做出来的大佬们能给出详细的WP). 但是考虑到会看我这种辣鸡写出来的 WP 的人多半是真萌新, 我会尽量把步骤说的细一点. 如果同学们有兴趣的话, 可以跟着我的思路走一走, 把这道题做完.

准备阶段

不知道大家拿到一道 Windows 逆向题目之后第一步会干什么. 有人喜欢直接拖 IDA, 有人喜欢先调试, 有人喜欢先用 PEID 扫一扫, 有人喜欢先跑跑看这道题是干嘛的. 但是对于我来说, 拿到一个 exe 的第一件事... 当然是传到 VirusTotal 上扫描一遍啦.

然而结果很蛋疼, 4 / 65 的报毒率, 其中 Symantec 的扫描结果还是 HighConfidence (喷血)... 所谓防人之心不可无嘛, 虽然逆向题是一个病毒的概率不大, 但是这时候有必要确认一下, 说不定自己被其他选手中间人攻击了呢: 比如检查一下, 主办方的网站是不是 HTTPS 啊 (不是), 有没有给文件 Hash 啊 (没有), 文件的 Hash 有没有被主办方的私钥签名啊 (不存在的).

往往这个时候, 我的心里就会骂一句出题人 (也就是我自己), 然后打开虚拟机. 逆向使用虚拟机的好处有很多, 比如调试到一半的时候打一个快照, 之后可以随时回复这个状态 (前提是你有一块好的 SSD). 虽然 Windbg 已经支持了 Time Travel Debugging, 但是毕竟会用 Windbg 的都是大佬. 我们这种萌新只好用这种土方法啦.

关于虚拟机, 我还想说一句, 推荐大家使用 VirtualBox 而不是 VMware. 特别是对于那些装了 VMware 还不想升级的同学, 这里提醒一下, 虚拟机逃逸的漏洞每年都有, 比如前几个月对 VMware 12.5.5 之前的版本都有效的漏洞 EXP, 早就公布到 Github 上了.

当然啦, 我知道这道题是是我自己写的, 所以不太在乎这个扫描结果. 然后咧, 我就打开了一款类似于 PEID 的静态检查软件, StudyPE+. 结合 VirusTotal 的结果, 从最宏观的角度看一看这个文件, 大概是多少位啊, 有没有加壳啊, 有没有 TLS Callback 啊, 入口点有没有被修改啊, 有没有 RWE 权限的区段啊, 之类的.

很好, 似乎又是一个普普通通的程序, 然后拖进 IDA. 看了看没什么可疑的地方, 先跑一跑试试看.

就一句话 "Please input the flag:", 随便输了几个字, 输出 "Nope, nope..." 然后退出了. 我就喜欢这种简单粗暴的程序, 哪个人出的题, 我要表扬一下 (wwwwwarai).

结束阶段

用 IDA 逆向程序, 多半首先找 main 函数. 对呀对呀, 可是 main 函数有四种找法, 你知道吗:

  • IDA 7.0 自动识别 (喂, 这也算?
  • 经验问题 (说了等于没说...
  • 搜索字符串 (Shift-F12 + X + F5 三连见过没?
  • 其它解法 (你们这群家伙不知道 switch 要有 default 吗...

吼, 进入到 main 函数, 一看 sub_46A9DE() 可能就是 printf() (实则不然), 按 N 重命名为 printf, 按 Y 把函数原型改成 int sub_46A9DE(char *a1, ...) 便于分析堆栈.

然后看一看流程, 大概是最多读入 25 个字符, 写到数组里面, 传入 sub_401064(), 再和一串字符串比较. 好, 那么主要的处理一定是在 sub_401064() 里面了.

进入这个函数, 发现自动切回到了 Text View, 如果试图进行 F5, IDA 6.8 会卡好久然后得出一个不错的结果, IDA 7.0 会直接报错 too big function, 当然把IDA 7.0\cfg\hexrays.cfg 中的 MAX_FUNCSIZE 参数改大一点也能 F5. 但是我们这里假设不使用 F5 大法, 只会 F5 的萌新永远是萌新 (就像我, 大家不要学我).

大致看一看汇编, 多数指令都很相似, 关于跳转的指令只有两条: jmp byte ptr ds:off_46A3A4[edx*4]jmp $+2 . 进入到off_46A3A4, 发现又是一大堆 loc, 看来是一个跳转表, 估计对应一个 switch. jmp $+2 没啥用, 属于垃圾指令.

回头看 switch 的条件, edx 是由 div ecx 产生的, 而 ecx 是一个定值 0x51, eax 的值由 rdtscp 产生. 所以可以肯定的是, 每一个 switch 的结果是一样的. 我们不妨假定 eax = 0 .

这时候需要看一看 CFG, 把 Options -> General -> Graph -> Max number of nodes 改高一点, 按 space 切换到 Graph View. 发现所有路径都是相同的一条线下来. 到最后就直接 retn 了, 看来中间一定有处理输入的指令我们没有发现.

这时候, 如果你觉得自己是欧皇的话, 随便上下翻动找一找, 就能发现一条突兀的 call 指令出现. 当然如果你试了很久, 还没发现, 然后才意识到自己并不是欧皇, 就请使用下面的方法:

我们注意到, 这个函数里面的指令种类十分有限. 如果我们将这段代码的十六进制 Dump 出来 (可以用 010 Editor 来做, 不过要自己转换 RVA), 输入到 Capstone 里面反汇编, 然后管道到 uniq, 就能看到一个 call 指令, call 的那个函数就是我们要找的函数.

当然也有更好的办法 (虽然我更希望大家使用上面的笨办法, 以便让大家知道 IDA 的局限性): 在函数列表里面查找临近的函数, 因为编译器往往会把一个 .c 的代码编译到一起, 链接器也会使得程序员写的代码相互靠近. 所以对于一个小程序而言, 一个函数要调用的用户函数往往在它附近.

还有更好的办法, 就是右键 -> Proximity browser, 这个会把函数调用图清晰地画出来. 可以看到 sub_401064() 调用了两个函数, 一个 sub_401006() 发现是 sprintf(), 另一个就是 sub_46A4E8() 了.

进入 sub_46A4E8(), IDA 这次死活分析不对了. 我们来看一看汇编:

.text:0046A4EE                 mov     edi, [ebp+8]
.text:0046A4F1                 mov     eax, offset loc_46A665
.text:0046A4F6                 push    23h
.text:0046A4F8                 push    eax
.text:0046A4F9                 push    33h
.text:0046A4FB                 sub     eax, 163h
.text:0046A500                 push    eax
.text:0046A501                 retf

简单分析一下, 大概这样的:

mov     edi, 传入参数
push    23h
push    0x46A665
push    33h
push    0x46A502
retf

那么这个 retf 是啥子咧? 遇到不懂的地方, 我们萌新往往会问 Google. 这里我先推荐一下 Intel developer manual, 如果做 PC 端逆向的人不看这本书, 可以说成为大佬的一条路就封死了.

查阅手册可以知道, retf 的作用就是更改 cs 寄存器然后返回. 随手调试一个 32 位的程序, 发现它的 cs 寄存器的值是 0x23, 那么把 cs 改到 0x33 有什么用吗, 为什么 IDA 就无法反汇编了呢?

继续查阅手册, 我们发现段寄存器里面装的是 Segment Selector, 而 Segment Selector 后三位是 Table Indicator 和 Request Privilege Level. 而 0x33 代表着, 这个 Segment Descriptor 是在 GDT 的第 6 项的.

为了找出 GDT 里面第 6 项到底是啥, 我们参照 Microsoft 的文档, 给我们虚拟机搭建好内核调试环境. 在 Host 上使用 Windbg 连接上内核调试, 输入 dg 8 0x40 来读取 GDT 表:

3: kd> dg 8 0x40
                                                    P Si Gr Pr Lo
Sel        Base              Limit          Type    l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0008 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000
0010 00000000`00000000 00000000`00000000 Code RE Ac 0 Nb By P  Lo 0000029b
0018 00000000`00000000 00000000`00000000 Data RW Ac 0 Bg By P  Nl 00000493
0020 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P  Nl 00000cfb
0028 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P  Nl 00000cf3
0030 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P  Lo 000002fb
0038 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000
0040 00000000`0e00f000 00000000`00000067 TSS32 Busy 0 Nb By P  Nl 0000008b

可以看到, 第 4 个和第 6 个 Segment Descriptor 的多个属性段都不相同, 我们继续查阅手册, 发现 Long 这个 bit 代表着 CPU 是否在 64-bit 模式下运行!

所以可以确定, cs 寄存器为 0x33 时, CPU 转而进入 64-bit 模式. 而代码自然是 64 位的代码. 至此, 一切水落石出. IDA 误将其当成 32 位的代码进行反编译, 自然会出错.

回到本题上来, 一切非常简单. 将 0x46A502 到 0x46A665 的代码 Dump 出来, 另存为一个文件, 开启一个新的 IDA 打开, 设置好 64 位模式和 ABI, 按下 F5:

  do
  {
    v5 = 4 * *input & 0x1F;
    *temp ^= A2a[*input >> 3];
    v6 = input[1];
    temp[1] ^= A2a[((input[1] >> 6) + v5) & 0x3F];
    temp[2] ^= A2a[(unsigned __int8)(4 * v6) >> 3];
    v7 = input[2];
    temp[3] ^= A2a[((input[2] >> 4) + (16 * v6 & 0x1F)) & 0x3F];
    temp[4] ^= A2a[((v7 >> 7) + (2 * v7 & 0x1F)) & 0x3F];
    v8 = input[3];
    temp[5] ^= A2a[(unsigned __int8)(2 * v8) >> 3];
    v9 = input[4];
    temp[6] ^= A2a[((input[4] >> 5) + (8 * v8 & 0x1F)) & 0x3F];
    temp[7] ^= A2a[v9 & 0x1F];
    temp += 8;
    input += 5;
  }
  while ( temp != temp_end );

其中 A2a 是个长度 32 的从 'A' 到 'a' 的数组, 这个代码大概将每 5 个输入字符转化为 8 个 A 到 a 之间的字符, 有经验的同学能够直接猜出来这是一个 Base 32 算法. 将最外面的那串字符串拿出来, 异或一下算法中的几个常数, 然后随便找一个可以控制字符集的 Base 32 解码算法就可以了.

至此, 这道题已经可以说基本做完了. 谢谢观看. 下期节目再见.

九条线索

我曾经说过这道题有 9 条线索, 不知大家找到了多少... 甚至有人开始怀疑这句话的正确性? 在此作者亲自揭晓题目中所有的暗藏玄机.

0

首先 strings reverse.exe (如果有人还不放心, 可以使用 FLOSS), 有 8 条线索都可以从这条命令里面看出来:

[email protected] ~/Desktop > strings reverse.exe
This program CAN be run in DOS mode.
...
Hint: Change the max number of nodes to 10000 in IDA general options
Hint: Change graph background to black in IDA color options
Hint: Switch to graph mode in IDA
...
Hint: Decompilation will take a long time and the result may disappoint you
...
Welcome, master of debugger! Decoding one more hint...
...
Hint: You should use a debugger supporting both x86 and x64 simultaneously like WinDBG does.
...
Bagv:OfpznjrvtnlahlgwrmbxixwkkqbxyfmksdqxoumktypohlacypkulehwxdtonuaCqdlsuqugdzrybgnnicbgfasgysxznzxtjloklwtutyplijmjndqncjmayhlhcllrwzdxueugnydtiltdqpqulmgqseeollrvbzyonkfchsfty
Vbznenay Vxwx: Qfgn dnz. Je cgwlt gxe iar.

1

Hint: Xor the Base64 string with preset constants and then decode it.

我已经都把 "CAN" 大写了... 为啥就没人注意到???

一般的程序 strings 一下是什么样的 --- "This program cannot be run in DOS mode." 这明显不一样好吗... 这个程序是可以在 DOS 下运行的, 安装 DOSBox 试一下, 运行结果就是一个 Hint.

不信邪的可以把文件拖到 IDA 里面, 选择 DOS 格式:

seg000:0000                 public start
seg000:0000 start           proc near
seg000:0000                 push    cs
seg000:0001                 pop     ds
seg000:0002                 assume ds:seg000
seg000:0002                 mov     si, 1Dh
seg000:0005                 mov     cx, 45h ; 'E'
seg000:0008
seg000:0008 loc_10008:                              ; CODE XREF: start+F↓j
seg000:0008                 mov     al, [si]
seg000:000A                 xor     al, 0CCh
seg000:000C                 mov     [si], al
seg000:000E                 inc     si
seg000:000F                 loop    loc_10008
seg000:0011                 mov     dx, 1Dh
seg000:0014                 mov     ah, 9
seg000:0016                 int     21h             ; DOS - PRINT STRING
seg000:0016                                         ; DS:DX -> string terminated by "$"
seg000:0018                 mov     ax, 4C01h
seg000:001B                 int     21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg000:001B start           endp                    ; AL = exit code

啧啧.

2 - 5

Hint: Change the max number of nodes to 10000 in IDA general options

Hint: Change graph background to black in IDA color options

Hint: Switch to graph mode in IDA

Hint: Decompilation will take a long time and the result may disappoint you

不说了, 这 4 条免费.

6

Hint: Find the func returning 23333333h

难道是我英文水平问题? 为啥每个人理解这句话的意思都不一样... 我的意思是说找到返回值为 23333333h 的函数.

这个 Hint 是动态解密的, 需要调试出来. 不推荐自己手工静态解, 因为这个加密算法不是很常见, 叫 Grain, 有兴趣的可以搜一搜.

调试是有个坑的, 不知道大家试过没有. 不过有些坑点不好放在明面上说, 建议大家自己回去试一下. 我重新实现了一个 printf() 函数, 并把检测调试的代码藏在里面, 第一眼估计是看不出来的. 解决倒是很好解决, 把假的 printf() nop 掉就行了. 或者各种插件都可以.

注意, 这个解密是从那个仿 Base64 字符串里面解密的, 可能需要手工处理一下.

7

Hint: You should use a debugger supporting both x86 and x64 simultaneously like WinDBG does.

这个 Hint 直接被 strings 出来了, 不过不在代码里, 也不在数据里, 所以 IDA 可能找不出来. 仔细一看这个在 .rsrc 段里面, 是某音频的一部分, 这件事本身也是提示.

8

Hint: Just try to count number of different instructions in that enormous switch. Also maybe you should realize that there are reasons for this thirtytwo-bits program not being able to run on thirtytwo-bits machine.

Dixitque Deus: Fiat lux. Et facta est lux.

最后一个 string, 大家不觉得很奇怪吗?

如果大家看一看区段表, 会发现除了常见的 .text, .data 之类的区段以外, 还多了一个 .vgnr 区段, 区段里面就是那最后一个 string.

这个区段名称就很有提示, 猜测是 Vigenere 加密. 没错, 这是一道 crypto 题! 随便找一个破解 Vigenere 加密的软件 (当然也可以自己写), 我推荐CrypTool 2, 就可以解出来这个 Hint.

解出来里面还有一句看不懂, 别再试图继续解了... 那是一句拉丁文, 用来装做大佬的样子吓唬萌新混淆词频的.

9

Hint: This is not Base64 doesn't mean it has nothing to do with another base.

最后一个提示在哪呢?

注意到第 7 个 Hint 其实是音频的一部分, 这时候我们将目光转向这个程序的图标...

没错, 这个程序的 icon 是一张被隐写了的图片!

icon 由多幅不同尺寸的图组成, 最大的那个尺寸是 PNG, 里面 LSB 隐写着 Hint.

关于表情

很多人很好奇 IDA 里面那个表情是怎么做到的... 其实说穿了也很简单, 详情可以看看 REpsych.

实现起来也不难, 但是我自己写的代码太丑太长... 就不放出来了...

后话

总之呢, 希望大家做这道题能够感到愉悦~ 做了一点微小的工作, 谢谢大家.

results matching ""

    No results matching ""