贴个CHDEMU最新的开发图

昨天达成了一个checkpoint,即物品系统基本操作逻辑的完成。包括物品的获取(目前是通过GM作弊命令)、销毁、分发、装备等。

其中物品装备的逻辑还是有点难度的,因为装备栏有的物品是占用两格(双手武器)或三格(时装衣服),装备物品时要判断冲突,想当然写了几遍都有逻辑漏洞,最后不得不在开发笔记上列举所有的冲突情况才总结出与原版的服务端一致的逻辑(算法)。

目前所有的代码都是重构过的了,采用的是与官方相同的编码规范(不要问我从哪儿得到的官方编码规范),即变量全部为匈牙利命名法(驼峰法,有前导数据类型字符),函数全部为首字母大写。如m_fPosX、GetPosX()、m_sObjects等(这也是Windows下做开发的微软系的惯例规范,Java系终究还是不习惯)。上个版本的编码规范是C式的写法,所有变量不带m_也不带类型字符,结果越写越混乱。

架构上基本是自己重新画的,有些东西借鉴了ArcEmu。ArcEmu是我比较喜欢的一个项目,几年前就有读过,最近重读它的代码觉得质量还是蛮不错的,尽管我从来没玩过Wow。我的项目起名叫ChdEmu也是学ArcEmu的。其他的项目像MaNGOS系也有阅览过,但觉得不合口味,比如ACE库显得有点笨。Ryzom也有读,毕竟是真正在营的游戏,但觉得太重量级,子系统划分过细,本来很简单的一个操作他能给你在10个子系统里转来转去执行了1000行代码。

我是个完美主义和精简主义的人,这两个主义结合起来难度可不小。我的目标是用最少的代码、最少的CPU和内存完成最好的工作,并且这些代码的架构还要读起来漂亮、高端大气上档次。这堪比开一场真正意义上的音乐会。

呃好像跑题了。回到项目。因为物品系统能用了,所以现在可以当个纸娃娃玩,给自己弄点好看的衣服穿。下一个checkpoint是技能系统。发布时间预计在明年1-2月份。

不过大概要等到放假再写了。这周是第八周,依据本人多年开音乐会死在台上的经验,接下来要进入学霸模式,差不多持续6周,等曲子都背出来才能稍微缓一口气。

叨叨完毕,下面上图(根据惯例传到百度相册,看大图请亲自前往)。

啥?你说下图穿的是裙子?呃被发现了,可能是状态系统一直没怎么弄完善,初始化的时候Gender随便填的,我开始也没怎么注意后来弄了个裤子结果发现是灰色的没法装备。。。

CHDEMU LaTale Emulator V1.00 Readme For Foregin Users

Part I: Definations and Statements

CHDEMU is a server emulator of the MMORPG LaTale, which is developed by Actoz Inc. from Korea. CHDEMU IS NOT A PRIVATE SERVER. It should be only used for study and for fun.

The project is developed by gmsj0001, the author of this blogger. The project is closed source currently.

Part II: Client Compatibility

This version of CHDEMU is only compatible with the Chinese version 1.42, which is released at December, 2008, with the LaTaleClient version 1.262.4XX. The client package download link will be provided at the end of this article.

Part III: Features

Of the connection server, the emulator provides you to login, create or delete character.

Of the world server, the emulator currently implemented the Map system, Coord system. Some of the NPCs, Chat are alos implemented. The emulator support multiple players to logon to the game.

In a word, the emulator allows you to tour around the game world. Please notice that this project is the first server emulator of LaTale in the world that can perform a map tour.

Part IV: Instructions for use

For local test (single player), you can simply run the server chdemu.exe by a double click on the executable file. The client shoud be launched from the command line:
LaTaleClient.exe /Run:VHIGH /Window /LoginServer:127.0.0.1:10000

For multiple players on lan or wan, you need to run the server by specifying the listening IP address using the command line like this:
chdemu.exe -ip 192.168.1.237
And the client command line should be:
LaTaleClient.exe /Run:VHIGH /Window /LoginServer:192.168.1.237:10000

The first login will create the user. Please input the correct username and password. This password will be used when you login again. Then you can create your character and logon to game.

