(2023.8.28)Hi铁布衫-CM Ver 0.001 - Cracked-writeup

发布时间 2023-08-29 02:26:46作者: 小dbg

Hi铁布衫-CM Ver 0.001 WriteUp

本文作者:XDbgPYG(小吧唧)
发布时间:2023年8月28日
内容概要:Hi铁布衫-CM Ver 0.001 WriteUp

收集信息

有一段时间没写过代码了,源码早就忘光了,今天碰巧有时间,好好测试一下我的网络验证的强度。

PE 信息如下:

vc 8.0 above 是个很新的程序~

内存布局如下:

嗯,很帅。

字符串如下:

有一堆的 PYG 水印。(除了水印已无太多信息)

readMe:

  1. readMe 有提到程序是由 hi-Easyx 编写的。
  2. readMe 有提到程序由 登录窗口=> 功能窗口 组成。
  3. readMe 有提到这是一个 网络验证 CrackME

所以我们可以尝试通过 CreateWindow系列函数hi-Easyx相关字符串 进行定位 hi-Easyx 创建窗口的 Call 或 Sig(特征码)。

所以我们可以尝试通过 sendrecv 等网络函数进行回溯=>分析验证的加密算法、解密算法等对于网络验证来说很重要的东西。

调试

创建窗口函数的分析-失败

可以看到有个 Error creating window: 字符串,转到目标地址,进行控制流分析。

可以看出程序是通过 CreateWindowExW 创建的窗口,而在其地址上方有个 可能的关键信息:wchar_t *HiEasyX::g_lpszClassName

我习惯先对字符串进行搜索引用(函数的调用可能会有很多),经过引用搜索,发现俩个字符串都位于该函数中。

void __cdecl HiEasyX::InitWindow(...)

对这个函数进行引用分析,发现只有 1 个引用。

0x0007FF7169B8D4A lea rcx, ds:[0x0x0007FF7169B7C80]

继续回溯,看样子想通过 className 进行定位 创建窗口 函数的想法是泡汤了。

0x0007FF7169B8FA2 call HWND__* __cdecl HiEasyX::initgraph_win32(...)

累了,不跟踪了。

通过 send recv 定位 1 个关键点范围

如果程序的客户端验证没有使用到线程池技术(不考虑有保护的情况)的话,那么有趣的事情就会这样发生。

有线程池情况的 处理包流程图:没学原理,到时候给验证添加线程池后再来补好了。

无线程池(有循环)获取一段数据的情况:

无线程池(无循环)获取一段数据的情况:

现在搜索一下 send 的引用,发现许多调用,看样子程序是把 发包函数inline 了。

现在搜索一下 recv 的引用,看样子程序是把 收包函数inline 了。

不知道 收包函数、发包函数 是否有多种加密解密情况,如果有的话,就不逆向算法了。

有趣的地方来了,我似乎没有对缓冲区进行清理导致直接获得了一个信息 web|notice 。很明显,程序这是在获取公告。

既然如此,就碰碰运气,对所有 recv 函数的引用地址下断点,重启下程序,进行简单的测试。

获取公告
0x0007FF7169C1A2B    | FF15 5FFB0600         | call qword ptr ds:[<&recv>]                                            |

循环获取公告(看控制流有多次调用、应该是循环获取公告)
0x0007FF7169C1D13    | FF15 77F80600         | call qword ptr ds:[<&recv>]                                            |

登录
login|pygChina|UserPassWord|superPassWord
0x0007FF7169C4C9F | FF15 EBC80600          | call qword ptr ds:[<&recv>]                                            |

定位到 获取登录返回值 的地址了,对 recvbuffer 下硬件访问断点。

看样子程序是调用了 rc4 加密。
0x0007FF7169B9BF0 CryptoLibEx::RC4::Encode(unsigned char *, unsigned char *, int)


0x0007FF7169B9CCA | 48:8B05 575D0C00       | mov rax,qword ptr ds:[<class CryptoLibEx::RC4 rc4>]        | 0x0007FF716A7FA28:&"1234567890..."

0x0007FF7169B9D8D | 41:3203                | xor al,byte ptr ds:[r11]                                   |

看样子程序是调用了 rc4 解密。
0x0007FF7169B9DE0 public: class CryptoLib::stringx __cdecl CryptoLibEx::RC4::Decode(unsigned char *, int)

既然现在找到了验证的包加密、解密算法。那山寨一个出来就不是难题了。

CryptoLibEx::RC4::Decode 搜索引用下断点,并进行跟踪调试。

登录返回值 L"bad..."。
0x0007FF7169C4CB3 | E8 2851FFFF            | call <hi铁布衫-cm ver 0.001.public: class CryptoLib::stringx __cdecl |

将 L"bad..." 转换为 "bad...",即 unicode2Asccii()。
0x0007FF7169C4CC4 | E8 A7B10x000            | call <hi铁布衫-cm ver 0.001.public: class std::basic_string<char, struc |

对字符串下硬件访问断点。可以看到如下关键信息。
0x0007FF7169C4DF7 | 4C:8B8424 00070000    | mov r8,qword ptr ss:[rsp + 0x700]                                     |
0x0007FF7169C4DFF | 4C:3BC0               | cmp r8,rax                                                            |
0x0007FF7169C4E02 | 0F85 051D0000         | jne hi铁布衫-cm ver 0.001.7FF7169C6B0D                                   |
0x0007FF7169C4E08 | 48:8D9424 8C030000    | lea rdx,qword ptr ss:[rsp + 0x38C]                                    |
0x0007FF7169C4E10 | E8 6BFE0300           | call <hi铁布衫-cm ver 0.001.memcmp>                                      |
0x0007FF7169C4E15 | 85C0                  | test eax,eax                                                          |
0x0007FF7169C4E17 | 0F85 F01C0000         | jne hi铁布衫-cm ver 0.001.7FF7169C6B0D                                   |
0x0007FF7169C4E1D | C605 32AA0B00 01      | mov byte ptr ds:[<bool Logined>],0x1                                  | TestClient.cpp:486
0x0007FF7169C4E2F | 48:8D0D 62710B00      | lea rcx,qword ptr ds:[<class HiEasyX::Window methodWindow>]           | rcx:"bad..."

