Finally, Parallels Desktop

注: 虽然我不提倡, 但是毕竟难以耐住它贵啊. 一个可能可以的 链接. 我是学生, 请给我学生版 (bushi).

如果不考虑效果的话, 实际上使用 UTM 虚拟机的话我感觉还是很好用的. 能够模拟 PD 无法模拟的一些架构的系统, PD 目前只能模拟和 M1 一样的 arm 架构的一类系统.

坏, 太久没做逆向了, 完全都忘光了. 估计还要复习一下 Linux 系统的逆向.

About Windows

About Windows

对于 Windows 程序, 其中有一个比较要命的东西就是 Windows API. 相比 Linux 逆向遇到的那些用 C 写的简单直白的暴力实现的程序, 感觉在 Windows 程序里面遇到的更多的是一些意义不明的奇怪函数 (API) 调用.

下面的来源于《加密与解密》第 4 版:

通过动态链接库 DLL 来实现调用 API, 主要的 API 有:

API (DLL)Usage
Kernel (KERNEL32.DLL)操作系统核心功能服务, 包含进程和线程控制, 内存管理, 文件访问
User (USER32.DLL)用户接口, 键盘鼠标, 窗口和菜单管理
GDI (GDI32.DLL)图形设备接口

关于函数的命名:

  • 比如说遇到了两个函数 MessageBoxAMessageBoxW (实际上都是 MessageBox), 那么有什么区别呢?
  • A 结尾的是 ANSI 类型 (单字节方式)
  • W 结尾的是 Widechars (Unicode) 宽字节方式
  • 那么在调试的时候遇到的话, 就可以不用操心这些乱七八糟的东西
  • 不过可能会有一个比较有意思的现象, 即在调用 MessageBoxA 的时候, 实际上做了一个变换, 最后将 ANSI 字串变成 Unicode 字串, 然后调用 MessageBoxW (什么皮套人).

一些系统的消息:

  • Windows 通过消息驱动, 所以在调试程序的时候可以通过跟踪信息来
  • 一些常用的信息函数:
    Message FunctionUsageReturn Value
    SendMessage调用一个窗口的窗口函数, 将消息发送给窗口根据具体消息是否投递成功来返回非零 (TRUE) 与否.
    WM_COMMAND用户从菜单或按钮中选择命令, 或者控件发送消息给父窗口, 或者快捷键发送时若程序处理消息则返回零
    WM_DESTROY窗口被销毁时发送信息 02h如果程序处理消息则返回零
    WM_GETTEXT文本复制消息 0dh返回被复制的字符数量
    WM_QUIT在程序调用 PostQuitMessage 时生成没有返回值
    WM_LBUTTONDOWN光标停在窗口的客户区且按下鼠标左键的时候, 若未被捕获, 则发送给光标下窗口; 否则发送已经捕获鼠标动作的窗口如果处理, 返回值为零

虚拟内存:

  • 实现方法和过程:
    1. 应用程序启动, 创建进程并分配一个 (2G?) 虚拟地址.
    2. 虚拟内存管理器将应用程序的代码映射到虚拟地址的某个位置, 将当前需要的代码读入物理地址
    3. 使用的 DLL 被映射到进程的虚拟地址空间里面, 在需要的时候才读入物理内存
    4. 堆栈和数据从物理内存开始分配并映射到虚拟地址里
    5. 应用程序通过使用虚拟地址空间中的地址开始执行, 然后虚拟内存管理器将内存访问映射到物理位置
  • 应用程序不会直接访问物理地址
  • 虚拟内存管理器通过虚拟地址的访问请求来控制所有的物理地址的访问

注: 我感觉可能需要先编几个 Windows 程序之后才能够学会这些麻烦的东西. 之后如果有了真正理解的话再重新写一遍吧. 现在因为基本就是抄写参考书, 所以注释掉了.

IDA Pro or Ghidra

关于 Ghidra 的话, 之前有一个 介绍.

尽管当时说应该尽量少用盗版, 但是读书人的事, 怎么能叫盗版呢…

IDA Free 或者 Ghidra 实际上就可以满足大部分的功能了, 尽管不一定能够做到尽善尽美就是了.

(但是我可以都要啊, 乐. 哪个更加好用就用哪个, 人没有必要如此专一 bushi. )

WinDbg x64dbg and ret-sync

在 Windows 上有各种的调试工具, 但是巨硬维护了一个叫 WinDbg 的调试工具. 界面花里胡哨的. 好用是好用, 但是有一个问题, 就是在本地调试的时候, 会有各种各样的限制. (甚至连断点也不能, 淦, 是我大意了)