One cheat command have been used in the game. You can input the following syntax in the chat window to portal to anywhere.
//portal StageID MapGroupID [PosX] [PosY]
For example:
//portal 6 0          Elias
//portal 15 0        Blackmoon city
//portal 67 0        Atlantis

Part V: International Issues

LaTale uses the MultiByte Character Set (MBCS) in developing the game, so the client will show unrecognizable texts on non-chinese codepage machines. If you’re concerning about this, you can change the system locale settings or use the AppLocale utilities.

Part VI: Futher development

The next development will support the lastest client versions. It will be released at January, 2014.

Download links:

Server: http://dl2.gmsj.org/latale/chdemu_1_00.7z
Client: http://x001.gmsj.org/download/latale/LaTale_1_42_CHINA.7z

X001 server got hacked on Nov 4th and the hacker erased all my data on the disk. So the download link was broken. You can wait for the version 2.0 got released.

 

 

再次对付WordPress文章ID

我严重怀疑这个ID是WordPress开发者故意弄的,这个这个这个必须是逼疯强迫症病人的大杀器啊。。。

曾几何时为了这个文章ID做了不知多少事:
关闭Revision
关闭Auto Draft
冒着丢失几千字的风险关闭自动保存
甚至不惜影响交叉链接和搜索引擎收录而完全重排文章,几乎相当于重新建站

然而最近三观又有改变,喜欢简约风格,把主题给换了不说,而且还不再用Live Writter写文章了而改用dashboard里的在线编辑器。用这个当然不敢再一直冒风险关着自动保存,所以又把保存给打开了。而这样可能过几天发现ID又会呼啦一下跳很多了。所以今天决定再次对付一下文章ID的问题。

我的主要强迫症除了ID连续外,还觉得清理过无用revision后那些释放出来的ID为什么不能重新利用起来。所以我打算实现一下“从不连续的地方开始插入文章”这个目标。显然这个patch需要写的很底层,应该在距离执行sql insert最近的地方。经过对代码的一些阅读,最后决定直接patch在wp-db.php上。

	function insert( $table, $data, $format = null ) {
		//PATCHED BY LXF: Reset Autoincrease
		if ($table == $this->posts) {
			$insId = $this->get_var("SELECT ID+1 FROM $this->posts a WHERE NOT EXISTS (SELECT ID FROM $this->posts WHERE ID = a.ID + 1) LIMIT 1");
			//$this->query("ALTER TABLE $this->posts AUTO_INCREMENT = $insId");
			$data['ID'] = $insId;
		}
		return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
	}

开始写的是注释掉的那句ALTER,但是测试发现当前版本的MySQL不能将AUTO_INCREMENT改为小于表中已有数据最大ID的数字。我记得以前似乎是没有这个限制的,因为我曾经遇到过存在与AUTO_INCREMENT数字相等的记录导致insert永远失败的例子。

这样修改后凡是往wp_posts表里插入数据,均会取最小可插入的ID插入数据。读者可以发现这篇文章的ID已经变成了130多号而不是290号左右。先这么试用着,如果有兼容性问题再来改正。

参考文章:
WordPress 文章 ID 连续之终极方法

WordPress 3.4.2、3.5文章ID连续方法—禁用自动草稿、自动保存和文章修订

某某视频平台加密视频的解密

说实话这种玩意发出来对我没什么好处,但是本着高尚的自由主义精神,还是稍微发一下。

严重鄙视一位同行写出了类似程序但“解密一个视频收费XXX元”的行为。

做网站的也程序员也挺不容易的,所以在此不点名是哪个视频网站。不过还是要吐槽一下:你们号称的“独创的DRM视频版权保护”能不能不这么垃圾,就把FLV文件用对称加密处理一下文件头有意思伐,Adobe官方出的DRM系统你们倒是有没有了解过。

