GhostCHD网络封包引擎用户层过LaTale台服nProtect

这篇文章其实不能给外挂初学者带来什么帮助,因为我的目的不是调试NP保护下的游戏来找call什么的——事实上,在国服待的几年我已经把这游戏研究透了,不夸张地讲给我一年的全日制时间我能自己逆向写一份这游戏。

GhostCHD引擎只有一个功能——分析游戏的通信协议、然后自己发包。辅助软件坚决不读写游戏内存,即便是当年发布的GhostCHD辅助实际上也是一个小型的独立游戏客户端,而不依赖游戏本身。说白了就是脱机挂。脱机版本的GhostCHD我没有发布过,不过当年接代练生活技能就用的是脱机的,一天能同时接五六个单子,最后受不了的是宽带带宽的说。

这使得我的思路受NP的限制不大。众所周知NP主要防的是外部程序对游戏内存的读写。

在国服,我的思路的Hook部分需要做3个地方:发包的地方、收包的地方、以及游戏主循环中的一个地方。

由于NP据说会对游戏代码段进行校验,曾经有位大神曰过,解决代码段校验的最好办法就是不对游戏代码段进行修改,虽然我听了很恼火,但是想来想去没有更性价比的方法,所以原来的Hook需要改一改。

那么很自然地想到改为ws2_32.dll中的send与recv。这样比以前麻烦一些,主要是send一次有可能发两个以上的包,而recv接收的单位不一定是一个封包而是半个封包,需要把它看作字节流来分析。另外需要自己处理加密与解密——这个倒简单了,标准端不像国服用SDDynDll做动态加密,而是个很简单的xor加密,10年还是11年改过一次算法,不过还是很快被爱好者们贴到ragezone论坛上去了,由于早知道发包相关代码的位置,自己静态分析也非常方便。

至于游戏主循环的那个hook,接触过D3D编程的都知道D3D游戏的窗口循环调用的是PeekMessage。OK,hook到这儿。

因为不知道NP会不会对send和recv这么重要的函数做inline hook检查,所以我决定使用修改IAT的办法做Hook。IAT的具体函数指针的地址可以通过运行不加载NP的情况(如不加参数的启动,会报错)挂载OD调试来找出来。

在计算机程序战争中,永远只有一个公理:谁先来谁是爷。所以注入游戏没有什么防NP的技术——我们只需要在NP加载前把代码注入了就是了。

又有传言NP会扫描程序加载的模块,看有没有可疑的DLL。这个好办,我毕业设计就是干这个的,专门把DLL变成shell code来注入。

台服的客户端加了壳,所以不能注入后就去修改东西,要等壳加载完毕后再修改。最后Hook方面主要的代码就是这个样子:

#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

void Encrypt(unsigned char *data, unsigned int len)
{
	const char *key = "qmfaktnpgjs";
	for (unsigned int i = 0; i < len; ++i)
	{
		if (data[i] != 0)
			data[i] ^= key[i % 11];
	}
}

int PASCAL Hook_send(SOCKET s, const char *buf, int len, int flags)
{
	//some code
}

int PASCAL Hook_recv(SOCKET s, char FAR * buf, int len, int flags)
{
	//some code
}

BOOL WINAPI Hook_PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
{
	//some code
	return PeekMessageA(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
}


extern "C" __declspec(dllexport) void main()
{
	DWORD* iat_send = (DWORD*)0x98A4AC;
	DWORD* iat_recv = (DWORD*)0x98A4E4;
	DWORD* iat_PeekMessageA = (DWORD*)0x98A410;
	DWORD pfn_send = (DWORD)GetProcAddress(GetModuleHandleA("ws2_32.dll"), "send");
	DWORD pfn_recv = (DWORD)GetProcAddress(GetModuleHandleA("ws2_32.dll"), "recv");
	DWORD pfn_PeekMessageA = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "PeekMessageA");

	while (!(*iat_send == pfn_send && *iat_recv == pfn_recv && *iat_PeekMessageA == pfn_PeekMessageA))
	{
		Sleep(10);
	}
	*iat_send = (DWORD)&Hook_send;
	*iat_recv = (DWORD)&Hook_recv;
	*iat_PeekMessageA = (DWORD)&Hook_PeekMessageA;
}

 

鉴于当年经常被人偷学技术,所以删掉了与挂有关的代码。仅供技术探讨。

吐槽一下HDD Regenerator的防破解技术

前几天因为以为几次蓝屏是硬盘的问题而装上了以前用过的HDD Regenerator 2011破解版,然后这软件给我推送了更新的版本,就上网更新了下。

由于现在用的破解版是Patch形式而不是KeyGen,所以新版自然没有破解。出于好奇,就想自己破破看。

我的目标很简单,HDDREG不是有写到U盘上的DOS版本么,我只需要找到注册版的这个DOS程序就够了。

事实证明,我手贱了。。。这是我接触逆向这么多年来碰到的最厉害的软件保护。

==================华丽丽的分割线==================

用winhex看了下安装目录下个各个文件,发现DOS版本是放在DSCK.TR这个文件里的,以磁盘扇区的形式。看到文件头就是磁盘0扇区熟悉的55AA结尾,就把这个文件拖到WinImage里,结果打开是打开了,但是里面就一个文件。

由于生成的U盘中HDDREG是打包成了一个cab文件,所以在这个文件里搜索了MSCF文件头标记,找到了,但是往后翻cab的文件大小却发现翻了不久就是0字节了——这说明DSCK.TR中数据的存放是打乱顺序的!解密的关键看来在EXE文件中。

于是用ProcessMonitor监视下文件读写,然后运行一次写U盘,果真看到乱序的读取Log。然后找到其中一个开头的位置,在WinHex中打开程序的内存搜索这个数据,搜到了看似是顺序表的东西——68 四字节位置 68 四字节长度——Wait。。。这是硬编码的push指令。。。给跪。。

