发布时间:2023年8月28日
内容概要:Hi铁布衫-CM Ver 0.001 WriteUp
软件加密与解密-1-软件保护技术[XDbg]-1.png)
有一段时间没写过代码了,源码早就忘光了,今天碰巧有时间,好好测试一下我的网络验证的强度。
PE 信息如下:
vc 8.0 above 是个很新的程序~
内存布局如下:
嗯,很帅。
字符串如下:
有一堆的 PYG 水印。(除了水印已无太多信息)
readMe:
hi-Easyx
编写的。登录窗口
=> 功能窗口
组成。网络验证 CrackME
。所以我们可以尝试通过 CreateWindow系列函数
和 hi-Easyx相关字符串
进行定位 hi-Easyx
创建窗口的 Call 或 Sig(特征码)。
所以我们可以尝试通过 send
、recv
等网络函数进行回溯=>分析验证的加密算法、解密算法等对于网络验证来说很重要的东西。
可以看到有个 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>] |
定位到 获取登录返回值
的地址了,对 recv
的 buffer
下硬件访问断点。
看样子程序是调用了 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
我们拿到了正版数据,最简单的 Patch 方式,直接把记录到的 地址
给 Hook 掉,把数据补进去。不过为了贴近一丢丢的实际情况(这个方法太容易被对抗了),用下面的方法。
在知道了加密与解密的算法,在程序是单线程发包的情况下,可以尝试 Hook Rc4.EnCode
与 Rc4.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.001 还是太脆弱了。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章