蚯蚓翔龙 发表于 2022-9-4 17:36

第一次尝试粗浅的逆向破解golang程序

## 前言

这几天在我无聊逛 github 的时候,发现了一个写着能导出微信数据库密码和聊天记录的仓库,就想下载运行看看效果如何。

!(https://img1.imgtp.com/2022/09/04/TFLDmIkr.png)​

然而启动后且报错 `pass error`,显然是需要个正确密码。

!(https://img1.imgtp.com/2022/09/04/VjRr64qi.png)​

之后试用了下确实能用,但我很好奇 dump 个微信密钥就这为什么还得需要传个密码才能启动,而且 git 上也没源码只放了 release,第一想法以为可能是暗藏了根据电脑时间来做运行限制之类的暗桩,比如超过某段时间后算法会计算要提供新的密码才能用,也可能是这程序本来是在 shell 上用的,为了防止被蓝队拿了然后去分析或者二次利用。(*其实我也经常想给自己的程序加个启动密码。另外 README 本有写要传密码,刚开始没注意)*)

所以想看看是为什么要这么搞,能不能把密码限制给去掉。

## 初步分析

用 exeinfope 查了目标程序,无壳,64 位程序,用 go 语言写的。可以直接上 IDA 分析,或者用 x64dbg。

!(https://img1.imgtp.com/2022/09/04/VkKtm3eW.png)

之前没分析过编译后的 go 程序,大概知道 golang 是静态链接的,封装层次高,有多返回值特点,gc、协程实现,标准库第三方库等等都嵌入了,文件体积贼大。

载入 IDA,能看到一大坨的函数,不可能一个个都去看的。后面干啃去调试时,函数间的跳转比较饶,加上有异步,容易跑飞。

!(https://img1.imgtp.com/2022/09/04/VTTG1afW.png)

像这种应该就是被 strip 掉了符号,go build 本身有命令可以去掉

```bash
go build ldflags="-s -w" main.go
```

也没找到 `.gopclntab` 区段

## 开局,搜索字符串

按老套路,最简单的搜索字符串,定位关键代码

!(https://img1.imgtp.com/2022/09/04/uvHSYTrw.png)​

然而可惜的是,这并没有什么用,都找不到。这时猜测要么就是故意加密了,要么就是 go 字符串结构比较特殊。只能采取其他方法。

## 还原符号

由于启动密码的判断都写在程序的开头,如果能找到入口点,分析起来就简单不少,虽然这个程序没有调试符号,但一般情况就算 go 编译时去掉了,也还会包含一定量的信息,能还原出一些函数符号和源码文件路径。

我借助了 `IDAPython` 运行 (https://github.com/0xjiayu/go_parser) 和 `IDAGolangHelper` 那些脚本来解析,希望能方便后续分析。

然而可惜的是,不是有报错就是解析不全面,字符串也没解析出来,没有看到 `main_init` `main_main` 入口函数,当然可能是 go 版本较高,pclntab 结构有变化,一些工具还不支持。

不过至少还原了部分 runtime,这总比干啃好。

!(https://img1.imgtp.com/2022/09/04/jWgWnbRn.png)

有些奇怪的是,解析之后不少函数名都是随机字符串。我也不确定这程序是经过了混淆保护了,还是 golang 本身有其他什么设置就能导致如此。

!(https://img1.imgtp.com/2022/09/04/ddI0fmiA.png)​

额外说一下:有时候 go 版本号可以在 ida string 列表找到。(虽然这个找不到)

!(https://img1.imgtp.com/2022/09/04/63scSIy7.png)​

go 的汇编代码底部会有个 jmp 到函数头,用这个特征可以识别出函数体,IDA 有时识别不出来,手动按 c 可以强制分析成代码

!(https://img1.imgtp.com/2022/09/04/EtiJgD5X.png)

IDA rebase 一下基址或导出 map 方便在调试时候对着看

!(https://img1.imgtp.com/2022/09/04/z60o9nDv.png)​

## 动态调试 API 断点回溯

接着尝试从 API 下断入手,逼近关键代码,首先很容易想到获取启动参数的 API 会用到 `GetCommandLineW`,go 用 `fmt.println` 写出到控制台的 API 则可能是 `WriteFile`,`WriteConsoleW`(64 位一般直接断 W),载入程序后下断点运行。

!(https://img1.imgtp.com/2022/09/04/bp79epEC.png)​

返回用户模块空间,接下来每一次返回提前在 ret 处下好断点,很容易跟飞

!(https://img1.imgtp.com/2022/09/04/1zjtt0oQ.png)

!(https://img1.imgtp.com/2022/09/04/nlJ8rcHJ.png)

中间遇到循环不用管,一直专注回溯

!(https://img1.imgtp.com/2022/09/04/mHpavuvK.png)

同时注意堆栈字符串的变化,println 是分成时间和 `pass error` 两段。

看到这里再往后一两次 ret 差不多就到了。

!(https://img1.imgtp.com/2022/09/04/Z7kTKF6G.png)

这地方就差不多到了,在函数头下断点,,如果不确定就多下 F2,重载运行,输入参数。

`0000000000DE5F60| 4C:8D6424 E8             | lea r12,qword ptr ss:                           |`

!(https://img1.imgtp.com/2022/09/04/o54ZgK4w.png)

这种方法还是比较绕的,尤其是没有符号和不熟悉的情况,下面还有其他方法。

## 跟踪数据读写

命令行参数值始终是要判断的,用 go 写个 demo,大概写法就是:

```go
package main

import (
      "fmt"
      "os"
)

func main() {
      if len(os.Args) > 2 {
                if os.Args == "123456" {
                        fmt.Printf("os.Args: %v\n", os.Args)
                } else {
                        fmt.Println("pass error")
                }
      } else {
                fmt.Println("error 1")
      }
}

```

在没有符号的情况下,可以通过对比自己写的 demo 汇编结构和 go 源码来人肉识别某些库函数。

```go
// These routines end in 'ln', do not take a format string,
// always add spaces between operands, and add a newline
// after the last operand.

// Fprintln formats using the default formats for its operands and writes to w.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Fprintln(w io.Writer, a ...any) (n int, err error) {
      p := newPrinter()
      p.doPrintln(a)
      n, err = w.Write(p.buf)
      p.free()
      return
}
...
......
```

os.Args 是一个全局切面变量,找到这个也可以定位。os.Args 是在 os.init 中初始化的。

!(https://img1.imgtp.com/2022/09/04/swtw9pyG.png)

启动后对 `GetCommandLineW` 下段,单步往下,对 rax 的第一个参数下硬件访问断点

!(https://img1.imgtp.com/2022/09/04/k7tSFa2A.png)​

F9 运行来到这里,看起来只是在计算长度,继续运行

!(https://img1.imgtp.com/2022/09/04/THwHUB19.png)

循环取值到 `edi`,然后赋值转移到 ``,那也在这里下个硬件断点

!(https://img1.imgtp.com/2022/09/04/PSbRJXKc.png)

接着运行

!(https://img1.imgtp.com/2022/09/04/JFwQADeB.png)​

这些地方大概是在将 unicode 的 string 转换编码,单步往下走,末尾 ret 下断,eax 有有新生成的字符串地址

!(https://img1.imgtp.com/2022/09/04/2CxWv5mt.png)​

继续单步下去,观察 eax 用到哪

!(https://img1.imgtp.com/2022/09/04/5YKmwxav.png)

可以发现赋给了一个值

!(https://img1.imgtp.com/2022/09/04/ypFg8ZQF.png)

!(https://img1.imgtp.com/2022/09/04/vO7SQQ0f.png)​

`mov qword ptr ds:, rax` 这里就是 os.Args,这应该是个通用特征,可以通过搜索常量引用或者硬件下断都可以定位到校验方法

`0000000000DE5F60| 4C:8D6424 E8             | lea r12,qword ptr ss:                           |`

!(https://img1.imgtp.com/2022/09/04/t7tDCauB.png)​

## patch 代码

很简单了,有字符串比较,跟一遍,只要 nop 掉两处跳转即可

!(https://img1.imgtp.com/2022/09/04/5H9mkyJe.png)​

另外字符串是用这些方法移位解密的,怪不得搜不到。

```x86asm
0000000000DE58C0| 49:3B66 10               | cmp rsp,qword ptr ds:         |
0000000000DE58C4| 0F86 EE010000            | jbe target.DE5AB8                     |
0000000000DE58CA| 48:83EC 70               | sub rsp,70                              |
0000000000DE58CE| 48:896C24 68             | mov qword ptr ss:,rbp         | :"袩\r"
0000000000DE58D3| 48:8D6C24 68             | lea rbp,qword ptr ss:         | :"袩\r"
0000000000DE58D8| 48:BA E4E18554A2A332C3   | mov rdx,C332A3A25485E1E4                |
0000000000DE58E2| 48:895424 54             | mov qword ptr ss:,rdx         |
0000000000DE58E7| 48:BA A2A332C3BFD17EA1   | mov rdx,A17ED1BFC332A3A2                |
0000000000DE58F1| 48:895424 58             | mov qword ptr ss:,rdx         |
0000000000DE58F6| 48:BA C5CDCF5350E17134   | mov rdx,3471E15053CFCDC5                |
0000000000DE5900| 48:895424 60             | mov qword ptr ss:,rdx         |
0000000000DE5905| 0FB65424 55            | movzx edx,byte ptr ss:          |
0000000000DE590A| 885424 53                | mov byte ptr ss:,dl             |
0000000000DE590E| 44:0FB64424 66         | movzx r8d,byte ptr ss:          |
0000000000DE5914| 44:884424 52             | mov byte ptr ss:,r8b            |
0000000000DE5919| 44:0FB64C24 54         | movzx r9d,byte ptr ss:          |
0000000000DE591F| 44:884C24 51             | mov byte ptr ss:,r9b            |
0000000000DE5924| 44:0FB65424 56         | movzx r10d,byte ptr ss:         |
0000000000DE592A| 44:885424 50             | mov byte ptr ss:,r10b         |
0000000000DE592F| 44:0FB65C24 5B         | movzx r11d,byte ptr ss:         |
0000000000DE5935| 44:885C24 4F             | mov byte ptr ss:,r11b         |
0000000000DE593A| 44:0FB66424 64         | movzx r12d,byte ptr ss:         |
0000000000DE5940| 44:886424 4E             | mov byte ptr ss:,r12b         |
0000000000DE5945| 44:0FB66C24 57         | movzx r13d,byte ptr ss:         |
0000000000DE594B| 44:886C24 4D             | mov byte ptr ss:,r13b         |
0000000000DE5950| 44:0FB67C24 65         | movzx r15d,byte ptr ss:         |
0000000000DE5956| 44:887C24 4C             | mov byte ptr ss:,r15b         |
0000000000DE595B| 44:0FB66C24 58         | movzx r13d,byte ptr ss:         |
0000000000DE5961| 44:886C24 4B             | mov byte ptr ss:,r13b         |
0000000000DE5966| 44:0FB66C24 5E         | movzx r13d,byte ptr ss:         |
0000000000DE596C| 44:886C24 4A             | mov byte ptr ss:,r13b         |
0000000000DE5971| 44:0FB66C24 67         | movzx r13d,byte ptr ss:         |
0000000000DE5977| 44:886C24 49             | mov byte ptr ss:,r13b         |
0000000000DE597C| 44:0FB66C24 62         | movzx r13d,byte ptr ss:         |
0000000000DE5982| 44:886C24 48             | mov byte ptr ss:,r13b         |
0000000000DE5987| 44:0FB66C24 61         | movzx r13d,byte ptr ss:         |
0000000000DE598D| 44:886C24 47             | mov byte ptr ss:,r13b         |
0000000000DE5992| 44:0FB66C24 5C         | movzx r13d,byte ptr ss:         |
0000000000DE5998| 44:886C24 46             | mov byte ptr ss:,r13b         |
0000000000DE599D| 44:0FB66C24 60         | movzx r13d,byte ptr ss:         |
0000000000DE59A3| 44:886C24 45             | mov byte ptr ss:,r13b         |
0000000000DE59A8| 44:0FB66C24 63         | movzx r13d,byte ptr ss:         |
0000000000DE59AE| 44:886C24 44             | mov byte ptr ss:,r13b         |
0000000000DE59B3| 44:0FB66C24 5F         | movzx r13d,byte ptr ss:         |
0000000000DE59B9| 44:886C24 43             | mov byte ptr ss:,r13b         |
0000000000DE59BE| 44:0FB66C24 59         | movzx r13d,byte ptr ss:         |
0000000000DE59C4| 44:886C24 42             | mov byte ptr ss:,r13b         |
0000000000DE59C9| 44:0FB66C24 5A         | movzx r13d,byte ptr ss:         |
0000000000DE59CF| 44:886C24 41             | mov byte ptr ss:,r13b         |
0000000000DE59D4| 44:0FB66C24 5D         | movzx r13d,byte ptr ss:         |
0000000000DE59DA| 44:886C24 40             | mov byte ptr ss:,r13b         |
0000000000DE59DF| 48:8D05 5AAE0000         | lea rax,qword ptr ds:         | rax:"pass er"
0000000000DE59E6| 31DB                     | xor ebx,ebx                           |
0000000000DE59E8| 31C9                     | xor ecx,ecx                           |
0000000000DE59EA| 48:89CF                  | mov rdi,rcx                           | rdi:"pass er"
0000000000DE59ED| BE 0A000000            | mov esi,A                               | A:'\n'
0000000000DE59F2| E8 89A4E9FF            | call target.C7FE80                      |
0000000000DE59F7| 0FB65424 53            | movzx edx,byte ptr ss:          |
0000000000DE59FC| 44:0FB64424 52         | movzx r8d,byte ptr ss:          |
0000000000DE5A02| 44:29C2                  | sub edx,r8d                           |
0000000000DE5A05| 8810                     | mov byte ptr ds:,dl                | rax:"pass er"
0000000000DE5A07| 0FB65424 50            | movzx edx,byte ptr ss:          |
0000000000DE5A0C| 44:0FB64424 51         | movzx r8d,byte ptr ss:          |
0000000000DE5A12| 44:31C2                  | xor edx,r8d                           |
0000000000DE5A15| 8850 01                  | mov byte ptr ds:,dl            | rax+1:"ass er"
0000000000DE5A18| 0FB65424 4F            | movzx edx,byte ptr ss:          |
0000000000DE5A1D| 44:0FB64424 4E         | movzx r8d,byte ptr ss:          |
0000000000DE5A23| 44:29C2                  | sub edx,r8d                           |
0000000000DE5A26| 8850 02                  | mov byte ptr ds:,dl            | rax+2:"ss er"
0000000000DE5A29| 0FB65424 4D            | movzx edx,byte ptr ss:          |
0000000000DE5A2E| 44:0FB64424 4C         | movzx r8d,byte ptr ss:          |
0000000000DE5A34| 44:29C2                  | sub edx,r8d                           |
0000000000DE5A37| 8850 03                  | mov byte ptr ds:,dl            | rax+3:"s er"
0000000000DE5A3A| 0FB65424 4A            | movzx edx,byte ptr ss:          |
0000000000DE5A3F| 44:0FB64424 4B         | movzx r8d,byte ptr ss:          |
0000000000DE5A45| 44:01C2                  | add edx,r8d                           |
0000000000DE5A48| 8850 04                  | mov byte ptr ds:,dl            | rax+4:" er"
0000000000DE5A4B| 0FB65424 49            | movzx edx,byte ptr ss:          |
0000000000DE5A50| 44:0FB64424 48         | movzx r8d,byte ptr ss:          |
0000000000DE5A56| 44:29C2                  | sub edx,r8d                           |
0000000000DE5A59| 8850 05                  | mov byte ptr ds:,dl            | rax+5:"er"
0000000000DE5A5C| 0FB65424 46            | movzx edx,byte ptr ss:          |
0000000000DE5A61| 44:0FB64424 47         | movzx r8d,byte ptr ss:          |
0000000000DE5A67| 44:31C2                  | xor edx,r8d                           |
0000000000DE5A6A| 8850 06                  | mov byte ptr ds:,dl            |
0000000000DE5A6D| 0FB65424 45            | movzx edx,byte ptr ss:          |
0000000000DE5A72| 44:0FB64424 44         | movzx r8d,byte ptr ss:          |
0000000000DE5A78| 44:29C2                  | sub edx,r8d                           |
0000000000DE5A7B| 8850 07                  | mov byte ptr ds:,dl            |
0000000000DE5A7E| 0FB65424 43            | movzx edx,byte ptr ss:          |
0000000000DE5A83| 44:0FB64424 41         | movzx r8d,byte ptr ss:          |
0000000000DE5A89| 44:29C2                  | sub edx,r8d                           |
0000000000DE5A8C| 8850 08                  | mov byte ptr ds:,dl            |
0000000000DE5A8F| 0FB65424 40            | movzx edx,byte ptr ss:          |
0000000000DE5A94| 44:0FB64424 42         | movzx r8d,byte ptr ss:          |
0000000000DE5A9A| 44:31C2                  | xor edx,r8d                           |
0000000000DE5A9D| 8850 09                  | mov byte ptr ds:,dl            |
0000000000DE5AA0| 48:8D4B 0A               | lea rcx,qword ptr ds:            |
0000000000DE5AA4| 48:89C3                  | mov rbx,rax                           | rax:"pass er"
0000000000DE5AA7| 31C0                     | xor eax,eax                           |
0000000000DE5AA9| E8 D2D6E9FF            | call target.C83180                      |
0000000000DE5AAE| 48:8B6C24 68             | mov rbp,qword ptr ss:         | :"袩\r"
0000000000DE5AB3| 48:83C4 70               | add rsp,70                              |
0000000000DE5AB7| C3                     | ret                                     |
0000000000DE5AB8| E8 63C0EAFF            | call target.C91B20                      |
0000000000DE5ABD| 0F1F00                   | nop dword ptr ds:,eax            |
0000000000DE5AC0| E9 FBFDFFFF            | jmp target.DE58C0                     |
```

没什么技术含量,以上纯属初次学习和分享。附件包含目标程序、和 patch 过后的程序。


*debug 一小时,写文章好久了,有些图片截图的时间比较久了,后面重新换了,所以可能会有些输出对不上*





蚯蚓翔龙 发表于 2022-9-4 23:48

|矢空 发表于 2022-9-4 21:16
IDA7.5 之后可以直接解析出go的函数符号,不需要插件了,可以方便一点

对于这个应该是strip过没法直接解析

jujumanman 发表于 2022-9-7 14:12

之前遇到过一个golang的程序,ida插件解析不出符号,现在看来可能也是strip掉了吧,学到了,感谢分享!

淡忘不了的记忆 发表于 2022-9-4 19:09

感谢分享

ciker_li 发表于 2022-9-4 19:12

感谢分享

hermanleung 发表于 2022-9-4 20:29

好强1111

|矢空 发表于 2022-9-4 21:16

IDA7.5 之后可以直接解析出go的函数符号,不需要插件了,可以方便一点

aspllh 发表于 2022-9-4 22:26

学习了..感谢大佬的教程!

lyn038111 发表于 2022-9-4 22:27

感谢分享,666

icjhao 发表于 2022-9-4 23:13

这个程序主要是做什么用的

kotlyne 发表于 2022-9-4 23:32

学习了学习了

友谊少年sss 发表于 2022-9-4 23:47

感谢分享
页: [1] 2 3
查看完整版本: 第一次尝试粗浅的逆向破解golang程序