既然知道Key在程序内存里,那么我们Debug它一下。。。拿出OD神器。。。Bang——无法附加到指定的进程。

第一次见到这种问题,还以为是软件做了什么手脚。于是跟踪父进程(这个软件运行后会启动一个相同的子进程,然后工作都是子进程做)。调查许久才发现附加不进去的原因是——父进程是子进程的调试器。

这样看来就没办法调试了。好吧,我们考虑Dump下来静态法分析。于是打开WinHex,打开子进程的EXE映射空间,另存为。Bang——无法读取内存。。。内存中几乎所有的区域都是UNREADABLE PAGE。

同时对父进程下Log在WriteProcessMemory,会发现每当打开软件的什么功能都会出现一大堆的Log,写入的位置都是1000的整数,长度都是1000。显然这意味着程序需要执行某段代码而引发访问违规的时候才由父进程把这段代码的内容写入内存,也就是说这个程序手动地实现了类似缺页中断的机制。

既不能调试也不能dump。好样的。不过办法还是有的,就是写个shellcode注入到子进程中,访问不能访问的内存引发缺页,当父进程写入正确的数据后再dump。不过实验起来总是不明原因的崩溃,但是还好,每次运行程序可以dump一个页面出来。基于这样的思路,我用vb写了个程序,自动运行软件300多次,每次dump 0x1000个字节,自动化耗时近半小时(因为需要Sleep,不然可能还没来及写入正确的数据),终于把软件给dump了出来。

你以为这样就完了么。。。。。。。。事实证明我在dump出来的代码中并没有找到注册版写U盘的硬编码的代码。。。这说明作者编译了非注册版和注册版的两个EXE,然后在程序的起始以动态代码技术只加载其中一个。

于是我白忙活了。

后记:以后可以尝试学习一下调试器技术。。。

解决MeGUI有时出现的StackHash崩溃问题

无聊想压一份Lord of The Rings,结果写了AVS结果在MeGUI中载入会导致崩溃,故障模块显示为StackHash_XXXX这样的字样,但是Windows中并没有这样一个模块。

Google得到的信息并不多,有这一篇文章引起了我的注意:

解决WIN7出现stackhash APPCRASH问题(故障模块名称: StackHash_af76)

虽然不确定是不是如文章所说的DEP问题,但还是按照文章的做法试着添到Windows的白名单中,结果提示此程序不支持关闭DEP保护类似这样的字样,无法添加进Windows的例外。

立即联想到PE文件头中有一个IMAGE_DLL_CHARACTERISTICS_NX_COMPAT标记指示该可执行映像是否为NX兼容。用WinHex打开MeGUI.exe,找到0xDE位置,将0x8540去掉该标记改为0x8440。

再重新打开会导致崩溃的AVS,发现问题已解决,而无需再添加白名单。

感慨一下,这可真的是很冷门的故障,以及,更冷门的解决方案。。。幸亏做毕设熟悉这块。。。

打造世界最小LZMA解压DLL(第二弹)

一年前我曾写过一篇《手写PE文件,打造史上最小LZMA解压DLL》,最近因为涉及毕业设计,重新开始研究PE,同时也需要提供LZMA压缩功能。

对原来的程序观察后发现,UPX使用的LZMA代码并不是最优化的编译,原因是局部变量全部使用ESP寻址,而Intel x86对寄存器寻址有个ESP例外,需要多占用一字节的指令。于是重新翻出LZMA SDK,使用VC6编译出2409字节的代码(原来是2694字节)。

另外一个问题就是原来开头写的sub esp, 3e80是不对的。Windows分页机制规定,栈分配是逐页进行的,当当前栈位于页顶端时访问上面的页会引发栈空间分配,但跨页访问即认为访问越界。显然3e80可能会造成跨页访问导致程序崩溃。

这次最终的DLL文件是2970字节,缩小至原来的90%。

这次先贴代码,DLL文件以后再更新后发,因为看上去可以通过修改SDK使文件更精简。