static void Main(string[] args)
{
    var fs_pcf = new FileStream(args[0], FileMode.Open, FileAccess.Read);
    var fs_flv = new FileStream(args[0] + ".flv", FileMode.Create, FileAccess.Write);
    fs_pcf.Seek(0x43, SeekOrigin.Begin);
    var buf = new byte[8184];
    int cb;
    int times = 5;
    while ((cb = fs_pcf.Read(buf, 0, 8184)) != 0)
    {
        if (times-- > 0)
        {
            var des = DESCryptoServiceProvider.Create();
            des.Key = Encoding.ASCII.GetBytes("Kyo2426C");
            des.Mode = CipherMode.ECB;
            var ciper = des.CreateDecryptor();
            var output = ciper.TransformFinalBlock(buf, 0, 8184);
            fs_flv.Write(output, 0, output.Length);
        }
        else
        {
            fs_flv.Write(buf, 0, cb);
        }
    }
    fs_pcf.Close();
    fs_flv.Close();
}

谢绝转载及二次发布。

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;
}

 

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

CHDEMU彩虹岛模拟器v1.00发布暨使用说明

(一)项目声明、与“私服”的区别、与运营商的关系准则

CHDEMU是大型多人在线角色扮演游戏(MMORPG)La Tale(中文名:彩虹岛)的单机与局域网模拟器。网络游戏La Tale由韩国Actoz开发,中国大陆区域于2007年至2012年由盛大网络代理运营,2013年以后由盛大游戏独立研发。以上组织视具体情况拥有该网络游戏的合法版权。

本项目由本人,即此博客博主、曾经的资深彩虹岛玩家、现上海师范大学音乐学院钢琴系研究生gmsj0001同学独立开发,初衷是当游戏停运或线上虚拟社会发展不乐观时能够帮助老玩家在线下找回曾经美好的回忆。项目所有服务端代码均为自主研发,中国大陆区目前没有出现源代码或服务端泄漏事件,虽然台湾区与北美区听说曾出现私服,但与本项目无关。

本项目当前使用的客户端为2008年中国大陆区域圣诞节版,此时国服彩虹岛为盛大网络代理运营,客户端版权为韩国Actoz所有,与盛大游戏无关。本项目未对该版本客户端(可执行档与游戏资源设定)进行任何修改。

“私服”是指未经版权方许可私自架设网络游戏服务器运营并从中牟利。一般而言,私服来自于泄漏的服务端程序或源代码,可以达到与正版服务器很高的仿真度。而本模拟器仿真度低下,仅能提供自娱自乐性质的基本的地图游览,同时由于作者当前主修音乐,计算机仅作为业余爱好,以后也并不会对本项目进行很大程度的维护与更新。所以本项目不是、将来也不可能成为或用于搭建真正意义上的私服。另外,作为一名深爱着彩虹岛的老玩家,本项目也秉持着给老玩家带来回忆的初衷,将永久免费提供给玩家使用,将来亦不会考虑收费。目前全世界范围内并无另外的模拟器项目,任何声称“出售彩虹岛私服”的组织或个人均为诈骗,请广大朋友注意。

即便如此,本项目仍以不影响运营商正常运营为基本准则。为此本项目采用了同样为自主研发的相对可靠的软件保护与版本控制策略,一旦发现或接到玩家汇报软件导致了有不利于官方服务器虚拟社会的情况出现,作者将有权关闭更新通道并停用已部署的软件。彩虹岛项目组若发现此软件给游戏运营带来了不利影响,请邮箱或电话联系本人,本人会配合处理。

使用本软件的玩家请注意以下几点:
1、在论坛转发本软件时请链接本文地址,注明软件名称为“彩虹岛模拟器”,不得使用“私服”、“SF”字样。
2、不得在公网Windows Server服务器上运行本软件并向公众发布IP地址。
3、不得利用本软件从事任何的营利活动,例如把本免费软件忽悠成收费的挂淘宝卖。

(二)功能与特性

本模拟器目前实现的功能有:
1、跑地图。
2、跑地图。
3、还是跑地图。

事实上,它就是个有画面的彩虹岛音乐播放器。听音乐之余,你可以与NPC对话来复习一下彩虹岛的剧情——那些你在官方服务器上忙着练级而忘了去关心的,作为一个游戏最重要的东西。

除此之外没有了。不能穿衣服,不能攻击,不能改键位,不能放技能和表情,不能吃药瓶,不能打怪。所以,鉴于客户端会占用近900M硬盘空间,请围观玩家务必一定谨慎选择是否要下载试玩。

