首页
论坛
专栏
课程

[原创]无源码加解密实现 && NDK Native Hook

ThomasKing
6
2014-9-8 17:11 40158
帖子名字取得有点搓,请各位见谅。
---------------------------------------
上回小弟小小研究了一番有源码so简单加解密方式。像各种加固软件一样,是拿不到别人的源码的。经过小弟一番折腾,实现了简单粗暴的无源码加解密方法。在此过程中,某晚YY到native method,有了一种简单的native method hook思路。看到论坛没有类似帖子,故来一帖,也作为学习笔记。限于小弟水平,肯定会有不少疏漏和错误之处,还请各位大牛指正,感激不尽!
---------------------------------------
一、  概述
在上个帖子 http://bbs.pediy.com/showthread.php?t=191649 中介绍了so加解密实现的基本思路和有源码自加解密实现。仅仅实现有源码的方式,肯定不是我们想要的。下面介绍一种无源码的加解密实现和简单的Native method hook思路。

二、  无源码加解密实现
在上个简单粗暴的so加解密帖子中,介绍了基于section和特定目标函数(或数据区)加解密实现思路。加密步骤中,我们并不需要被加密so文件的源码,而需要源码原因是:解密的时机放在了so文件被linker加载执行.init_array中的代码过程中,实现so加载时的自解密。把解密代码插到.init_array中,自然需要源码。其实,只要在函数被执行前进行解密,都是可以的。由于所有被加载的so处于同一进程中,故可以将解密函数放在另一个so执行,即实现无源码解密。

将解密函数放在另一个so中,只需保证解密函数在被加密函数执行前执行即可。和其他so一样,解密so的加载也放在类的初始化static{}中。我们可以在解密so加载过程中执行解密函数,就能保证被加密函数执行前解密。执行时机可以选在linker执行.init_array时,也可以选在OnLoad函数中。当然,解密so一定要放在被解密so后加载。否则,搜索进程空间找不到被解密的so。

加密方法可以随意选择上个帖子中的一种,解密只需将原来的解密函数单独放在一个so文件即可。相信读者已经明白,这里就不啰嗦,见附件源码shelldemo。

三、  Native method Hook 简单实现
1.  解包添加hook.so实现
很容易知道,native 方法并不像so中的其他函数被直接被调用。而是通过注册并被间接调用的。在so加载和卸载过程中,Onload 和 Unload函数会被调用,类似于驱动的加载和卸载。Onload函数主要功能是调用RegisterMethod注册native函数;而Unload则相反。那么如果增加一个so文件,在Onload中调用UnregisterMethod函数,将当前绑定的函数注销,再调用RegisterMethod注册我们自己的函数,即实现简单的hook功能。相信读者已经懂得如何做,这里就不给出源码了。

这里存在着一个问题,UnregisterMethod会将clazz所有的函数都注销掉(Un-register all native methods associated with the class)。当一个类存在多个native函数时,这种方法显然是不可取的。我们不可能实现原so中的所有函数,且也达不到hook的某一或某些函数的目的。
我们知道,同一个native方法是可以被多次注册的。既然可以被多次注册,那么struct Method insns 字段绑定的是最后一次注册的函数。Hook的思路很简单了:调用RegisterMethod,替换原来的某一或某些native方法。相信读者已经懂得如何做,这里就不给出源码了。

2.  进程注入实现
既然可以hook native方法,绕过一些简单注册什么的,还是可以的,读者可以自行试试。但美中不足的是,现很多apk作了自校验,直接添加hook.so到原包中,添加smali 加载代码实现hook肯定不现实。在不动原包的情况下,我们只能通过进程注入来实现了。

网上有很多大大的注入源码,这里假设读者已经有源码并能正常注入进程(我这里使用古河大大的注入代码)。为了便于读者理解,我这里构建一个简单的apk。


Button调用native foo()函数,获取字符串并在TextView中显示。在demo.so中,foo函数为:static jstring foo(JNIEnv* env){
    return env -> NewStringUTF("Wait for hook!");
};

我们的目标是将foo函数hook,返回字符串:”Hooked! Oh yeah!”。可能读者已经想到,在hook_entry中调用findclass,然后直接注册函数即可。当我写好代码测试时,发现如下错误:


NoClassDefFoundError,初看以为类名写错了,复制demo.c的代码过来测试,还是同样的错误。仔细看还有[generic],翻看源码和各种谷歌,发现此错误其实可能不是类名没找到,而类加载器不对。可能读者会问,第一种实现并未出现异常。的确,那是因为so是通过loadLibrary加载的,而这里是通过注入实现的。loadLibrary 会自动找到合适的类加载器,而这里使用默认的类加载器bootclassloader加载,出现未找到的异常(不太了解jni机制的读者,可以参看老罗关于jni加载机制的帖子)。具体异常原因就是:调用findclass由于类加载器不对,出现异常并且未作异常处理而抛出的。

知道这个原因,具体实现就简单了,就在是findclass失败后,添加调用自己实现的findclass即可。主要代码:
extern "C" void HookNativeMethod(char *para){
  JNIEnv *env = NULL;
  jclass clazz = NULL;
  env = android::AndroidRuntime::getJNIEnv();
  if(env == NULL){
    __android_log_print(ANDROID_LOG_INFO, "INJECTED", "Get JNIEnv failed\n");
    goto _error;
  }
  clazz = android::AndroidRuntime::findClass(env, JNIREG_CLASS);
  if(clazz == NULL){
    __android_log_print(ANDROID_LOG_INFO, "INJECTED", "Find Class failed by BootClassLoader\n");
    if(env->ExceptionOccurred()){
          env->ExceptionDescribe();
          env->ExceptionClear();
    }
    clazz = myFindClass(env, JNIREG_CLASS);
    if(clazz == NULL)
    {
      __android_log_print(ANDROID_LOG_INFO, "INJECTED", "Find Class failed\n");
      goto _error;
    }
  }
  if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0) {
    __android_log_print(ANDROID_LOG_INFO, "INJECTED", "Register Hook Method failed\n");
    goto _error;
  }
  return;
_error:
  __android_log_print(ANDROID_LOG_INFO, "INJECTED", "Hook Native Method failed\n");
}
具体实现见源码,这里就不贴了。
注入后的效果如下:


四、参考文献
http://blog.csdn.net/luoshengyang/article/details/8923483
后面发现,其实在这篇帖子中:
http://bbs.pediy.com/showthread.php?t=186054&highlight=%E6%B3%A8%E5%85%A5
也有findclass的实现
安卓源码: dalvik\vm\jni.c native.cpp jniinternal.h androidRuntime.cpp androidRuntime.h

附件:
Shelldemo.zip
NativeMethodHook.zip
ndk hook.pdf

[推荐]十年磨一剑!《加密与解密(第4版)》上市发行

上传的附件:
最新回复 (32)
OnlyEnd 2014-9-8 17:30
2

0