隐藏行号 复制代码 这是一段程序代码。
  1. .386
  2. .model flat
  3. .code
  4. push ebp
  5. mov ebp,esp
  6. mov ecx,0fa0h
  7. push 0
  8. loopd $-2
  9. mov eax,esp
  10. push eax
  11. add eax,4
  12. push [ebp+0ch]
  13. push [ebp+8]
  14. push eax
  15. add eax,4
  16. push [ebp+14h]
  17. push [ebp+10h]
  18. push eax
  19. mov dword ptr [eax],20003h
  20. push eax
  21. db 55h,8Bh,0ECh,83h,0ECh,40h,8Bh,45h,8,53h,56h,57h,8Ah,48h,2,6Ah
  22. db 1,80h,65h,0Bh,0,5Bh,8Bh,0D3h,33h,0F6h,0D3h,0E2h,8Ah,48h,1,8Dh
  23. db 78h,4,89h,7Dh,0E8h,89h,75h,0E4h,89h,75h,0F8h,89h,5Dh,0F0h,4Ah,89h
  24. db 5Dh,0DCh,89h,55h,0C4h,8Bh,0D3h,0D3h,0E2h,8Bh,4Dh,14h,89h,5Dh,0E0h,89h
  25. db 5Dh,0D4h,4Ah,89h,55h,0C0h,0Fh,0BEh,10h,89h,31h,8Bh,4Dh,20h,89h,55h
  26. db 0C8h,89h,31h,8Ah,48h,1,3,0CAh,0B8h,0,3,0,0,0D3h,0E0h,5
  27. db 36h,7,0,0,74h,10h,8Bh,0C8h,0B8h,0,4,0,4,0D1h,0E9h,0F3h
  28. db 0ABh,13h,0C9h,66h,0F3h,0ABh,8Bh,45h,0Ch,8Bh,4Dh,10h,89h,45h,0FCh,3
  29. db 0C1h,89h,45h,0F4h,8Bh,45h,0FCh,83h,0CAh,0FFh,89h,75h,10h,33h,0C9h,3Bh
  30. db 45h,0F4h,0Fh,84h,0B0h,8,0,0,8Bh,7Dh,10h,0Fh,0B6h,18h,0C1h,0E7h
  31. db 8,0Bh,0FBh,40h,41h,89h,7Dh,10h,83h,0F9h,5,89h,45h,0FCh,7Ch,0DFh
  32. db 39h,75h,1Ch,0Fh,86h,97h,8,0,0,8Bh,7Dh,0C4h,8Bh,45h,0F8h,23h
  33. db 7Dh,0E4h,8Bh,4Dh,0E8h,0C1h,0E0h,4,3,0C7h,81h,0FAh,0,0,0,1
  34. db 8Dh,0Ch,41h,73h,21h,8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h,67h,8,0
  35. db 0,8Bh,75h,10h,0Fh,0B6h,18h,0C1h,0E6h,8,0C1h,0E2h,8,0Bh,0F3h,40h
  36. db 89h,75h,10h,89h,45h,0FCh,66h,8Bh,1,8Bh,0F2h,0Fh,0B7h,0D8h,0C1h,0EEh
  37. db 0Bh,0Fh,0AFh,0F3h,39h,75h,10h,0Fh,83h,0ADh,1,0,0,8Bh,0D6h,0BFh
  38. db 0,8,0,0,0Fh,0B7h,0F0h,2Bh,0FEh,8Bh,75h,0C0h,0C1h,0FFh,5,3
  39. db 0F8h,23h,75h,0E4h,0Fh,0B6h,45h,0Bh,66h,89h,39h,0B1h,8,2Ah,4Dh,0C8h
  40. db 6Ah,1,5Bh,0D3h,0E8h,8Bh,4Dh,0C8h,0D3h,0E6h,8Bh,4Dh,0E8h,3,0C6h,8Dh
  41. db 4,40h,0C1h,0E0h,9,83h,7Dh,0F8h,7,8Dh,84h,8,6Ch,0Eh,0,0
  42. db 89h,45h,8,0Fh,8Ch,0B5h,0,0,0,8Bh,45h,0E4h,8Bh,4Dh,18h,2Bh
  43. db 45h,0F0h,0Fh,0B6h,4,8,89h,45h,0D8h,0D1h,65h,0D8h,8Bh,45h,0D8h,8Bh
  44. db 4Dh,8,25h,0,1,0,0,89h,45h,0CCh,3,0C3h,81h,0FAh,0,0
  45. db 0,1,8Dh,8Ch,41h,0,2,0,0,73h,21h,8Bh,45h,0FCh,3Bh,45h
  46. db 0F4h,0Fh,84h,0B1h,7,0,0,8Bh,75h,10h,0Fh,0B6h,38h,0C1h,0E6h,8
  47. db 0C1h,0E2h,8,0Bh,0F7h,40h,89h,75h,10h,89h,45h,0FCh,66h,8Bh,1,8Bh
  48. db 0F2h,0Fh,0B7h,0F8h,0C1h,0EEh,0Bh,0Fh,0AFh,0F7h,39h,75h,10h,73h,1Bh,8Bh
  49. db 0D6h,0BEh,0,8,0,0,2Bh,0F7h,0C1h,0FEh,5,3,0F0h,0D1h,0E3h,83h
  50. db 7Dh,0CCh,0,66h,89h,31h,75h,2Eh,0EBh,1Bh,29h,75h,10h,2Bh,0D6h,66h
  51. db 8Bh,0F0h,8Dh,5Ch,1Bh,1,66h,0C1h,0EEh,5,2Bh,0C6h,83h,7Dh,0CCh,0
  52. db 66h,89h,1,74h,11h,81h,0FBh,0,1,0,0,0Fh,8Dh,82h,0,0
  53. db 0,0E9h,63h,0FFh,0FFh,0FFh,81h,0FBh,0,1,0,0,7Dh,75h,8Bh,45h
  54. db 8,8Dh,3Ch,1Bh,89h,7Dh,0CCh,3,0F8h,81h,0FAh,0,0,0,1,73h
  55. db 21h,8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h,1Bh,7,0,0,8Bh,4Dh,10h
  56. db 0Fh,0B6h,30h,0C1h,0E1h,8,0C1h,0E2h,8,0Bh,0CEh,40h,89h,4Dh,10h,89h
  57. db 45h,0FCh,66h,8Bh,7,8Bh,0CAh,0Fh,0B7h,0F0h,0C1h,0E9h,0Bh,0Fh,0AFh,0CEh
  58. db 39h,4Dh,10h,73h,15h,8Bh,0D1h,0B9h,0,8,0,0,2Bh,0CEh,0C1h,0F9h
  59. db 5,3,0C8h,66h,89h,0Fh,0D1h,0E3h,0EBh,9Ch,29h,4Dh,10h,2Bh,0D1h,66h
  60. db 8Bh,0C8h,66h,0C1h,0E9h,5,2Bh,0C1h,66h,89h,7,8Bh,45h,0CCh,8Dh,58h
  61. db 1,0EBh,83h,8Bh,45h,18h,8Bh,75h,0E4h,88h,5Dh,0Bh,88h,1Ch,6,46h
  62. db 83h,7Dh,0F8h,4,89h,75h,0E4h,7Dh,9,83h,65h,0F8h,0,0E9h,8Dh,6
  63. db 0,0,83h,7Dh,0F8h,0Ah,7Dh,9,83h,6Dh,0F8h,3,0E9h,7Eh,6,0
  64. db 0,83h,6Dh,0F8h,6,0E9h,75h,6,0,0,29h,75h,10h,2Bh,0D6h,66h
  65. db 8Bh,0F0h,66h,0C1h,0EEh,5,2Bh,0C6h,81h,0FAh,0,0,0,1,66h,89h
  66. db 1,8Bh,45h,0E8h,8Bh,4Dh,0F8h,8Dh,8Ch,48h,80h,1,0,0,73h,21h
  67. db 8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h,5Ch,6,0,0,8Bh,75h,10h,0Fh
  68. db 0B6h,18h,0C1h,0E6h,8,0C1h,0E2h,8,0Bh,0F3h,40h,89h,75h,10h,89h,45h
  69. db 0FCh,66h,8Bh,1,8Bh,0F2h,0Fh,0B7h,0D8h,0C1h,0EEh,0Bh,0Fh,0AFh,0F3h,39h
  70. db 75h,10h,73h,42h,8Bh,0D6h,0BEh,0,8,0,0,2Bh,0F3h,0C1h,0FEh,5
  71. db 3,0F0h,8Bh,45h,0E0h,89h,45h,0D4h,8Bh,45h,0DCh,89h,45h,0E0h,8Bh,45h
  72. db 0F0h,89h,45h,0DCh,33h,0C0h,83h,7Dh,0F8h,7,66h,89h,31h,0Fh,9Dh,0C0h
  73. db 48h,24h,0FDh,83h,0C0h,3,89h,45h,0F8h,8Bh,45h,0E8h,5,64h,6,0
  74. db 0,0E9h,48h,2,0,0,29h,75h,10h,2Bh,0D6h,66h,8Bh,0F0h,66h,0C1h
  75. db 0EEh,5,2Bh,0C6h,81h,0FAh,0,0,0,1,66h,89h,1,8Bh,45h,0F8h
  76. db 8Bh,4Dh,0E8h,8Dh,0B4h,41h,98h,1,0,0,89h,75h,8,73h,21h,8Bh
  77. db 45h,0FCh,3Bh,45h,0F4h,0Fh,84h,0BDh,5,0,0,8Bh,4Dh,10h,0Fh,0B6h
  78. db 18h,0C1h,0E1h,8,0C1h,0E2h,8,0Bh,0CBh,40h,89h,4Dh,10h,89h,45h,0FCh
  79. db 66h,8Bh,6,8Bh,0CAh,0Fh,0B7h,0D8h,0C1h,0E9h,0Bh,0Fh,0AFh,0CBh,39h,4Dh
  80. db 10h,0Fh,83h,0C1h,0,0,0,0BEh,0,8,0,0,8Bh,0D1h,2Bh,0F3h
  81. db 0C1h,0FEh,5,3,0F0h,8Bh,45h,8,66h,89h,30h,8Bh,45h,0F8h,8Bh,75h
  82. db 0E8h,83h,0C0h,0Fh,0C1h,0E0h,4,3,0C7h,81h,0F9h,0,0,0,1,8Dh
  83. db 4,46h,73h,23h,8Bh,75h,0FCh,3Bh,75h,0F4h,0Fh,84h,58h,5,0,0
  84. db 0Fh,0B6h,1Eh,0C1h,0E1h,8,8Bh,0D1h,8Bh,4Dh,10h,0C1h,0E1h,8,0Bh,0CBh
  85. db 46h,89h,4Dh,10h,89h,75h,0FCh,66h,8Bh,8,8Bh,0F2h,0Fh,0B7h,0D9h,0C1h
  86. db 0EEh,0Bh,0Fh,0AFh,0F3h,39h,75h,10h,73h,48h,8Bh,0D6h,0BEh,0,8,0
  87. db 0,2Bh,0F3h,0C1h,0FEh,5,3,0F1h,66h,89h,30h,8Bh,75h,0E4h,85h,0F6h
  88. db 0Fh,84h,12h,5,0,0,33h,0C0h,83h,7Dh,0F8h,7,8Bh,0CEh,0Fh,9Dh
  89. db 0C0h,2Bh,4Dh,0F0h,48h,24h,0FEh,83h,0C0h,0Bh,89h,45h,0F8h,8Bh,45h,18h
  90. db 8Ah,0Ch,1,88h,0Ch,6,46h,88h,4Dh,0Bh,89h,75h,0E4h,0E9h,0CDh,4
  91. db 0,0,29h,75h,10h,2Bh,0D6h,66h,8Bh,0F1h,66h,0C1h,0EEh,5,2Bh,0CEh
  92. db 66h,89h,8,0E9h,0Ch,1,0,0,29h,4Dh,10h,2Bh,0D1h,66h,8Bh,0C8h
  93. db 66h,0C1h,0E9h,5,2Bh,0C1h,8Bh,4Dh,0F8h,66h,89h,6,8Bh,45h,0E8h,81h
  94. db 0FAh,0,0,0,1,8Dh,8Ch,48h,0B0h,1,0,0,73h,21h,8Bh,45h
  95. db 0FCh,3Bh,45h,0F4h,0Fh,84h,9Eh,4,0,0,8Bh,75h,10h,0Fh,0B6h,18h
  96. db 0C1h,0E6h,8,0C1h,0E2h,8,0Bh,0F3h,40h,89h,75h,10h,89h,45h,0FCh,66h
  97. db 8Bh,1,8Bh,0F2h,0Fh,0B7h,0D8h,0C1h,0EEh,0Bh,0Fh,0AFh,0F3h,39h,75h,10h
  98. db 73h,19h,8Bh,0D6h,0BEh,0,8,0,0,2Bh,0F3h,0C1h,0FEh,5,3,0F0h
  99. db 8Bh,45h,0DCh,66h,89h,31h,0E9h,90h,0,0,0,29h,75h,10h,2Bh,0D6h
  100. db 66h,8Bh,0F0h,66h,0C1h,0EEh,5,2Bh,0C6h,81h,0FAh,0,0,0,1,66h
  101. db 89h,1,8Bh,45h,0F8h,8Bh,4Dh,0E8h,8Dh,8Ch,41h,0C8h,1,0,0,73h
  102. db 21h,8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h,2Bh,4,0,0,8Bh,75h,10h
  103. db 0Fh,0B6h,18h,0C1h,0E6h,8,0C1h,0E2h,8,0Bh,0F3h,40h,89h,75h,10h,89h
  104. db 45h,0FCh,66h,8Bh,1,8Bh,0F2h,0Fh,0B7h,0D8h,0C1h,0EEh,0Bh,0Fh,0AFh,0F3h
  105. db 39h,75h,10h,73h,16h,8Bh,0D6h,0BEh,0,8,0,0,2Bh,0F3h,0C1h,0FEh
  106. db 5,3,0F0h,8Bh,45h,0E0h,66h,89h,31h,0EBh,1Ah,29h,75h,10h,2Bh,0D6h
  107. db 66h,8Bh,0F0h,66h,0C1h,0EEh,5,2Bh,0C6h,66h,89h,1,8Bh,4Dh,0E0h,8Bh
  108. db 45h,0D4h,89h,4Dh,0D4h,8Bh,4Dh,0DCh,89h,4Dh,0E0h,8Bh,4Dh,0F0h,89h,45h
  109. db 0F0h,89h,4Dh,0DCh,33h,0C0h,83h,7Dh,0F8h,7,0Fh,9Dh,0C0h,48h,24h,0FDh
  110. db 83h,0C0h,0Bh,89h,45h,0F8h,8Bh,45h,0E8h,5,68h,0Ah,0,0,81h,0FAh
  111. db 0,0,0,1,73h,21h,8Bh,4Dh,0FCh,3Bh,4Dh,0F4h,0Fh,84h,96h,3
  112. db 0,0,8Bh,75h,10h,0Fh,0B6h,19h,0C1h,0E6h,8,0C1h,0E2h,8,0Bh,0F3h
  113. db 41h,89h,75h,10h,89h,4Dh,0FCh,66h,8Bh,8,8Bh,0F2h,0Fh,0B7h,0D9h,0C1h
  114. db 0EEh,0Bh,0Fh,0AFh,0F3h,39h,75h,10h,73h,28h,8Bh,0D6h,0BEh,0,8,0
  115. db 0,2Bh,0F3h,0C1h,0FEh,5,3,0F1h,0C1h,0E7h,4,83h,65h,0D8h,0,66h
  116. db 89h,30h,8Dh,44h,7,4,0C7h,45h,8,3,0,0,0,0E9h,98h,0
  117. db 0,0,29h,75h,10h,2Bh,0D6h,66h,8Bh,0F1h,66h,0C1h,0EEh,5,2Bh,0CEh
  118. db 81h,0FAh,0,0,0,1,66h,89h,8,73h,21h,8Bh,4Dh,0FCh,3Bh,4Dh
  119. db 0F4h,0Fh,84h,21h,3,0,0,8Bh,75h,10h,0Fh,0B6h,19h,0C1h,0E6h,8
  120. db 0C1h,0E2h,8,0Bh,0F3h,41h,89h,75h,10h,89h,4Dh,0FCh,66h,8Bh,48h,2
  121. db 8Bh,0F2h,0Fh,0B7h,0D9h,0C1h,0EEh,0Bh,0Fh,0AFh,0F3h,39h,75h,10h,73h,25h
  122. db 8Bh,0D6h,0BEh,0,8,0,0,2Bh,0F3h,0C7h,45h,0D8h,8,0,0,0
  123. db 0C1h,0FEh,5,3,0F1h,0C1h,0E7h,4,66h,89h,70h,2,8Dh,84h,7,4
  124. db 1,0,0,0EBh,81h,29h,75h,10h,2Bh,0D6h,66h,8Bh,0F1h,0C7h,45h,0D8h
  125. db 10h,0,0,0,66h,0C1h,0EEh,5,2Bh,0CEh,0C7h,45h,8,8,0,0
  126. db 0,66h,89h,48h,2,5,4,2,0,0,8Bh,4Dh,8,0C7h,45h,0ECh
  127. db 1,0,0,0,89h,4Dh,0CCh,8Bh,4Dh,0ECh,3,0C9h,89h,4Dh,0D0h,3
  128. db 0C8h,81h,0FAh,0,0,0,1,73h,21h,8Bh,75h,0FCh,3Bh,75h,0F4h,0Fh
  129. db 84h,83h,2,0,0,8Bh,7Dh,10h,0Fh,0B6h,1Eh,0C1h,0E7h,8,0C1h,0E2h
  130. db 8,0Bh,0FBh,46h,89h,7Dh,10h,89h,75h,0FCh,66h,8Bh,19h,8Bh,0FAh,0Fh
  131. db 0B7h,0F3h,0C1h,0EFh,0Bh,0Fh,0AFh,0FEh,39h,7Dh,10h,73h,16h,8Bh,0D7h,0BFh
  132. db 0,8,0,0,2Bh,0FEh,0C1h,0FFh,5,3,0FBh,0D1h,65h,0ECh,66h,89h
  133. db 39h,0EBh,18h,29h,7Dh,10h,66h,8Bh,0F3h,66h,0C1h,0EEh,5,2Bh,0DEh,2Bh
  134. db 0D7h,66h,89h,19h,8Bh,4Dh,0D0h,41h,89h,4Dh,0ECh,0FFh,4Dh,0CCh,75h,87h
  135. db 8Bh,4Dh,8,6Ah,1,8Bh,45h,0D8h,5Bh,8Bh,0F3h,0D3h,0E6h,2Bh,0C6h,1
  136. db 45h,0ECh,83h,7Dh,0F8h,4,0Fh,8Dh,0C4h,1,0,0,83h,45h,0F8h,7
  137. db 83h,7Dh,0ECh,4,7Dh,5,8Bh,45h,0ECh,0EBh,3,6Ah,3,58h,8Bh,4Dh
  138. db 0E8h,0C7h,45h,0CCh,6,0,0,0,0C1h,0E0h,7,8Dh,84h,8,60h,3
  139. db 0,0,89h,45h,8,8Bh,45h,8,8Dh,3Ch,1Bh,89h,7Dh,0D0h,3,0F8h
  140. db 81h,0FAh,0,0,0,1,73h,21h,8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h
  141. db 0C4h,1,0,0,8Bh,4Dh,10h,0Fh,0B6h,30h,0C1h,0E1h,8,0C1h,0E2h,8
  142. db 0Bh,0CEh,40h,89h,4Dh,10h,89h,45h,0FCh,66h,8Bh,7,8Bh,0CAh,0Fh,0B7h
  143. db 0F0h,0C1h,0E9h,0Bh,0Fh,0AFh,0CEh,39h,4Dh,10h,73h,15h,8Bh,0D1h,0B9h,0
  144. db 8,0,0,2Bh,0CEh,0C1h,0F9h,5,3,0C8h,66h,89h,0Fh,0D1h,0E3h,0EBh
  145. db 17h,29h,4Dh,10h,2Bh,0D1h,66h,8Bh,0C8h,66h,0C1h,0E9h,5,2Bh,0C1h,66h
  146. db 89h,7,8Bh,45h,0D0h,8Dh,58h,1,0FFh,4Dh,0CCh,75h,88h,6Ah,4,83h
  147. db 0EBh,40h,5Fh,3Bh,0DFh,0Fh,8Ch,0Dh,1,0,0,6Ah,1,8Bh,0CBh,8Bh
  148. db 0F3h,58h,0D1h,0F9h,23h,0F0h,49h,83h,0CEh,2,83h,0FBh,0Eh,89h,4Dh,0CCh
  149. db 7Dh,16h,0D3h,0E6h,8Bh,4Dh,0E8h,89h,75h,0F0h,2Bh,0F3h,8Dh,8Ch,71h,5Eh
  150. db 5,0,0,89h,4Dh,8,0EBh,53h,2Bh,0CFh,81h,0FAh,0,0,0,1
  151. db 73h,23h,8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h,1Ah,1,0,0,8Bh,5Dh
  152. db 10h,6Ah,1,0Fh,0B6h,0,0C1h,0E3h,8,0Bh,0D8h,58h,0C1h,0E2h,8,0FFh
  153. db 45h,0FCh,89h,5Dh,10h,0D1h,0EAh,0D1h,0E6h,39h,55h,10h,72h,5,29h,55h
  154. db 10h,0Bh,0F0h,49h,75h,0C4h,8Bh,4Dh,0E8h,89h,7Dh,0CCh,81h,0C1h,44h,6
  155. db 0,0,0C1h,0E6h,4,89h,4Dh,8,89h,75h,0F0h,89h,45h,0D0h,89h,45h
  156. db 0D8h,8Bh,45h,0D8h,81h,0FAh,0,0,0,1,8Dh,0Ch,0,8Bh,45h,8
  157. db 8Dh,1Ch,1,73h,21h,8Bh,45h,0FCh,3Bh,45h,0F4h,0Fh,84h,0B7h,0,0
  158. db 0,8Bh,75h,10h,0Fh,0B6h,38h,0C1h,0E6h,8,0C1h,0E2h,8,0Bh,0F7h,40h
  159. db 89h,75h,10h,89h,45h,0FCh,66h,8Bh,3,8Bh,0FAh,0Fh,0B7h,0F0h,0C1h,0EFh
  160. db 0Bh,0Fh,0AFh,0FEh,39h,7Dh,10h,73h,16h,0B9h,0,8,0,0,8Bh,0D7h
  161. db 2Bh,0CEh,0C1h,0F9h,5,3,0C8h,0D1h,65h,0D8h,66h,89h,0Bh,0EBh,1Bh,29h
  162. db 7Dh,10h,66h,8Bh,0F0h,66h,0C1h,0EEh,5,2Bh,0C6h,2Bh,0D7h,66h,89h,3
  163. db 8Bh,45h,0D0h,41h,9,45h,0F0h,89h,4Dh,0D8h,0D1h,65h,0D0h,0FFh,4Dh,0CCh
  164. db 0Fh,85h,7Bh,0FFh,0FFh,0FFh,0EBh,3,89h,5Dh,0F0h,0FFh,45h,0F0h,74h,38h
  165. db 8Bh,75h,0E4h,83h,45h,0ECh,2,39h,75h,0F0h,77h,3Ch,8Bh,4Dh,18h,8Bh
  166. db 0C6h,2Bh,45h,0F0h,0FFh,4Dh,0ECh,8Ah,4,8,88h,4,0Eh,46h,83h,7Dh
  167. db 0ECh,0,88h,45h,0Bh,89h,75h,0E4h,74h,5,3Bh,75h,1Ch,72h,0E0h,3Bh
  168. db 75h,1Ch,0Fh,82h,81h,0F7h,0FFh,0FFh,81h,0FAh,0,0,0,1,73h,10h
  169. db 8Bh,45h,0FCh,3Bh,45h,0F4h,75h,5,6Ah,1,58h,0EBh,18h,0FFh,45h,0FCh
  170. db 8Bh,45h,0FCh,8Bh,4Dh,14h,2Bh,45h,0Ch,89h,1,8Bh,45h,20h,8Bh,4Dh
  171. db 0E4h,89h,8,33h,0C0h,5Fh,5Eh,5Bh,0C9h
  172. add esp,20h
  173. leave
  174. retn 10h
  175. end