好像也不是不行, 网上看到有一个做法是开 2 个 Parrel Desktop 来调试, 不过光开一个我的电脑估计就已经吃不消了. Oh! Mac Air! 你 TMD 可是快一万的电脑啊!

不过尽管不能够用 WinDbg 调试的话, 实际上它还能够用于查看一些符号之类的方便的操作. 用于辅助分析还是非常方便的.

于是我换成了 x64dbg, 其他还有的有 ollydbg 之类的. 各种各样的东西. 可以使用一个叫做 ret-sync 的工具来将这些工具都串联起来, 形成一个比较好用的工作流.

踩了太多坑了, 之后一定要记录一下.

在配置和安装 ret-sync 的过程里面, 还是有非常多的坑的:

  • Ghidra 10.2.3: 需要按照 repo 里面的介绍, 先编译. 然后选择 File - Install Extensions…
  • x64dbg, x32dbg: 实际上需要对 win32, x86 每个版本都编译一次
    • 简单来说, 就是用 VS 打开 ext_x64dbg 文件夹中的 x64dbg_sync.sln 文件. 然后选择对应的平台版本, 然后选择 Build 后将生成的文件放到对应的文件夹下.
    • 对于 x64dbg, 需要 x64 版本, 编译结果是 .dp64 后缀的文件, 放到 x64/plugins 文件夹内.
    • 对于 x32dbg, 需要 Win32 版本, 编译结果是 .dp32 后缀的文件, 放到 x32/plugins 文件夹内.
  • windbg: 编译的方法和 x64dbg 一样, 需要放到 repo 里面指定的位置后, 在程序中使用 !load sync 后使用.

因为 ret-sync 通过服务器来传递信息, 所以需要配置好服务器地址. 服务器地址需要在 ~/.sync 里面说明. 对于 Windows 系统, ~User/yourname 处. 一个小小的坑就是虚拟机和宿主机的 IP 是不一样的, 所以需要给虚拟机里面的程序指定一个宿主机的 IP: 比如我的电脑的 IP 是 233.333.333.3, 开放的端口是 9100, 于是 .sync 应该写成:

[INTERFACE]
host=233.333.333.3
port=9100

(注: 一个方便的查看 ip 的方法 ifconfig -a | grep inet. 尽管在校园网里面有时候挺麻烦的… )

一个展示的例子:

/_img/windows-reverse/x32dbg-sync.png

/_img/windows-reverse/ghidra-sync.png

之后会将这些东西作为一个工作流来使用, 方便以后跑路换环境.

这里还是和 GDB 以及 LLDB 来联系在一起来学会比较方便:

MethodGhidra ret-syncx64dbgWinDbgGDBLLDB
Documents and Helpret-syncx64dbg (EN), x64dbg (CN) 0.1标准调试方法, 或者是 ? 获得基础命令列表, .help <cmd> 列出帮助信息Debugging with GDBTutorial LLDB
Command Grammar以快捷键操作为主GUI 为主, 命令: cmd arg1, arg2, ...不区分大小写, 元命令 (调试器自带), 以 . 开头, 拓展命令以 ! 开头<noun> <verb> [-options [option-value]] [argument [argument ...]]
Load Programsync 同步
BreakPointF2 断点bp, SetBPX 设置; DeleteBPX, bpc 删除bp, bu, bm 可以用于设置断点; bl 列出断点break [name/addr] 下断点, info break 列出断点列表br s [-n name/-a addr] 下断点, br l 列出断点列表
Examining Variablesvar, varlist 显示变量x Module!Symbol 显示符号info [args/locals] 显示环境参数与局部变量 p varfr v [-a/v_name] 列出符号, ta v name
List Functionslm 列出符号表info function 列出函数image lookup -r -s <FUNC_REGEX>
RunF5 运行g 或者 F5 运行程序r 或者 run 运行程序process launch 或者 r 或者 run
StepF10 单步步过st, StepOver 单步步过p 或者 F10 单步执行, 跳过 call 命令s 或者 steps 或者 thread step-in
NextF11 单步跟踪sti, StepInto 单步步进t 或者 F8 或者 F11, 跟进 call 指令n 或者 nextn 或者 next, thread step-over
StackGUI 可以直接看k 系列的命令, 给出 stack trace

就是一些非常简单的操作, 不过我觉得只看操作列表可能根本不知道是什么, 还是写点代码来试试看会比较好一点.

人难免烂俗

尽管我非常讨厌一口气就要吃掉我好多空间的 VS, 但是它毕竟是微软亲儿子 (bushi), 于是忍痛割爱, 删掉了 80G 的地铁. 感到十分惋惜 (bushi).