真棒!!!  学习学习`
zhighest 2014-9-8 19:59
3

0

学习………………感谢分享
mingxuan三千 2014-9-8 22:58
4

0

学习 望楼主多发文章
非虫 6 2014-9-9 13:04
5

0

感谢分享,加精处理。
ThomasKing 6 2014-9-9 13:46
6

0

感谢非虫大哥鼓励!
hwuyule 2014-9-9 13:53
7

0

追随王总脚步呀
丘比龙 2014-9-9 13:57
8

0

看来一下,没看太懂
wule 2 2014-9-9 15:12
9

0

楼主加油!!感觉越来越牛了!!
appview 2014-9-9 15:13
10

0

好、支持、有希望!
ThomasKing 6 2014-9-9 15:46
11

0

多谢你上次提醒我!
ThomasKing 6 2014-9-9 15:47
12

0

感谢大家的支持!
OnlyEnd 2014-9-9 15:50
13

0

王总威武,跟着王总学习各种姿势
万抽抽 2 2014-9-9 16:19
14

0

王总真是高产啊!学习了。
silence刘 2014-9-10 13:29
15

0

先mark一下,有时间再看看
Fido 2014-9-12 10:05
16

0

谢谢分享...学习............
Fido 2014-9-12 10:07
17

0

谢谢分享..学习了...
aiseven 2014-9-15 16:22
18

0

感谢分享

我运行的方法是

编译 NativeMethodHook 安装到手机上
然后用 cmd里面用adb shell 把 inject和 libhook.so push到/data/data下面
然后 /data/data/inject pid
然后点击 NativeMethodHook 里面的按钮

没有hook成功,能请教一下该怎么运行吗?
ThomasKing 6 2014-9-15 19:08
19

0

你看下logcat有hook输出没? 注意需要root权限
aiseven 2014-9-16 09:18
20

0

感谢楼主的回复!

手机肯定是root的。inject 和libhook.so 肯定都push到/data/data/ 下面

奇怪的是 inject后 cat /proc/pid/maps 后内存里面没有/data/data/libhook.so
所有的文件除了apk是我自己编译生成的,libs下面的都是用你工程里面带的。
能帮忙指点一下问题在哪吗?

再次感谢!

以下是logcat 里面的信息,看起来很正常

09-16 09:09:19.140: D/Thomas Say:(16226): [+] Injecting process: 2610
09-16 09:09:19.150: D/Thomas Say:(16226): [+] get_remote_addr: local[40094000], remote[40087000]
09-16 09:09:19.150: D/Thomas Say:(16226): [+] Remote mmap address: 4009fbbd
09-16 09:09:19.150: D/Thomas Say:(16226): [+] Calling mmap in target process.
09-16 09:09:19.150: D/Thomas Say:(16226): [+] Target process returned from mmap, return value=5c90b000, pc=0
09-16 09:09:19.165: D/Thomas Say:(16226): [+] get_remote_addr: local[b0001000], remote[b0001000]
09-16 09:09:19.180: D/Thomas Say:(16226): [+] get_remote_addr: local[b0001000], remote[b0001000]
09-16 09:09:19.180: D/Thomas Say:(16226): [+] get_remote_addr: local[b0001000], remote[b0001000]
09-16 09:09:19.180: D/Thomas Say:(16226): [+] Get imports: dlopen: b0005a7d, dlsym: b00059e9, dlclose: b0005929
09-16 09:09:19.180: D/Thomas Say:(16226): [+] Inject code start: 11018, end: 11094
09-16 09:09:19.180: D/Thomas Say:(16226): [+] _dlopen_param1_s: 5c90ec9c
09-16 09:09:19.180: D/Thomas Say:(16226): [+] _dlsym_param2_s: 5c90ed9c
09-16 09:09:19.180: D/Thomas Say:(16226): [+] _saved_r0_pc_s: 5c90ee9c
09-16 09:09:19.180: D/Thomas Say:(16226): [+] _inject_function_param_s: 5c90ef9c
09-16 09:09:19.180: D/Thomas Say:(16226): [+] Remote shellcode address: 5c90ec00
ThomasKing 6 2014-9-16 11:09
21

0

那你换一个so试试,看能正常注入么? 我这边又试了下,没什么问题。
jecray 2014-9-16 13:06
22

0

谢谢,学习了!
wule 2 2014-9-16 23:10
23

0

楼主大哥,小弟一直看不懂那个findclass函数,,能给点提示或者建议啥的么?
老党 2015-1-22 09:57
24

0

有大量兼容适配问题
ThomasKing 6 2015-1-22 11:32
25

0

额,有什么问题,请大大指出?
lsniagaraI 2015-1-30 11:39
26

0

mark一下,以后学习。
Colbert仔 2015-3-5 10:58
27

0

DK大神,问下Shelldemo.zip.里面的:
void clearcache(char* begin, char *end)
{
        const int syscall = 0xf0002;
        __asm __volatile (
                "mov         r0, %0\n"
                "mov         r1, %1\n"
                "mov         r7, %2\n"
                "mov     r2, #0x0\n"
                "svc     0x00000000\n"
                :
                :        "r" (begin), "r" (end), "r" (syscall)
                :        "r0", "r1", "r7"
                );
}

这个函数的作用是什么呢?syscall = 0xf0002是什么意思呢?fork函数?
Colbert仔 2015-3-6 10:02
28

0

研究出来了,用了系统调用号,清掉缓存
大王叫我挖坟 3 2016-4-15 14:14
29

0

你的文章总是那么棒那么给力,
大王叫我挖坟 3 2016-5-2 11:18
30

0

有没有直接对原来的so进行修改实现加壳的办法,比如在原来的so里面加入一个节,然后里面有个解密函数,在init_array中先调用这个解密函数,但是肯定需要改很多链接的过程吧,,,我说的是无源码加壳,并且不生成多的so,仅仅只是对so文件进行修改,不对apk处理,也不hook
田darren 2017-3-1 11:38
31

0

学习了
hackoflife 2017-3-2 11:56
32

0

留名收藏,当作字典
wcofen 2018-7-16 21:04
33

0

谢谢 正在学习 怎们HOOK NDK
返回