【原创】手写PE文件,打造史上最小LZMA解压DLL

因程序需求,需要在VB中调用LZMA解压数据,经过N天研究出此成果~

什么是LZMA:LZMA应该是目前世界上数一数二的压缩算法——压缩时相同的时间得到压缩比最高,解压时速度极快且几乎不占内存。如果你对LZMA算法并无耳闻,那么7z总听说过吧。。没错,LZMA即7z作者发明的,7z使用的算法。什么?7z也没听说过?这样吧,你在网上下的软件,比如旺旺、暴风影音、人人桌面,它们的安装程序都是清一色的NSIS,打包的压缩算法是LZMA。。

用到的工具:

文件、内存编辑器:winhex
汇编器、调试器:ollydbg
upx shell

参考的资料:

看雪论坛《加密与解密》
LZMA SDK
NSIS Source
UPX Source

为什么要使用upx shell呢?在这里偷偷告诉大家,upx shell是支持用lzma算法压缩exe、dll文件的,所以呢,其生成的加壳文件中必定有最最精简最最完美的lzma解压代码,我们有足够的理由相信,不管你用哪个版本的lzma sdk来编译,出来的C语言版本都不会有upx做好的asm版本要好~

废话不再多说,下面来一步步操作:

1、自然是随便找个EXE文件用UPX加壳,注意UPX核心要选择2.92b(只有这个版本是支持LZMA的),高级选项中勾选LZMA。

