virtualxposed使用教程_使用VirtualXposed修改手游

前篇

前言

这篇后续文章原本是打算很快就写完的,但是不知怎么一转眼就已经12月了,眼看今年都要过了,还是赶紧把这篇文章水出来吧。

在上篇文章说道的修改的核心思路,就是注入so到游戏中,而这次用到的VirtualXposed,其实就是作为注入器一样的存在。VirtualXposed这款软件已经不是什么新奇的东西了,在手游上被用来做外挂,做mod也是非常常见了。VirtualXposed原本的功能是利用VirtualApp做到无需root使用Xposed,而我们要使用的其实只是其中VirtualApp的部分,因为VirtualApp已经商业化开源部分已不再更新,兼容性存在问题,所以就直接使用VirtualXposed了。

既然VirtualXposed能用于修改手游,那被检测也是很正常的一件事,虽然也有办法对抗各种保护,但是这篇文章就不会说到这些东西了,所以为了保证本文还有点用处,我测试了中日热门的和新出的几款手游。结果倒是有点出乎我的意料,日游的话基本上是没法在VirtualXposed上运行,倒是国内游戏基本都能跑。稍微看了下日游现在大部分都有上保护,有保护的就凉了,而国内的游戏上了保护的也还是能正常运行,虽然不懂具体是什么情况,不过至少证明了用VirtualXposed做修改还是适合现在国内这个版本的。

好了,接下来就进入正题吧。

编译VirtualXposed

编译之前要做的事就是先安装好编译环境啦,包括java sdk,android sdk以及ndk,也可以选择安装Android Studio。搞定了编译环境之后就可以去github上clone源码了,作者的wiki上已经说明了应该如何正确clone源码,按他给的命令操作就行了,clone源码到本地之后,我们还需要修改一下源码从而达到注入so的目的。

源码要修改三个文件,其中两个是

VirtualXposed\VirtualApp\app\src\main\java\io\virtualapp\VCommends.java

VirtualXposed\VirtualApp\lib\src\main\jni\Jni\VAJni.cpp

在这两个文件里可以看到两个被注释“君子坦荡荡,小人常戚戚”的函数,是作者做签名验证用的,直接注释掉就行了。

VirtualXposed\VirtualApp\lib\src\main\java\com\lody\virtual\client\NativeEngine.java

在这个文件里可以看到一行代码

System.loadLibrary("va++");

这里就会让每个从VirtualXposed里启动的app都load一个libva++.so,所以我们直接在后面多加一句System.loadLibrary自己的so名就能实现注入了。

修改完源码就可以编译了,可以用Android Studio,也可以直接使用

gradlew.bat assembleRelease

命令进行编译,值得一提的是最新版(3311c0f)的代码编译x86的时候似乎有点问题,可以修改

VirtualXposed\VirtualApp\lib\build.gradle

这个文件的第21行删除x86配置来绕过,或者切换到7d051f0这个版本

当然编译中可能还会遇到各种各种的错误,这个时候就请善用百度啦。

编译so

这次要演示的是使用hook来修改unity3d dll的游戏,hook的话就是hook libmono下mono_image_open_from_data_with_name这个函数了。而从so被调用到进行hook,我们要做的事情就是判断是否是需要修改的游戏,然后获取到libmono在内存中的基址,这段代码在修改其他游戏的时候是可以通用的:

long get_module_base(const char* module_name, const char* package_name)

{

FILE *fp;

long addr = 0;

char *pch;

char filename[32];

char line[1024];

snprintf(filename, sizeof(filename), "/proc/self/maps");

fp = fopen(filename, "r");

if (fp != NULL) {

while (fgets(line, sizeof(line), fp)) {

if (strstr(line, module_name) && strstr(line, package_name)) {

pch = strtok(line, "-");

addr = strtoul(pch, NULL, 16);

if (addr == 0x8000)

addr = 0;

break;

}

}

fclose(fp);

}

return addr;

}

void *thread_hack(void *arg)

{

int count = 0;

while (true)

{

long mono = get_module_base("libmono.so", "游戏包名");

if (mono != 0)

{

hack_game(mono);

break;

}

count++;

if (count > 5)

{

break;

}

sleep(1);

}

return NULL;

}