客户端为标准的未经任何修改的国服1.42版,发布于2008年12月22日,即08年圣诞版。这个版本为刚刚开放了尘世城与太空塔的版本。之所以选择这个版本是因为作者玩彩虹岛主要在2009年,对这阶段的游戏比较熟悉。2010年开三转后彩虹岛通讯协议有了重大的变化,而那个时候作者已经离开彩虹岛了。同时,选用较旧的数据也是为了尽可能避免与官方主版本产生冲突。试想一个新资料片的地图还未在官方出现就出现在这儿的话,“领导们”会不高兴的。

(三)简单使用说明

客户端部分,用户需下载“1.42版客户端精简版”压缩包(约10M),并且需要拥有一套完整的国服当前版本的彩虹岛游戏。首次运行客户端时,软件会分析当前国服客户端中的文件,并自动提取旧版需要的文件来生成1.42版的客户端。整个过程约需要3-6分钟。

服务端模拟器部分,用户只需运行单一可执行文件chdemu.exe。请注意不要将模拟器解压到与客户端相同的目录下。默认情况下模拟器仅在本机监听,如果需要进行局域网或广域网多人游戏请使用命令行“chdemu.exe –ip xxx.xxx.xxx.xxx”启动模拟器。在广域网联机时,请自行设置路由器端口映射并进行ping测试,彩虹岛能够容忍延迟仅有50ms,否则会出现明显的卡顿,而且跑步时会抓不住梯子。

游戏登陆时会自动创建不存在的用户名,请注意第一次登陆即为设置密码的过程,务必输入准确。

结束游戏时请按ESC键点击结束游戏,不要直接关闭模拟器窗口,那样做等同于“炸线回档”,游戏将不能正确保存。

游戏中可以使用GM命令,即以双斜杠开始的指令。目前支持的仅有传送命令“//portal StageID MapGroupID [PosX] [PosY]”,可用于快速传送或人物掉入死区时恢复。如输入“//portal 6 0”可回到艾丽娅斯。常用的地图号有:6-0艾丽娅斯,15-0黑月城,67-0亚特兰蒂斯,80-0马拉松版普鲁顿神殿等等。更多数据请玩家自行实验。

(四)FAQ

暂时空缺

(五)关于误报

所有不给360“交保护费”的软件均会被该流氓杀软干掉,仔细想想这种宁可错杀一万不能放过一个的杀毒思路还确实挺适合中国大批的电脑小白的。所以请用户自行判断是否添加白名单。

(六)下载链接

客户端:LaTale_1_42_Lite.7z

模拟器:chdemu_1_00.7z

本项目已停止维护,请移步新版本http://lxf.me/228

UNICODE_STRING到wchar_t*的转换

这两天做了点Native API的东东,很近似驱动了。以前没有接触过驱动方面的东西,结果今天程序出了莫名其妙的bug。还好只是native api而没在内核,不然估计要看一天的蓝天白云下的雪崩了。

代码是注入的钩子,由于加载很早,先于exe本身的启动,故无法以dll注入的方式实现,只能以动态代码注入的形式,这样一来就无法在C语言级进行调试,只能通过反复的注释掉代码或插入CC指令、编译运行然后附加调试器的方法调试。

最后发现问题出在从UNICODE_STRING到C字符串的转换上。

开始是这么写的,并且90%的情况都能工作正常。

const wchar_t *str = punistr->Buffer;

这样写对么?看上去是对的,反正UNICODE_STRING初始化后最后是有个0字符的。

但是就有10%的概率不正常。仔细在一堆汇编里检查才发现,UNICODE_STRING中Buffer是个正常的字符串,而Length只到中间就结束了,操作系统这么做起到一个方便的截断字符串的功能,常用于取完整文件路径的目录。

于是修改代码为:

wchar_t str[256];
wcsncpy(str, punistr->Buffer, punistr->Length / 2);

对么?看上去解决了上面的问题,但是又崩了。再次仔细在一堆汇编里检查后发现原来strncpy不会往最后填写0字符。好吧,我承认这个错误不该在我这个水平上犯,但实际上我一直在用C++,很少用到strncpy,是故不知道或者忘了。

再次修改代码:

wchar_t str[256];
wcsncpy(str, punistr->Buffer, punistr->Length / 2);
str[punistr->Length / 2] = 0;

恩。它终于活过来了。立日志一篇提醒以后的自己。从这件事看来我现在还不适合写驱动诶。