2、将加壳的EXE用OD打开。从入口处往下看,可以看到有句mov [ebx],30002,然后后面是几个nop对齐,然后是熟悉的push ebp,这即是upx lzma核心解密函数的入口。把这个地址记为0,数到0x0A85,可以看到有对应的pop ebp,这2694字节就是upx lzma解密函数(没有写ret),是由4.43版的LZMA SDK修改得来的,函数声明即4.43版LZMA SDK中的LzmaDecode:

int LzmaDecode(CLzmaDecoderState *vs, const unsigned char *inStream, SizeT inSize, SizeT *inSizeProcessed, unsigned char *outStream, SizeT outSize, SizeT *outSizeProcessed);

与原版SDK不同的是,作为壳的loader,是不太允许去malloc的,所以这个函数被修改过,CLzmaDecoderState不再是原来的那个,而是包含了解压字典,这个结构的大小从加壳EXE中可以看出来有句lea ebx,[esp-3e80],所以我们如果要在C语言中内嵌这段代码,调用的时候也要开一个16K的空间才行。我这里说的都是结果,如果你对我怎么知道这些的感兴趣,可以下载upx的源代码看一下(注意是upx而不是upx shell哦)。

3、我所要编写的一个函数是这样的:

int LzmaDecode(byte *dest, long destLen, const byte *src, long srcLen);

