前言
这几天在我无聊逛 github 的时候,发现了一个写着能导出微信数据库密码和聊天记录的仓库,就想下载运行看看效果如何。
app.zip
(2.05 MB, 下载次数: 32)
然而启动后且报错 pass error
,显然是需要个正确密码。
之后试用了下确实能用,但我很好奇 dump 个微信密钥就这为什么还得需要传个密码才能启动,而且 git 上也没源码只放了 release,第一想法以为可能是暗藏了根据电脑时间来做运行限制之类的暗桩,比如超过某段时间后算法会计算要提供新的密码才能用,也可能是这程序本来是在 shell 上用的,为了防止被蓝队拿了然后去分析或者二次利用。(其实我也经常想给自己的程序加个启动密码。另外 README 本有写要传密码,刚开始没注意))
所以想看看是为什么要这么搞,能不能把密码限制给去掉。
初步分析
用 exeinfope 查了目标程序,无壳,64 位程序,用 go 语言写的。可以直接上 IDA 分析,或者用 x64dbg。
之前没分析过编译后的 go 程序,大概知道 golang 是静态链接的,封装层次高,有多返回值特点,gc、协程实现,标准库第三方库等等都嵌入了,文件体积贼大。
载入 IDA,能看到一大坨的函数,不可能一个个都去看的。后面干啃去调试时,函数间的跳转比较饶,加上有异步,容易跑飞。
像这种应该就是被 strip 掉了符号,go build 本身有命令可以去掉
go build ldflags="-s -w" main.go
也没找到 .gopclntab
区段
开局,搜索字符串
按老套路,最简单的搜索字符串,定位关键代码
然而可惜的是,这并没有什么用,都找不到。这时猜测要么就是故意加密了,要么就是 go 字符串结构比较特殊。只能采取其他方法。
还原符号
由于启动密码的判断都写在程序的开头,如果能找到入口点,分析起来就简单不少,虽然这个程序没有调试符号,但一般情况就算 go 编译时去掉了,也还会包含一定量的信息,能还原出一些函数符号和源码文件路径。
我借助了 IDAPython
运行 go_parse 和 IDAGolangHelper
那些脚本来解析,希望能方便后续分析。
然而可惜的是,不是有报错就是解析不全面,字符串也没解析出来,没有看到 main_init
main_main
入口函数,当然可能是 go 版本较高,pclntab 结构有变化,一些工具还不支持。
不过至少还原了部分 runtime,这总比干啃好。
有些奇怪的是,解析之后不少函数名都是随机字符串。我也不确定这程序是经过了混淆保护了,还是 golang 本身有其他什么设置就能导致如此。
额外说一下:有时候 go 版本号可以在 ida string 列表找到。(虽然这个找不到)
go 的汇编代码底部会有个 jmp 到函数头,用这个特征可以识别出函数体,IDA 有时识别不出来,手动按 c 可以强制分析成代码
IDA rebase 一下基址或导出 map 方便在调试时候对着看
动态调试 API 断点回溯
接着尝试从 API 下断入手,逼近关键代码,首先很容易想到获取启动参数的 API 会用到 GetCommandLineW
,go 用 fmt.println
写出到控制台的 API 则可能是 WriteFile
,WriteConsoleW
(64 位一般直接断 W),载入程序后下断点运行。
返回用户模块空间,接下来每一次返回提前在 ret 处下好断点,很容易跟飞
中间遇到循环不用管,一直专注回溯
同时注意堆栈字符串的变化,println 是分成时间和 pass error
两段。
看到这里再往后一两次 ret 差不多就到了。
这地方就差不多到了,在函数头下断点,,如果不确定就多下 F2,重载运行,输入参数。
0000000000DE5F60 | 4C:8D6424 E8 | lea r12,qword ptr ss:[rsp-18] |
这种方法还是比较绕的,尤其是没有符号和不熟悉的情况,下面还有其他方法。
跟踪数据读写
命令行参数值始终是要判断的,用 go 写个 demo,大概写法就是:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) > 2 {
if os.Args[1] == "123456" {
fmt.Printf("os.Args[1]: %v\n", os.Args[1])
} else {
fmt.Println("pass error")
}
} else {
fmt.Println("error 1")
}
}
在没有符号的情况下,可以通过对比自己写的 demo 汇编结构和 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 中初始化的。
启动后对 GetCommandLineW
下段,单步往下,对 rax 的第一个参数下硬件访问断点
F9 运行来到这里,看起来只是在计算长度,继续运行
循环取值到 edi
,然后赋值转移到 [rax+rbx*4]
,那也在这里下个硬件断点
接着运行
这些地方大概是在将 unicode 的 string 转换编码,单步往下走,末尾 ret 下断,eax 有有新生成的字符串地址
继续单步下去,观察 eax 用到哪
可以发现赋给了一个值
mov qword ptr ds:[0x0000000000F0F300], rax
这里就是 os.Args,这应该是个通用特征,可以通过搜索常量引用或者硬件下断都可以定位到校验方法
0000000000DE5F60 | 4C:8D6424 E8 | lea r12,qword ptr ss:[rsp-18] |
patch 代码
很简单了,有字符串比较,跟一遍,只要 nop 掉两处跳转即可
另外字符串是用这些方法移位解密的,怪不得搜不到。
0000000000DE58C0 | 49:3B66 10 | cmp rsp,qword ptr ds:[r14+10] |
0000000000DE58C4 | 0F86 EE010000 | jbe target.DE5AB8 |
0000000000DE58CA | 48:83EC 70 | sub rsp,70 |
0000000000DE58CE | 48:896C24 68 | mov qword ptr ss:[rsp+68],rbp | [rsp+68]:"袩\r"
0000000000DE58D3 | 48:8D6C24 68 | lea rbp,qword ptr ss:[rsp+68] | [rsp+68]:"袩\r"
0000000000DE58D8 | 48:BA E4E18554A2A332C3 | mov rdx,C332A3A25485E1E4 |
0000000000DE58E2 | 48:895424 54 | mov qword ptr ss:[rsp+54],rdx |
0000000000DE58E7 | 48:BA A2A332C3BFD17EA1 | mov rdx,A17ED1BFC332A3A2 |
0000000000DE58F1 | 48:895424 58 | mov qword ptr ss:[rsp+58],rdx |
0000000000DE58F6 | 48:BA C5CDCF5350E17134 | mov rdx,3471E15053CFCDC5 |
0000000000DE5900 | 48:895424 60 | mov qword ptr ss:[rsp+60],rdx |
0000000000DE5905 | 0FB65424 55 | movzx edx,byte ptr ss:[rsp+55] |
0000000000DE590A | 885424 53 | mov byte ptr ss:[rsp+53],dl |
0000000000DE590E | 44:0FB64424 66 | movzx r8d,byte ptr ss:[rsp+66] |
0000000000DE5914 | 44:884424 52 | mov byte ptr ss:[rsp+52],r8b |
0000000000DE5919 | 44:0FB64C24 54 | movzx r9d,byte ptr ss:[rsp+54] |
0000000000DE591F | 44:884C24 51 | mov byte ptr ss:[rsp+51],r9b |
0000000000DE5924 | 44:0FB65424 56 | movzx r10d,byte ptr ss:[rsp+56] |
0000000000DE592A | 44:885424 50 | mov byte ptr ss:[rsp+50],r10b |
0000000000DE592F | 44:0FB65C24 5B | movzx r11d,byte ptr ss:[rsp+5B] |
0000000000DE5935 | 44:885C24 4F | mov byte ptr ss:[rsp+4F],r11b |
0000000000DE593A | 44:0FB66424 64 | movzx r12d,byte ptr ss:[rsp+64] |
0000000000DE5940 | 44:886424 4E | mov byte ptr ss:[rsp+4E],r12b |
0000000000DE5945 | 44:0FB66C24 57 | movzx r13d,byte ptr ss:[rsp+57] |
0000000000DE594B | 44:886C24 4D | mov byte ptr ss:[rsp+4D],r13b |
0000000000DE5950 | 44:0FB67C24 65 | movzx r15d,byte ptr ss:[rsp+65] |
0000000000DE5956 | 44:887C24 4C | mov byte ptr ss:[rsp+4C],r15b |
0000000000DE595B | 44:0FB66C24 58 | movzx r13d,byte ptr ss:[rsp+58] |
0000000000DE5961 | 44:886C24 4B | mov byte ptr ss:[rsp+4B],r13b |
0000000000DE5966 | 44:0FB66C24 5E | movzx r13d,byte ptr ss:[rsp+5E] |
0000000000DE596C | 44:886C24 4A | mov byte ptr ss:[rsp+4A],r13b |
0000000000DE5971 | 44:0FB66C24 67 | movzx r13d,byte ptr ss:[rsp+67] |
0000000000DE5977 | 44:886C24 49 | mov byte ptr ss:[rsp+49],r13b |
0000000000DE597C | 44:0FB66C24 62 | movzx r13d,byte ptr ss:[rsp+62] |
0000000000DE5982 | 44:886C24 48 | mov byte ptr ss:[rsp+48],r13b |
0000000000DE5987 | 44:0FB66C24 61 | movzx r13d,byte ptr ss:[rsp+61] |
0000000000DE598D | 44:886C24 47 | mov byte ptr ss:[rsp+47],r13b |
0000000000DE5992 | 44:0FB66C24 5C | movzx r13d,byte ptr ss:[rsp+5C] |
0000000000DE5998 | 44:886C24 46 | mov byte ptr ss:[rsp+46],r13b |
0000000000DE599D | 44:0FB66C24 60 | movzx r13d,byte ptr ss:[rsp+60] |
0000000000DE59A3 | 44:886C24 45 | mov byte ptr ss:[rsp+45],r13b |
0000000000DE59A8 | 44:0FB66C24 63 | movzx r13d,byte ptr ss:[rsp+63] |
0000000000DE59AE | 44:886C24 44 | mov byte ptr ss:[rsp+44],r13b |
0000000000DE59B3 | 44:0FB66C24 5F | movzx r13d,byte ptr ss:[rsp+5F] |
0000000000DE59B9 | 44:886C24 43 | mov byte ptr ss:[rsp+43],r13b |
0000000000DE59BE | 44:0FB66C24 59 | movzx r13d,byte ptr ss:[rsp+59] |
0000000000DE59C4 | 44:886C24 42 | mov byte ptr ss:[rsp+42],r13b |
0000000000DE59C9 | 44:0FB66C24 5A | movzx r13d,byte ptr ss:[rsp+5A] |
0000000000DE59CF | 44:886C24 41 | mov byte ptr ss:[rsp+41],r13b |
0000000000DE59D4 | 44:0FB66C24 5D | movzx r13d,byte ptr ss:[rsp+5D] |
0000000000DE59DA | 44:886C24 40 | mov byte ptr ss:[rsp+40],r13b |
0000000000DE59DF | 48:8D05 5AAE0000 | lea rax,qword ptr ds:[DF0840] | 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:[rsp+53] |
0000000000DE59FC | 44:0FB64424 52 | movzx r8d,byte ptr ss:[rsp+52] |
0000000000DE5A02 | 44:29C2 | sub edx,r8d |
0000000000DE5A05 | 8810 | mov byte ptr ds:[rax],dl | rax:"pass er"
0000000000DE5A07 | 0FB65424 50 | movzx edx,byte ptr ss:[rsp+50] |
0000000000DE5A0C | 44:0FB64424 51 | movzx r8d,byte ptr ss:[rsp+51] |
0000000000DE5A12 | 44:31C2 | xor edx,r8d |
0000000000DE5A15 | 8850 01 | mov byte ptr ds:[rax+1],dl | rax+1:"ass er"
0000000000DE5A18 | 0FB65424 4F | movzx edx,byte ptr ss:[rsp+4F] |
0000000000DE5A1D | 44:0FB64424 4E | movzx r8d,byte ptr ss:[rsp+4E] |
0000000000DE5A23 | 44:29C2 | sub edx,r8d |
0000000000DE5A26 | 8850 02 | mov byte ptr ds:[rax+2],dl | rax+2:"ss er"
0000000000DE5A29 | 0FB65424 4D | movzx edx,byte ptr ss:[rsp+4D] |
0000000000DE5A2E | 44:0FB64424 4C | movzx r8d,byte ptr ss:[rsp+4C] |
0000000000DE5A34 | 44:29C2 | sub edx,r8d |
0000000000DE5A37 | 8850 03 | mov byte ptr ds:[rax+3],dl | rax+3:"s er"
0000000000DE5A3A | 0FB65424 4A | movzx edx,byte ptr ss:[rsp+4A] |
0000000000DE5A3F | 44:0FB64424 4B | movzx r8d,byte ptr ss:[rsp+4B] |
0000000000DE5A45 | 44:01C2 | add edx,r8d |
0000000000DE5A48 | 8850 04 | mov byte ptr ds:[rax+4],dl | rax+4:" er"
0000000000DE5A4B | 0FB65424 49 | movzx edx,byte ptr ss:[rsp+49] |
0000000000DE5A50 | 44:0FB64424 48 | movzx r8d,byte ptr ss:[rsp+48] |
0000000000DE5A56 | 44:29C2 | sub edx,r8d |
0000000000DE5A59 | 8850 05 | mov byte ptr ds:[rax+5],dl | rax+5:"er"
0000000000DE5A5C | 0FB65424 46 | movzx edx,byte ptr ss:[rsp+46] |
0000000000DE5A61 | 44:0FB64424 47 | movzx r8d,byte ptr ss:[rsp+47] |
0000000000DE5A67 | 44:31C2 | xor edx,r8d |
0000000000DE5A6A | 8850 06 | mov byte ptr ds:[rax+6],dl |
0000000000DE5A6D | 0FB65424 45 | movzx edx,byte ptr ss:[rsp+45] |
0000000000DE5A72 | 44:0FB64424 44 | movzx r8d,byte ptr ss:[rsp+44] |
0000000000DE5A78 | 44:29C2 | sub edx,r8d |
0000000000DE5A7B | 8850 07 | mov byte ptr ds:[rax+7],dl |
0000000000DE5A7E | 0FB65424 43 | movzx edx,byte ptr ss:[rsp+43] |
0000000000DE5A83 | 44:0FB64424 41 | movzx r8d,byte ptr ss:[rsp+41] |
0000000000DE5A89 | 44:29C2 | sub edx,r8d |
0000000000DE5A8C | 8850 08 | mov byte ptr ds:[rax+8],dl |
0000000000DE5A8F | 0FB65424 40 | movzx edx,byte ptr ss:[rsp+40] |
0000000000DE5A94 | 44:0FB64424 42 | movzx r8d,byte ptr ss:[rsp+42] |
0000000000DE5A9A | 44:31C2 | xor edx,r8d |
0000000000DE5A9D | 8850 09 | mov byte ptr ds:[rax+9],dl |
0000000000DE5AA0 | 48:8D4B 0A | lea rcx,qword ptr ds:[rbx+A] |
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:[rsp+68] | [rsp+68]:"袩\r"
0000000000DE5AB3 | 48:83C4 70 | add rsp,70 |
0000000000DE5AB7 | C3 | ret |
0000000000DE5AB8 | E8 63C0EAFF | call target.C91B20 |
0000000000DE5ABD | 0F1F00 | nop dword ptr ds:[rax],eax |
0000000000DE5AC0 | E9 FBFDFFFF | jmp target.DE58C0 |
没什么技术含量,以上纯属初次学习和分享。附件包含目标程序、和 patch 过后的程序。
debug 一小时,写文章好久了,有些图片截图的时间比较久了,后面重新换了,所以可能会有些输出对不上