VC++类成员函数委托(第一版)

前置博文:

2010.10.3:《有关C++类成员函数的指针调用

2012.12.18:《C++成员函数回调的一种实现

本研究为XDL项目(Xiaofei Development Library)的一部分。

 

实现基于X86动态代码的函数委托,并提供基于模板类的代码中显式调用。

目前实现的功能:

1、除pascal外任何调用约定的C++类成员函数通过cdecl调用约定的C函数指针进行调用的适配器。

2、thiscall调用约定的C++类成员函数通过stdcall调用约定的C函数指针进行调用的适配器。

其中1多用于代码中调用的委托,2多用于系统API传入回调函数(如WndProc、ThreadProc)。因为目前仅做到了通过RTTI识别函数指针类型的调用约定,而未做到识别参数的个数与长度,如果要做到识别参数,RTTI的parser庞大到一个小型编译器的地步,并且我对文法分析已经忘的差不多了。所以对于以上两点之外的调用约定转换考虑到用到的机会几乎为零,暂时舍弃。

使用方法举例:

class Worker
{
public:
	void ThreadProc(void *param)
	{
		return;
	}
};

int main()
{
	Worker worker;
	Delegate<void(*)(void*)> callback(&Worker::ThreadProc, &worker);
	callback((void*)12345);
	_beginthread(callback, 0, (void*)12345);
	for(;;);
	return 0;
}

主要实现代码:

static HANDLE xdl_exec_heap_handle = NULL;

void* xdl_exec_heap_malloc(unsigned int size)
{
	if (xdl_exec_heap_handle == NULL)
		xdl_exec_heap_handle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
	return HeapAlloc(xdl_exec_heap_handle, 0, size);
}

void xdl_exec_heap_free(void *p)
{
	HeapFree(xdl_exec_heap_handle, 0, p);
}

static char xdl_fn_adapter_get_callconv(const char *type_raw_name)
{
	char *p = (char*)type_raw_name;
	if (*p++ == '.' && *p++ == 'P')
	{
		if (*p == '6')
			return *++p;
		else if (*p++ == '8')
		{
			for ( ; !(*p == '@' && *(p + 1) == '@'); ++p);
			return *(p + 3);
		}
	}
	return -1;
}

void* xdl_fn_adapter(const void* fn, const void *obj, const type_info *_In_Type, const type_info *_Out_Type)
{
	int size = 0;
	char *buf = NULL;
	int pos = 0;
	if (obj == NULL)	//c function
	{
		size = 5;
		buf = (char*)xdl_exec_heap_malloc(size);
		buf[pos++] = '\xe9';	//jmp
		*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4); pos += 4;
	}
	else	//c++ member function
	{
		char in_callconv = xdl_fn_adapter_get_callconv(_In_Type->raw_name());
		char out_callconv = xdl_fn_adapter_get_callconv(_Out_Type->raw_name());
		if (out_callconv == 'A')	// cdecl
		{
			size = 45;
			buf = (char*)xdl_exec_heap_malloc(size);
			*(int*)&buf[pos] = '\x55\x8b\xec\x81';	//push ebp; mov ebp,esp; sub esp,100
			*(int*)&buf[pos + 4] = '\xec\x00\x01\x00';
			*(int*)&buf[pos + 8] = '\x00\x56\x8d\x75';	//push esi; lea esi,[ebp+8]
			*(int*)&buf[pos + 12] = '\x08\x57\x8d\xbd';	//push edi; lea edi,[ebp-100]
			*(int*)&buf[pos + 16] = '\x00\xff\xff\xff';
			*(int*)&buf[pos + 20] = '\xb9\x40\x00\x00';	//mov ecx,40
			*(int*)&buf[pos + 24] = '\x00\xf3\xa5\x5f';	//rep movsd; pop edi
			*(short*)&buf[pos + 28] = '\x5e\x68'; pos += 30;	//pop esi; push
			*(int*)&buf[pos] = (int)obj; pos += 4;
			if (in_callconv == 'E' || in_callconv == 'I')		//thiscall or fastcall
				buf[pos++] = '\x59';	//pop ecx
			if (in_callconv == 'I')
				buf[pos++] = '\x5a';	//pop edx
			buf[pos++] = '\xe8';	//call
			*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4);	pos += 4;
			*(int*)&buf[pos] = '\x8b\xe5\x5d\xc3'; pos += 4;	//mov esp,ebp; pop ebp; retn
		}
		else if (in_callconv == 'E' && out_callconv == 'G')	//thiscall to stdcall
		{
			size = 10;
			buf = (char*)xdl_exec_heap_malloc(size);
			buf[pos++] = '\xb9';	//MOV ECX
			*(int*)&buf[pos] = (int)obj; pos += 4;
			buf[pos++] = '\xe9';	//JMP
			*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4);	pos += 4;	
		}
	}
	return buf;
}