于是我在OD中找到一段空余的空间,模仿upx调用解密函数前面一段的代码,自己编写以下代码:

push ebp ;堆栈框架
mov ebp,esp
sub esp,3e80 ;栈内开16K空间
push edi ;保存edi(Windows下C编译器与API规范规定被调用函数不得修改ebx ebp esi edi四个寄存器)
mov ecx,0fa0 ;准备给16K空间填0
xor eax,eax
lea edi,[esp+4]
rep stos
lea eax,[esp+4]
push eax ;outSizeProcessed
add eax,4
push [ebp+c] ;outSize
push [ebp+8] ;outStream
push eax ;inSizeProcessed
add eax,4
push [ebp+14] ;inSize
push [ebp+10] ;inStream
push eax ;lzmaDecodeState
mov dword [eax],20003 ;硬编码LZMA基本属性,lc=3 lp=0 pb=2
push eax ;dummy call,这里本来是写Call XXXX的,但是函数直接接下去了,要留出一格返回地址的空间
jmp XXXXXXXX ;这里插入LzmaDecode本身的代码
add esp,20 ;相当于ret,7个参数加上一个dummy的返回地址是0x20字节
pop edi ;恢复edi
leave ;出堆栈框架,返回
retn 10

4、用winhex打开调试程序的内存,复制以上汇编好的和2694字节的解压机器码按粘贴到新文件中。

