代码重用及RPC思路

代码重用及RPC思路

这里讨论的代码重用算是狭义意义上的黑盒或灰盒代码重用,如对编译后文件中具体的一个函数、一个类进行利用,这里涉及两种情况:

  1. 可以分离出目标代码所在文件的,在不加固情况下可以拿到dex或so文件的,在加固情况下通过脱壳可以还原出目标代码,且目标代码依赖类也被还原出来;
  2. 无法分离出目标代码的,一般就是加固应用。

对于第一种情况,最理想的情况就是通过直接扣出代码进行重用,但是对于代码量过大或反编译效果不太理想的情况,就需要在运行时进行重用,思路是自己写app加载dex或so进行反射调用;
对于第二种情况,只能在运行时重用,且无法剥离原应用,只能通过hook解决,这里重点介绍xposed和frida两种工具的操作方式:

frida

代码重用主要解决的是获取到方法所在类的实例,利用实例进行方法调用,需要实例的方式有:

  1. 通过Java.use(‘xxx’).method.implementation实现对具体方法hook时获取this对象来保存目标对象实例,尚不清楚Java.retain(this)和直接获取this有啥区别;
  2. 通过Java.choose来搜索当前堆中已有的目标类对象实例再对其保存;
  3. 对于构造函数参数简单的类可以通过调用&new创建一个实例。

之前帮一位大佬搞一个app的时候因为需要将app的方法rpc,吹一波大佬写的burp插件:https://github.com/mr-m0nst3r/Burpy

rpc:
用大佬的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function exported() {
rpc.exports = {
Decrypt: function (data) {
console.log("Calling Decrypt function:");
var res = null;
Java.perform(function () {
res = sm4Instance.g(key, data);
}
console.log("key", key, "data", data);
console.log("dec res:", res);
});
return res ? res : "failed to decrypt";
},
...
};
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Burpy:
def __init__(self):
self.rpc = self._load_rpc()
def _load_rpc(self):
source = ""
with codecs.open(r"hook.js",'r','utf-8') as f:
source = f.read()
script = self.session.create_script(source)
script.load()
return script.exports
def decrypt(self,header,body):
print("decrypting")
ret0 = self.rpc.decres(url, body)
print("decry result:", ret0)
return header, ret0

本机与注入进程间的通讯是使用frida提供的rpc.exports,burp与本地python进程间的通讯大佬用的是pyro。

xposed

xposed的重用方式与frida总体类似:

  1. 通过XposedHelpers.findAndHookMethod(…)在对具体方法hook时获取methodHookParam.this对象来保存目标对象实例;
  2. 通过分析代码寻找赋值为目标类实例对象的类成员变量来反射获取;
  3. 对于构造函数参数简单的类可以通过调用newInstance创建一个实例。

rpc:

这里介绍一种使用广播进行rpc的方法,注册一个调用目标函数的广播后,通过adb命令触发这个广播来实现rpc,所以本机和App进程通讯是通过终端io,这并不理想,如果后续找到更好的通讯方式再做分享。
首先我们写一个广播,包括接收参数,参数处理,并调用目标函数。动态注册该广播:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class MyReiceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg_b64=intent.getStringExtra("msg");
String type=intent.getStringExtra("type");
String url=intent.getStringExtra("url");
Class CryptoUtil= XposedHelpers.findClass("com.xxx.EncryptUtils.SM4",loadPackageParam.classLoader);
Class<?> PEJniLib = XposedHelpers.findClass("com.xxx.EncryptUtils.PEJniLib",loadPackageParam.classLoader);
//target func is static
if(type.equals("0")){
String msg = new String(Base64.decode(msg_b64.getBytes(), Base64.DEFAULT));
String result=(String)XposedHelpers.callStaticMethod(CryptoUtil,"h",msg,key);
XposedBridge.log("invoke SM4encrypt:"+result);
}else if(type.equals("1")){//target func is direct
try {
Object PEJniLib_=PEJniLib.newInstance();
String result = (String) XposedHelpers.callMethod(PEJniLib_, "a", msg, key);
XposedBridge.log("invoke getNativeValue:" + result);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}

intentFilter = new IntentFilter();
intentFilter.addAction("com.fancy.tohook.cipher");
MyReiceiver myBroadcastReceiver = new MyReiceiver();
if(ct==null){
XposedBridge.log("context is null");
}
else {
//ct is the application context
ct.registerReceiver(myBroadcastReceiver, intentFilter);
}

使用am命令指定该action发送广播:

1
adb shell am broadcast  -a com.fancy.tohook.cipher --include-stopped-packages --es type "0" --es msg "2Jpg7LWBDy8TeZNy2KBWnjDDebA5kgNKWGQ3SzJfseXjViquEhKMAAQlI3smiQqfmsID2FuZ3OvUp0laB8DOY3UOxZdBsCyBIqzXhhlZ1+uCv/iBSQX5yfgRyxjtgXQwCq/ZmAkZ2SPvY3r2Q/mfBUmHvMgpAQJAGTlessJCXBnJfjT3R/1950ToS0w1xpXnIEYb7kMMF4SnqQNQ76BGWaLAvKfHc2O3B6a7YtmHoLbH8+Cl8h3TLi6zl9/YiGDdJGOn3ikijF2blKzjo5PjWzEf+imb8EJ9hq+D9C0qWAPfPwjxAT73GKgqKJR7lBjzqJ+pBg0uel8ud7geDVJ4G7s5wStZVVFpmW4EWplNG06ln8SWwj+v1t8Vo3hxGMIzqJSiWk3P1Fkt/y8Q7GaB9E5iv4rv5JLc6OpSqmEC74A744VF9kcL36LnVTwp7j04PtDdZzrD7i8qRZzX2sxG30QH/wDhu7fQSekkLzcVB1mDaxJzKi1JUBn8Aykseio/muzc4qiNb5VURBu5WsIT+SQpY0YVtKV2sBLJlQC7bj4Lo3KPrcvbklgg8EYPh0OhSHY2FozeKCR/ZLVngpqcL7nGJ7EsBJnxVjJO1F59pDc=" --es key "1234567890123456"

使用adb logcat -d -s xxx_tag获取目标函数返回内容。

扩展

对脚本语言或部分依赖虚拟机的语言的代码文件反编译可以得到比较理想的伪代码,并且代码量小、耦合度低的目标代码才能通过将其分离出来,正向开发的方式对其重用,这是很理想的场景;大部分的目标往往需要通过hook来实现目标代码重用,而这需要依赖于目标代码文件具备对应的可执行环境并且真正运行起来,可执行环境包括对应的硬件设备,所在的操作系统平台,甚至是某个版本,比如需要一台运行最新安卓系统的手机;能够运行起来还需要保证可执行文件所依赖的资源完整,比如一个apk包。

再延伸开去就涉及到虚拟机和模拟器的讨论,放到下篇讨论吧。