void xdl_fn_adapter_free(void *p)
{
	xdl_exec_heap_free(p);
}

template<typename _CFn>
struct Delegate
{
public:
	Delegate()
	{
		m_obj = NULL;
		m_fn_in = NULL;
		m_fn_out = NULL;
		m_type_in = NULL;
	}
	Delegate(const Delegate& d)
	{
		m_obj = d.m_obj;
		m_fn_in = d.m_fn_in;
		m_fn_out = NULL;
		m_type_in = d.m_type_in;
	}
	~Delegate()
	{
		if (m_fn_out)
			xdl_fn_adapter_free(m_fn_out);
	}
	Delegate(_CFn fn)
	{
		new(this) Delegate();
		bind(fn);
	}
	template<class _Fn>
	Delegate(_Fn fn, void* obj)
	{
		new(this) Delegate();
		bind(fn, obj);
	}

	void bind(_CFn fn)
	{
		m_fn_in = fn;
	}

	template<class _Fn>
	void bind(_Fn fn, void *obj)
	{
		m_obj = obj;
		m_fn_in = xdl_force_cast<void*>(fn);
		m_type_in = &typeid(_Fn);
	}

	operator _CFn()
	{
		if (!m_fn_out)
		{
			m_fn_out = xdl_fn_adapter(m_fn_in, m_obj, m_type_in, &typeid(_CFn));
		}
		assert(m_fn_out);
		return (_CFn)m_fn_out;
	}
private:
	const void* m_obj;
	const void* m_fn_in;
	void* m_fn_out;
	const type_info *m_type_in;
};

C++成员函数回调的一种实现

成员函数回调即C语言函数(如Windows API)调用C++类成员函数,相当于C#中的委托。

实现这个功能有两个难点,一是C语言中不允许从成员函数指针到万能指针void*的类型强转,这个可以通过union来绕过。

第二是从C调用带this指针的C++要求每个函数C函数指针能够保存不同的this指针。这个必须通过动态代码技术实现。

这两个问题记得都困扰了很久,今天终于写出一种方案,能够简单地将stdcall调用约定的C函数转到thiscall调用约定的C++函数,这样便能实现WindowProc直接窗口子类化到类成员函数中,而不需要再通过窗口句柄hash表来查找窗口类指针。动态代码也很简单,因为stdcall与thiscall对堆栈处理相同,故只需要向ecx中填写this指针然后简单地jump到成员函数的代码地址就可以。

static HANDLE xdl_exec_heap_handle = NULL;

void* xdl_thiscall_adapter(void *obj, void *fn)
{
	if (xdl_exec_heap_handle == NULL)
		xdl_exec_heap_handle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
	unsigned char *buf = (unsigned char*)HeapAlloc(xdl_exec_heap_handle, 0, 10);
	buf[0] = 0xB9;	//MOV ECX
	*(unsigned int*)&buf[1] = (unsigned int)obj;
	buf[5] = 0xE9;	//JMP
	*(unsigned int*)&buf[6] = (unsigned int)fn - ((unsigned int)buf + 10);
	return (void*)buf;
}

void xdl_thiscall_adapter_delete(void *p)
{
	HeapFree(xdl_exec_heap_handle, 0, p);
}

template<class _Out, class _In>
__inline _Out xdl_force_cast(_In value)
{
	union
	{
		_In in;
		_Out out;
	} u = {0};
	u.in = value;
	return u.out;
}

template<class _Obj, class _Fn>
__inline LPVOID xdl_thiscall_adapter(_Obj* obj, _Fn fn)
{
	return xdl_thiscall_adapter((void*)obj, xdl_force_cast<void*>(fn));
}