5、到了这一步,我们就已经可以用masm或者vc写一大堆dd把刚才弄好的数据直接汇编成dll了。不过我们再来一点新的挑战——手写DLL。因为编译器总会给生成的文件弄一大堆不需要的东西,比如重定位段(这个程序是不需要重定位的),而手写的话就可以把文件做到最精简。

注意:下面的内容是最让人抓狂的PE文件结构。。。

6、首先,弄清楚我们需要什么。刚才写好的那段代码总长度是2757字节,加上简单的一句DllMain(mov eax,1 ret 0c),就是2765字节,也就是0xACD,所以我们的代码段长度要对齐到0xC00。

然后我们需要一个导出表,导出表中只有一个函数。为了节省空间,我把导出表放到了dos stub的地方。

最后文件的布局就是:

0x0000:DOS文件头(RVA:0x0000)
0x0040:导出表(原DOS代码)
0x00B0:PE文件头
0x01A8:区段表
0x0200:代码段- LzmaDecode(RVA:0x1000)
0x0CC5:DllMain
0x0CD0:结束(最后有4字节的0,为了美观还是对齐到十位吧~)

文件共3280字节。

7、需要注意的有一个地方,就是区段表中VirtualSize和SizeOfRawData的填写。很长的一段时间我的DLL无法正常加载(LoadLibrary失败,LastError是BAD_EXE_FORMAT什么的),折腾好久也不好,最后LordPE修复一下好了,把修复的和原来的比对了一下,发现SizeOfRawData尽管资料上说是要填对齐值,但实际上看来这个值是不得超过文件结尾的,也就是说我开始填了0x0C00,但加载的时候NtLoader看来是遇到了EOF,造成加载失败。