ps: 值得注意的是,我看到了创建窗口的符号,这说明第一个思路是对的,可能是因为操作失误了,导致没有追根到底。

看样子我们找到关键的控制流跳转了(jcc)。简单的修改下标志位和跳转,测试下:程序爆破成功(MayBeSuccess?)。

ps:在看完源代码后,发现这个窗口标题是服务器已经发现在破解情况下触发的;同时这个窗口是暗装窗口。

搜索下创建窗口的引用,有趣的来了,有三个窗口。这说明有 1 个是假的功能窗口。

现在,我不想继续跟下去了,要是分析了半天暗装算法,就得不偿失了。
通过 Server 端,拿出来一个 测试账号 玩玩。

uN: pygxDbg
pW: oKVxXlGKtomCUCML

正版卡记录控制流与数据

0x0007FF7169C4E17 jump
successResult: "bad2..."
0x0007FF7169C6B74 noJump
0x0007FF7169C6B7A | C605 D58C0B00 01     | mov byte ptr ds:[<bool Logined>],0x1                                                                    | TestClient.cpp:532

有趣的是,程序依旧被检测到调试了。(我也不知道为啥,也许是登录超时了)

嗯,下次要快点,重来一次,记录好数据。(控制流已经不用管了)

0x0007FF7169C6E36 | 48:8BC8                 | mov rcx,rax                          | rax:&L"PVZ Plug Ver 4.7\n"
0x0007FF7169C7090 | 48:8BC8                 | mov rcx,rax                          | rax:L"46"
0x0007FF7169C72AE | 48:8BC8                 | mov rcx,rax                          | rax:L"47"
0x0007FF7169C74C7 | 48:8BC8                 | mov rcx,rax                          | rax:L"48"
0x0007FF7169C76AF | 48:8BC8                 | mov rcx,rax                          | rax:L"50"
0x0007FF7169C7907 | 48:8BC8                 | mov rcx,rax                          | rax:&L"MayBeSuccess?\n"

对比下窗口,看样子有 4 个数据是与本地算法有关的。(另外俩个分别是标题、标签的字符串,我记得是没有 数据复用 的情况的)

ps:通过看源代码,最后一个数据是通过这 4 个 整数 算出来的。如果有 1 个补错了,服务端就会检测到并警告。

刚才的是数据是窗口加载时要用到的(这又是一个不错的暗装点\检测点),还有一个 Check 会用到数据。

看样子是从 Server 上获取用户名,如果和本地用户名不匹配,估计又要扰一会头了。
0x0007FF7169C22BD | 48:8BC8                 | mov rcx,rax                                | rax:&L"chinapyg"

好了,控制流和数据都拿到了,该逆向本地验证了。(笑

网络验证与本地验证相结合

在搞定网络验证后,还需要填写一个本地算法生成出来的 Serial 方可通过。

网络传输过来的数据还是服务于本地的软件,目前我的项目是这样,所以补了几百或几千个数据,收包解密后的数据依旧是加密的。

本地验证部分参考的这个 Web Site

山寨 Hi-铁布衫

我们拿到了正版数据,最简单的 Patch 方式,直接把记录到的 地址 给 Hook 掉,把数据补进去。不过为了贴近一丢丢的实际情况(这个方法太容易被对抗了),用下面的方法。

在知道了加密与解密的算法,在程序是单线程发包的情况下,可以尝试 Hook Rc4.EnCodeRc4.DeCode 把正版数据补回去。

分析 Hook 点

这台电脑没有环境,改天让周清帮忙装下~
所以,山寨就略了。

逆向 Rc4.EncodeRc4.DeCode

照着源码跟就好了~ (奇怪,我似乎找不到 CryptoLibEx)坏了,坏了,改日再补。

// main
rc4.SetNormalKey();

__forceinline int Send(std::string _Str)
{
	UCHAR* en = new UCHAR[_Str.size()]{ 0 };
	rc4.Encode((UCHAR*)_Str.c_str(), en, _Str.size());
	return send(connection, (char*)en, _Str.size(), 0);
	delete en;
}

__forceinline std::string Recv()
{
	auto _buffer = new  char[1024]{ 0 };
	int _bufferSize = 1024;
	int size = recv(connection, _buffer, _bufferSize, 0);
	auto r = rc4.Decode((UCHAR*)_buffer, size).GetString();
	delete _buffer;
	return r;
}

一不小心,发现没搭建好环境,尴尬。不过相信身为 Reverser/Ctfer/Geeker 不会因为这点困难而停止逆向的脚步~

总结、反思与铁布衫0.2

铁布衫 0.001 还是太脆弱了。

  1. 可以尝试通过 recv 的调用频率、引用定位关键点
  2. 网络验证无正版 KEY 不可破同时难以分析
  3. 发包与收包的缓冲区应该及时清除
  4. 有用的符号太多=>想办法把程序的符号信息全部去除或修改
  5. 验证协议过于简单=>使用成体系的协议算法=>逆向或收集其他网络验证协议以加强自身
  6. 创建窗口搜索引用失败的原因(还未尝试重现)
  7. 暗装可以设计多个
  8. 可以尝试实现动态心跳包来检测调试
  9. 实现多线程与服务端通信

链接

(2023.7.24)软件加密与解密-2-1-程序分析方法[XDbg].md