防调试代码

程序功能:“断线程”技术防调试
运行平台:x86 & x64
编写时间:2012.12.13-2012.12.14
参考资料:毕业设计第一版、《加密与解密》、网上的一些x64平台资料

00401013  |.  60            PUSHAD
00401014  |.  6A 00         PUSH 0
00401016  |.  6A 00         PUSH 0
00401018  |.  6A 11         PUSH 11
0040101A  |.  6A FE         PUSH -2
0040101C  |.  33C9          XOR ECX,ECX
0040101E  |.  64:8B71 30    MOV ESI,DWORD PTR FS:[ECX+30]
00401022  |.  8B76 0C       MOV ESI,DWORD PTR DS:[ESI+0C]
00401025  |.  8B76 1C       MOV ESI,DWORD PTR DS:[ESI+1C]
00401028  |.  8B6E 08       MOV EBP,DWORD PTR DS:[ESI+8]
0040102B  |.  8B5D 3C       MOV EBX,DWORD PTR SS:[EBP+3C]
0040102E  |.  8B5C2B 78     MOV EBX,DWORD PTR DS:[EBP+EBX+78]
00401032  |.  03DD          ADD EBX,EBP
00401034  |.  8B4B 18       MOV ECX,DWORD PTR DS:[EBX+18]
00401037  |>  8B7B 20       /MOV EDI,DWORD PTR DS:[EBX+20]
0040103A  |.  03FD          |ADD EDI,EBP
0040103C  |.  8B7C8F FC     |MOV EDI,DWORD PTR DS:[ECX*4+EDI-4]
00401040  |.  03FD          |ADD EDI,EBP
00401042  |.  33C0          |XOR EAX,EAX
00401044  |.  99            |CDQ
00401045  |>  3217          |/XOR DL,BYTE PTR DS:[EDI]
00401047  |.  C1C2 05       ||ROL EDX,5
0040104A  |.  AE            ||SCAS BYTE PTR ES:[EDI]
0040104B  |.^ 75 F8         |\JNE SHORT 00401045
0040104D  |.  8B43 24       |MOV EAX,DWORD PTR DS:[EBX+24]
00401050  |.  03C5          |ADD EAX,EBP
00401052  |.  0FB74448 FE   |MOVZX EAX,WORD PTR DS:[ECX*2+EAX-2]
00401057  |.  8B7B 1C       |MOV EDI,DWORD PTR DS:[EBX+1C]
0040105A  |.  03FD          |ADD EDI,EBP
0040105C  |.  8BF5          |MOV ESI,EBP
0040105E  |.  033487        |ADD ESI,DWORD PTR DS:[EAX*4+EDI]
00401061  |.  81FA 8D98A0E2 |CMP EDX,E2A0988D
00401067  |.  74 02         |JE SHORT 0040106B
00401069  |.^ E2 CC         \LOOP SHORT 00401037
0040106B  |>  8B46 01       MOV EAX,DWORD PTR DS:[ESI+1]
0040106E  |.  8BD4          MOV EDX,ESP
00401070  |.  33C9          XOR ECX,ECX
00401072  |.  64:8B35 C0000 MOV ESI,DWORD PTR FS:[0C0]
00401079  |.  85F6          TEST ESI,ESI
0040107B  |.  75 04         JNZ SHORT 00401081
0040107D  |.  CD 2E         INT 2E
0040107F  |.  EB 07         JMP SHORT 00401088
00401081  |>  89E5          MOV EBP,ESP
00401083  |.  FF5E 01       CALL FAR FWORD PTR DS:[ESI+1]            ; Far jump or call
00401086  |.  89EC          MOV ESP,EBP
00401088  |>  83C4 10       ADD ESP,10
0040108B  |.  61            POPAD
HEX: 606A006A006A116AFE33C9648B71308B760C8B761C8B6E088B5D3C8B5C2B7803DD8B4B188B7B2003FD8B7C8FFC03FD33C0993217C1C205AE75F88B432403C50FB74448FE8B7B1C03FD8BF503348781FA8D98A0E27402E2CC8B46018BD433C9648B35C000000085F67504CD2EEB0789E5FF5E0189EC83C41061