最终文件可以在这里下载

LzmaDecode

VB实现COM接口针对返回S_FALSE时的COM HOOK

这几天在写HtmlUI的VB6版本,Browser端原来折腾过,并且代码都在,还算好说,Server端用Asynchronous Pluggable Protocols,遍地求代码求不到,最后只得自己研究E文的MSDN,主要是要实现IInternetProtocol接口。

用VB写过COM的同学应该都知道,实现接口时VB是没法直接返回HRESULT的,通常的做法是什么都不做就代表S_OK,用Err.Raise E_XXXX返回错误。

在实现IInternetProtocol时,和往常一样,开始是一大堆的S_OK和一大堆的E_NOTIMPL,执行时发现IInternetProtocol::Read函数一直会被调用,查下MSDN得知如果数据全部发送完毕则返回S_FALSE。于是在函数结尾写上Err.Raise S_FALSE。然后奇怪的问题就来了,从断点命中来看,Start、Read、Terminate都是按顺序执行的,但页面却无法显示。折腾半天未果,遂找了份前人写过的代码比对(http://www.vbgood.com/thread-91065-1-1.html,估计这份代码是全世界唯一的APPVB版公开代码)。

这份代码用的是手工的纯C语言式接口实现,执行正常。断点命中表示Read函数被调用了多次,而我的代码只被调用了一次。联想到昨天实现IClassFactory时Err.Raise pUnknown.QueryInterface(…)的写法莫名其妙地罢工而改成hr = pUnknown.QueryInterface(…)  If hr <> 0 Then Err.Raise hr就好了,推测Err.Raise不能用来返回正确的错误码。打开OD,通过内存断点断到Read函数里,在urlmon.dll的返回地址处断下,果然,Err.Raise S_FALSE的返回值并非1,而是0x800A0001。

Google了国内外一圈,没有找到有关S_FALSE的快速解决办法。考虑到既能解决这个问题又能保全VB在COM应用上的方便性,决定用Com Hook解决。

Com Hook,和API Hook类似(导入导出表修改方式,非inline hook),只不过这次Hook的是COM的虚函数表,将原有虚函数表中的函数地址替换成我们的地址,改变程序流程,做点手脚。具体做法请复习32位汇编与com原理,下面贴代码~

Private Sub DoHook_IInternetProtocol_Read()
    Dim pInternetProtocol As olelib.IInternetProtocol
    Dim pVTable As Long, pFuncInternetProtocol_Read As Long, dwCode As Long
    Set pInternetProtocol = Me
    CopyMemory pVTable, ByVal ObjPtr(pInternetProtocol), 4
    CopyMemory pFuncInternetProtocol_Read, ByVal pVTable + 4 * 9, 4
    CopyMemory dwCode, ByVal pFuncInternetProtocol_Read, 4
    If dwCode <> &H102474FF Then
        Dim pAlloc As Long, jmpOffset As Long, bCode() As Byte
        pAlloc = VirtualAlloc(ByVal 0, &H1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
        bCode = Hex2Byte("FF742410FF742410FF742410FF742410E8000000003D01000A807505B801000000C210")
        CopyMemory ByVal pAlloc, bCode(0), UBound(bCode) + 1
        jmpOffset = pFuncInternetProtocol_Read - (pAlloc + 21)
        CopyMemory ByVal pAlloc + 17, jmpOffset, 4
 
        VirtualProtect ByVal pVTable, &H1000, PAGE_EXECUTE_READWRITE, dwCode
        CopyMemory ByVal pVTable + 4 * 9, pAlloc, 4
    End If
End Sub

以上代码在Class_Initialize中调用。

因为仅仅是一个函数遇到这样的问题,个例,所以hook也就只针对一个函数,目前尚未考虑写一个通用的处理函数。先这样放着吧~

放弃破解Rayman2的海盗头。。

整整三天的时间,最后闹个束手无策。。为三天假期表示沉痛哀悼。。

本来破解补丁已经写好了,通过Hook贴图的代码过滤掉海盗头和相关文字,正高兴着呢,结果上去玩两分钟就哭了。。Ubi太坏了,这招绝不是简单的贴个影响游戏的图就完了,游戏元素也会受到影响,本该潜水的鱼缸里没了水,悬崖峭壁的尽头少了紫色抓环。。

至此算是束手无策了,因为整个过程,从判定到检验到耍花招,所有的代码都是游戏地图编译脚本实现的,也就是说我们面对的是一堆无法理解的字节码,和一个不知道原理的脚本虚拟机。众所周知分析一个虚拟机程序意味着什么(VMP,给你做广告呢,别忘了给广告费~~)。更具体地说,Rayman2中脚本系统几乎占了全部的比重,也就是说x86代码只负责提供一个底层图形接口,任何有关游戏的东西全部由脚本实现,Ubi把文件改一改,还用Rayman2.exe就是另外一个游戏了(《麻烦大了》?Maybe?)

不过也并非一无所获,至少懂得了开发一个游戏,脚本系统的重量。相比之下彩虹岛果真垃圾的一塌糊涂了,貌似比wow占内存还多?

EDIT: (2010-8-5)
昨天又想到一个点子,是找到比对校验码的地方,强行相等,经过一下午搞,找到了突破口,但是很奇怪还是会出现CD-Missing,有点怀疑是程序校验了自己的代码段,最诡异的是如果用硬盘版在Whale关出CD-Missing的瞬间会崩溃,所以不能简单的屏蔽显示贴图能搞定的,另外就是16关那个反弹子弹打开通道的地方怎么也过不去,怀疑是游戏元素受到了影响。Win7下总是会出现各式各样的问题,XP下虚拟光驱就工作的很好。这下看来是真的没救了。