官网 下载 内存毁灭者, 选择安装 Desktop Development with C++. 新建工程选择空白工程… 然后在 Source Files 里面添加代码并编译.

(注: 貌似最新的 Windows 有转译的功能, 可以让 Arm 架构的系统运行 x86 的程序. 唯一需要注意的是, 需要在 VS 的窗口选择 Release (模拟更加真实的环境), x86 (目标架构), 最后选择编译. )

Hello World Example

Hello World Example (Too Easy Skipped)
#include <stdio.h>

int main(void) {
  printf("Hello World\n");
  return 0;
}
一个吐槽

关于工程这件事, 可能你会认为啊, 我 TMD 就写一个傻逼 Hello World, 为什么还要搞这么麻烦.

但是还是很有必要的, 项目版本管理, 工程文件管理等等, 都是一个完整项目不应该缺少的东西. 尤其是后期想要把项目做大做强的话 (bushi).

那么使用 Ghidra 来对 VS 编译生成的文件进行逆向:

                     **************************************************************
                     *                          FUNCTION                          *
                     **************************************************************
                     int __cdecl main(int _Argc, char * * _Argv, char * * _Env)
                       assume FS_OFFSET = 0xffdff000
     int               EAX:4          <RETURN>
     int               Stack[0x4]:4   _Argc
     char * *          Stack[0x8]:4   _Argv
     char * *          Stack[0xc]:4   _Env
                     _main                                           XREF[1]:     __scrt_common_main_seh:00401218(
                     main
00401040 68 00 21        PUSH       s_Hello_World_                                   = »Hello World «
         40 00
00401045 e8 c6 ff        CALL       printf                                           int printf(char * _Format, ...)
         ff ff
0040104a 83 c4 04        ADD        ESP,0x4
0040104d 33 c0           XOR        EAX,EAX
0040104f c3              RET

(注: 定位程序的方法还是原来那一套, 根据字符串来找函数的位置. )

那么我们可在打印前和打印后下断点. (注: 这里有一个坑爹的地方, 需要让 x32dbg 进入管理员模式才能够和 Ghidra 进行沟通. )

Simple Branch

Simple Branch
#include <stdio.h>

int check(int passwd) {
  int total = 45;
  if (passwd < 0) return 0;
  if (passwd > 10) return 0;
  while (passwd--) total -= passwd;
  if (total) return 0;
  return 1;
}

int main(void) {
  int passwd;
  printf("Oh my load, plz give me your passwd:");
  scanf("%d", &passwd);
  if (check(passwd)) {
    puts("Yes, you're my load.");
  } else {
    puts("Oh wait, who are you?");
  }
  return 0;
}

这个程序的源代码非常的简单, 之后会试试用这种简单的东西来做一些有趣的事情.

看看它的编译结果

实际上没什么好看的

说实话, 我又不会肉眼编译, 体系结构课程也根本没学, 跟我说什么看汇编代码来理解程序的话, 对现在的我可能有点太难了.

                     **************************************************************
                     *                          FUNCTION                          *
                     **************************************************************
                     int __cdecl main(int _Argc, char * * _Argv, char * * _Env)
                       assume FS_OFFSET = 0xffdff000
     int               EAX:4          <RETURN>
     int               Stack[0x4]:4   _Argc
     char * *          Stack[0x8]:4   _Argv
     char * *          Stack[0xc]:4   _Env
     undefined4        Stack[-0x8]:4  local_8                                 XREF[2]:     0040108d(W), 
                                                                                           004010dd(R)  
     undefined4        Stack[-0xc]:4  local_c                                 XREF[2]:     0040109a(*), 
                                                                                           004010a8(R)  
                     _main                                           XREF[1]:     __scrt_common_main_seh:004012b8(
                     main
00401080 55              PUSH       EBP
00401081 8b ec           MOV        EBP,ESP
00401083 83 ec 08        SUB        ESP,0x8
00401086 a1 00 30        MOV        EAX,[__security_cookie]                          = BB40E64Eh
         40 00
0040108b 33 c5           XOR        EAX,EBP
0040108d 89 45 fc        MOV        dword ptr [EBP + local_8],EAX
00401090 68 08 21        PUSH       s_Oh_my_load,_plz_give_me_your_pa                = "Oh my load, plz give me your 
         40 00
00401095 e8 86 ff        CALL       printf                                           int printf(char * _Format, ...)
         ff ff
0040109a 8d 45 f8        LEA        EAX=>local_c,[EBP + -0x8]
0040109d 50              PUSH       EAX
0040109e 68 30 21        PUSH       s_%d                                             = "%d"
         40 00
004010a3 e8 a8 ff        CALL       scanf                                            int scanf(char * _Format, ...)
         ff ff
004010a8 8b 45 f8        MOV        EAX,dword ptr [EBP + local_c]
004010ab 83 c4 0c        ADD        ESP,0xc
004010ae b9 2d 00        MOV        ECX,0x2d
         00 00
004010b3 85 c0           TEST       EAX,EAX
004010b5 78 1b           JS         LAB_004010d2
004010b7 83 f8 0a        CMP        EAX,0xa
004010ba 7f 16           JG         LAB_004010d2
004010bc 85 c0           TEST       EAX,EAX
004010be 74 12           JZ         LAB_004010d2
                     LAB_004010c0                                    XREF[1]:     004010c5(j)  
004010c0 48              DEC        EAX
004010c1 2b c8           SUB        ECX,EAX
004010c3 85 c0           TEST       EAX,EAX
004010c5 75 f9           JNZ        LAB_004010c0
004010c7 85 c9           TEST       ECX,ECX
004010c9 75 07           JNZ        LAB_004010d2
004010cb 68 34 21        PUSH       s_Yes,_you're_my_load.                           = "Yes, you're my load."
         40 00
004010d0 eb 05           JMP        LAB_004010d7
                     LAB_004010d2                                    XREF[4]:     004010b5(j), 004010ba(j), 
                                                                                  004010be(j), 004010c9(j)  
004010d2 68 4c 21        PUSH       s_Oh_wait,_who_are_you?                          = "Oh wait, who are you?"
         40 00
                     LAB_004010d7                                    XREF[1]:     004010d0(j)  
004010d7 ff 15 b8        CALL       dword ptr [->API-MS-WIN-CRT-STDIO-L1-1-0.DLL::   = 00002834
         20 40 00
004010dd 8b 4d fc        MOV        ECX,dword ptr [EBP + local_8]
004010e0 83 c4 04        ADD        ESP,0x4
004010e3 33 cd           XOR        ECX,EBP
004010e5 33 c0           XOR        EAX,EAX
004010e7 e8 04 00        CALL       __security_check_cookie                          void __security_check_cookie(uin
         00 00
004010ec 8b e5           MOV        ESP,EBP
004010ee 5d              POP        EBP
004010ef c3              RET

那么直接看反编译的结果吧:

main 函数
int __cdecl main(int _Argc,char **_Argv,char **_Env)

{
  int iVar1;
  char *_Str;
  int local_c;
  uint local_8;

  local_8 = __security_cookie ^ (uint)&stack0xfffffffc;
  printf("Oh my load, plz give me your passwd:");
  scanf("%d",&local_c);
  iVar1 = 0x2d;
  if (((-1 < local_c) && (local_c < 0xb)) && (local_c != 0)) {
    do {
      local_c = local_c + -1;
      iVar1 = iVar1 - local_c;
    } while (local_c != 0);
    if (iVar1 == 0) {
      _Str = "Yes, you\'re my load.";
      goto LAB_004010d7;
    }
  }
  _Str = "Oh wait, who are you?";
LAB_004010d7:
  puts(_Str);
  iVar1 = 0;
  __security_check_cookie(local_8 ^ (uint)&stack0xfffffffc);
  return iVar1;
}

实际上大部分时间并不会关心这个函数, 看代码的顺序基本上就是:

  1. _Str = "Yes, you\'re my load."; 哦, 原来成功分支在这里
  2. iVar1 实际上编译器直接做了优化, 把函数调用给优化掉了的样子. 关于 iVar1 的部分的代码实际上就是 check 函数里面的内容. 于是想要达成成功分支, 就需要让 iVar1 满足等于零的条件.

    尽管这个可能比较容易求解 (毕竟就只是一个等差数列求和, 也就是 iVar1 == (local_c + 1) * local_c / 2 => local_c = 9). 如果对于比较困难的问题的话, 可以考虑尝试巨硬的 Z3 或者别的方式来实现. 之后可以找一个比较困难的例子来试试.

Simple Functions

来点稍微复杂一点的程序:

#include <stdio.h>

一些其他的例子

Radare 2

虽然但是, r2 不是一个只面向 Windows 的一个逆向工具. 并且相比其他的几个工具, 它好像不是那么的好学… 你说的对, 但是我希望一个能够不用切出编辑器就能够在终端里面用的工具, 方便我上课作笔记的同时摸鱼

一些简单的介绍
digraph {
  rankdir = LR;
  node [shape = rect];
  
}
  • ? 显示帮助命令, 之后的命令就用命令里面的文字来记录
  • 静态分析部分
    • aaa perform deeper analysis, most common use
    • afl list functions, ii imports
    • s func/addr/sym seek command
    • pdf disassemble function. 如果安装了 r2ghidra 的话, 使用 pdg: Decompile current function with the Ghidra decompiler.
    • VV 程序框图
    • v visual-mode 可视化模式 Visual-Mode
  • 动态调试部分
    • r2 -d progn 来进行本地的调试
    • db debug breakpoint, dc debug continue, ood restart debug

更加好用的一些较完整的 Cheatsheet:

一些训练

Just in Time 康复训练

关于我被被 Windows 程序薄纱, 选择去做一些简单的 Linux 程序来安慰自己. (Difficulty = 2.0)

例子来源于 crackmesJust in Time.

因为是 stripped 的程序, 所以不太好找入口, 字符串查找大法可能不是那么好用了. (因为里面做了一个小操作). 于是使用 ltrace 试图碰碰运气:

...
putchar(32, 0x55d19180c4c0, 0xc0c0ffffe1ff0000, 0) = 32
strlen("\224\346\362\324\356J\352\314\360\360\370\350\356\322~J") = 16
fgets(Enter password:
...

于是确定应该是通过 fgets 之类的函数来作为入口的节点. 在 gdb 里面用 b putchar 来对 putchar 做断点, r 运行后, 使用 fin 跳出断点, 于是就来到了一些合理的函数里面, 就容易控制程序了.

控制程序的一个抄写:

int check_input(char *input) {
  // time = localtime(time);
  int shift = ((time % 10) % 3) & 0xf; // code is 0x1f
  if (input[0] == '%' &&               /* % */
      input[1] << shift == 200 &&      /* 0xc8 >> shift */
      input[2] << shift == 0xd4 &&     /* 0xd4 >> shift */
      input[3] == 'k' &&               /* k */
      input[4] << shift == 0x50 &&     /* 0x50 >> shift */
      input[5] == '9' &&               /* 9 */
      input[6] == '^' &&               /* ^ */
      input[7] << shift == 0xf6 &&     /* 0xf6 >> shift */
      input[8] == '.' &&               /* . */
      input[9] == 'f' &&               /* f */
      input[10] << shift == 0x80 &&    /* 0x80 >> shift */
      input[11] == '1' &&              /* 1 */
      input[12] == 'F' &&              /* F */
      input[13] << shift == 0x68       /* 0x68 >> shift */
      ) {
    return 0;
  } else {
    return 1;
  }
}

那么差不多就好了吧… 大概吧.

KataVM Level 1 还是康复训练

例子来源于 Crackmes KataVM1

还是 Linux 端的一个例子, 尽管嘲笑我吧, 只会挑简单的来做…

实际上感觉这个问题可能更加适合静态分析而不是动态调试. 不过实际上感觉题目名称已经给了很多的提示了: VM, 不就是虚拟机么. 对于虚拟机类型的问题, 实际上就只要搞清楚每个指令部分在做什么即可. 不过我觉得目前如果让我遇到一个没有提示的 VM 的话, 估计我没法从代码里面看出来.

使用 radare2 列出信息并进入 main 函数部分, 然后用 pdg 查看反汇编:

      ┌────────────────────────────────────────────────────┐
      │ [0x1170]                                           │
      │   ; DATA XREF from entry0 @ 0x11e1(r)              │
      │ 74: int main (int argc, char **argv, char **envp); │
      │ endbr64                                            │
      │ sub rsp, 8                                         │
      │ call fcn.000012d0;[oa]                             │
      │ test al, al                                        │
      │ jne 0x11ad                                         │
      └────────────────────────────────────────────────────┘
                 f t
                 │ │
                 │ └────────────────────┐
        ┌────────┘                      │
        │                               │
┌──────────────────────────┐    ┌───────────────────────────────────┐
│  0x1181 [od]             │    │  0x11ad [oi]                      │
│ ; "\n[+] Correct!"       │    │ ; CODE XREF from main @ 0x117f(x) │
│ lea rdi, [0x00006b22]    │    │ ; "\n[!] Invalid. Try harder."    │
│ call fcn.000010c0;[oc]   │    │ lea rdi, [0x00006b30]             │
│ jmp 0x1195               │    │ call fcn.000010c0;[oc]            │
└──────────────────────────┘    │ jmp 0x11a6                        │
                                └───────────────────────────────────┘

注: 未完结, 下周先去做别的事情先.