__attribute__((constructor)) void entry()

{

int ret;

pthread_t ntid;

if ((ret = pthread_create(&ntid, NULL, thread_hack, NULL)))

{

LOGE("can't create thread: %s\n", strerror(ret));

}

}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)

{

JNIEnv* env;

if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)

{

return JNI_ERR;

}

return JNI_VERSION_1_6;

}

这里我用的是非常简单的读取maps的方式判断游戏和获取需要修改的so的基址,每过一秒判断一次,超过5次就会自动退出,虽然这种方式不是什么很好的做法,不过能用就行了。

上面hack_game这个函数就是修改游戏的部分了,我们就在这个函数里hook mono_image_open_from_data_with_name。这次用的是简单的inlinehook,就不需要什么hook框架了

typedef void* (*GAME_PROXY)(char *data, int data_len, int need_copy, void *status, int refonly, char* name);

GAME_PROXY old_game_proxy = 0;

char g_HookCode_game_proxy[8] = { 0 };

char g_OrigCode_game_proxy[16] = { 0 };

void inlineHook_game_proxy(void* currentFunc, void* targetFunc)

{

//保存原函数头

char *tmp = (char*)currentFunc;

for (int i = 0; i < 8; i++) {

g_OrigCode_game_proxy[i] = tmp[i];

}

//函数头设置属性可写

void* page_start = (void*)((long)tmp - (long)tmp % PAGE_SIZE);

if (-1 == mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC)) {

LOGE("mprotect failed(%d)", errno);

return;

}

//生成跳转指令

memcpy(g_HookCode_game_proxy, "\x04\xf0\x1f\xe5\x00\x00\x00\x00", 8);

*(unsigned long *)&(g_HookCode_game_proxy[4]) = (unsigned long)targetFunc;

//替换函数头,把跳转指令写进去

for (int i = 0; i < 8; i++) {

tmp[i] = g_HookCode_game_proxy[i];

}

//跳回原函数指令

memcpy(&g_OrigCode_game_proxy[8], "\x04\xf0\x1f\xe5\x00\x00\x00\x00", 8);

*(unsigned long *)&(g_OrigCode_game_proxy[12]) = (unsigned long)currentFunc + 8;

//设置可执行权限

page_start = (void*)((long)g_OrigCode_game_proxy - (long)g_OrigCode_game_proxy % PAGE_SIZE);

if (-1 == mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC)) {

LOGE("mprotect failed(%d)", errno);

return;

}

old_game_proxy = (GAME_PROXY)(g_OrigCode_game_proxy);

}

void* new_game_proxy(char *data, int data_len, int need_copy, void *status, int refonly, char *name)

{

if (strstr(name, "Assembly-CSharp.dll"))

{

FILE * pFile = fopen("/data/local/tmp/Assembly-CSharp.dll", "r");

fseek(pFile, 0, SEEK_END);

int len= ftell(pFile);

rewind(pFile);

char * buffer = (char *)malloc(sizeof(char)*len);

fread(buffer, 1, len, pFile);

fclose(pFile);

data = buffer;

data_len = len;

}

return old_game_proxy(data, data_len, need_copy, status, refonly, name);

}

void hack_game(long addr)

{

long mono_image_open_from_data_with_name = addr + 偏移;

inlineHook_game_proxy((void*)mono_image_open_from_data_with_name, (void*)new_game_proxy);

}

这个的修改本质其实就是在调用mono_image_open_from_data_with_name之前修改传入的参数,从而载入我们修改好的dll,对于一些有加密的游戏,可以选择先调用mono_image_open_from_data_with_name,然后修改返回值MonoImage结构体的raw_data和raw_data_len。

写完so后就可以用ndk进行编译了,然后塞进上面编译好的VirtualXposed,签名后就可以安装使用啦。

结束啦

文章到这里就结束啦,虽然只演示了修改unity3d的dll这一种,但是其他il2cpp或者cocos2dx游戏修改so的,其实就是直接操作指针修改内存了,参考inlinehook部分就能很容易写出来了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值