本次选用BlackBox进行二次开发,添加两个功能:
1 做一个隐私检测的功能,并适配我的隐私阅读器
2 设置一个frida开关,使其在非root机也能使用frida
一、隐私检测
1 选用的hook框架为Pine,注入点在BActivityThread的loadXposed函数
public void loadXposed(Context context) {
String vPackageName = getAppPackageName();
String vProcessName = getAppProcessName();
YLog.info("loadXposed start process -> " + vProcessName);
if (!TextUtils.isEmpty(vPackageName) && !TextUtils.isEmpty(vProcessName)) {
if (BXposedManager.get().isXPEnable()){
// TODO -> xposed hook
// ... ...
}else{
// TODO -> Pine hook (privacy check)
try{
HookHelper.getInstance().registerHook(vPackageName).hook();
} catch (Throwable th){
YLog.error(th.toString());
}
}
}
if (BlackBoxCore.get().isHideXposed()) {
NativeCore.hideXposed();
}
}
HookHelper.java
public class HookHelper {
private static HookHelper mHelper;
private final List hooks = new ArrayList<>();
public void hook(){
for (HookMethods hook : hooks) {
try {
hook.hook();
} catch (Exception err) {
YLog.error(err.toString());
}
}
}
private HookHelper(){
}
public static HookHelper getInstance(){
if (mHelper == null) {
synchronized (HookHelper.class) {
if (mHelper == null) {
mHelper = new HookHelper();
}
}
}
return mHelper;
}
public HookHelper registerHook(String pkg) {
try{
MsgDispatcher.dispatch();
//hooks.add(new HookBinderProxy());
hooks.add(new HookTelephonyManager());
hooks.add(new HookWifiInfo());
hooks.add(new HookNetworkInterface());
hooks.add(new HookLocationManager());
hooks.add(new HookApplicationPackageManager());
hooks.add(new HookPackageManager(pkg));
hooks.add(new HookSettingsSecure());
hooks.add(new HookSettingsSystem());
hooks.add(new HookSystemProperties());
hooks.add(new HookBluetoothAdapter());
hooks.add(new HookBluetoothDevice());
hooks.add(new HookCamera());
hooks.add(new HookInetAddress());
hooks.add(new HookActivityManager());
hooks.add(new HookCdmaCellLocation());
hooks.add(new HookGsmCellLocation());
hooks.add(new HookWifiManager());
hooks.add(new HookLocation());
hooks.add(new HookBuild());
hooks.add(new HookClipboardManager());
//hooks.add(new HookSmsManager());
hooks.add(new HookContextImpl());
//hooks.add(new HookContentProviderProxy());
}catch (Exception err) {
YLog.error(err.toString());
}
return this;
}
}
Hook的相关类
public interface HookInterface {
void hook();
}
public abstract class HookMethods implements HookInterface {
public Class> mCls = null;
public String mClz = "";
public HookMethods(String clz) {
mClz = clz;
}
protected abstract void hookMethod();
@Override
public void hook(){
try{
mCls = Class.forName(mClz);
hookMethod();
}catch (Exception e){
YLog.error(e.toString());
}
}
}
public class HookMessage {
public String mStackTrace = null;
public String mClass = null;
public String mMethod = null;
public String mMethodSignature = null;
public List mParams = null;
public String mReturn = "";
public String mDescription = null;
public String mDate = null;
public int mCount = 0;
public HookMessage(String stacktrace, String cls, String method, String sig, List params, String ret, String des, int count, String date){
mStackTrace = stacktrace;
mClass = cls;
mMethod = method;
mMethodSignature = sig;
mParams = params;
mReturn = ret;
mDescription = des;
mCount = count;
mDate = date;
}
public static class Builder{
public String mStackTrace = "";
public String mClass = "";
public String mMethod = "";
public String mMethodSignature = "";
public List mParams = null;
public String mReturn = "";
public String mDescription = "";
public int mCount = 0;
public String mDate = "";
public Builder(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = sdf.format(new Date());
setDate(currentTime);
}
public Builder setCount(int c){
this.mCount = c;
return this;
}
public Builder setParams(List ls){
this.mParams = ls;
return this;
}
public Builder setReturn(String s){
if (s == null){
this.mReturn = "null";
}else{
this.mReturn = s;
}
return this;
}
public Builder setStackTrace(String s){
this.mStackTrace = s;
return this;
}
public Builder setClass(String s){
this.mClass = s;
return this;
}
public Builder setMethod(String s){
this.mMethod = s;
return this;
}
public Builder setMethodSignature(String s){
this.mMethodSignature = s;
return this;
}
public Builder setDescription(String s){
this.mDescription = s;
return this;
}
public Builder setDate(String s){
this.mDate = s;
return this;
}
public HookMessage build(){
return new HookMessage(mStackTrace, mClass, mMethod, mMethodSignature, mParams, mReturn, mDescription, mCount, mDate);
}
}
}
public class MsgDispatcher {
public static final LinkedList mMsg = new LinkedList(); //消息队列
public static void dispatch(){
new Thread() {
public void run() {
while(true){
try {
HookMessage msg = get();
if (msg != null){
send(msg);
}
}catch (Throwable th) {
YLog.error(th.toString());
}
}
}
}.start();
}
public static void put(HookMessage msg){
synchronized(mMsg) {
mMsg.add(msg);
}
}
public static HookMessage get(){
HookMessage msg = null;
synchronized(mMsg) {
if (mMsg.isEmpty()){
return msg;
}
msg = (HookMessage)mMsg.poll();
}
return msg;
}
public static void send(HookMessage msg){
try{
JSONObject jSONObject = new JSONObject();
jSONObject.put("Time", "" + msg.mDate);
jSONObject.put("Class", "" + msg.mClass);
jSONObject.put("Method", "" + msg.mMethod + msg.mMethodSignature);
jSONObject.put("Total", "" + msg.mCount);
jSONObject.put("Description", msg.mDescription);
if (msg.mParams == null){
jSONObject.put("Params", "");
}else{
jSONObject.put("Params", msg.mParams.toString());
}
jSONObject.put("Return", msg.mReturn);
jSONObject.put("StackTrace", msg.mStackTrace);
MsgClient.getInstance().send(jSONObject.toString());
} catch (Throwable th){
YLog.error(th.toString());
}
}
}
Hook信息转发的客户端
public class MsgClient {
public static MsgClient client;
public static boolean isInit = false;
public Socket mSocket = null;
public OutputStream mOutputStream = null;
public LinkedList mMsg = new LinkedList(); //消息队列
public MsgClient() {
try{
mSocket = new Socket(getIP(), 33188);
mOutputStream = mSocket.getOutputStream();
loop();
isInit = true;
YLog.info(">> Socket客户端启动成功! <<");
} catch (Throwable th){
isInit = false;
YLog.error(th.toString());
}
}
public String getIP(){
try{
Class methodClass = Class.forName("android.app.ActivityThread");
Method currentApplication = methodClass.getDeclaredMethod("currentApplication");
currentApplication.setAccessible(true);
Application application = (Application) currentApplication.invoke(null);
WifiManager wifiManager = (WifiManager)application.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
String localIP = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
(ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
return localIP;
} catch (Throwable th){
YLog.error(th.toString());
}
return null;
}
public void loop(){
new Thread(){
public void run(){
try{
PrintWriter pw = new PrintWriter(mOutputStream);
while (true){
String data = "";
synchronized(mMsg) {
if (mMsg.isEmpty()){
continue;
}else{
data = (String)mMsg.poll();
}
}
pw.write(data);
pw.flush();
}
} catch (Throwable th){
YLog.error(th.toString());
}
}
}.start();
}
public static MsgClient getInstance() {
if (!isInit) {
synchronized (MsgClient.class) {
if (!isInit) {
client = new MsgClient();
}
}
}
return client;
}
public void send(@NonNull String data){
if (isInit){
synchronized(mMsg) {
mMsg.add(data);
}
}
}
}
服务端
public class NanoServer extends EdNanoHTTPD {
private Context mContext;
public BufferedInputStream input = null;
public BufferedOutputStream output;
public SocketAddress scaddr;
public ServerSocket serverSocket;
public Socket socket;
public final LinkedList mMsg = new LinkedList(); //消息队列
public NanoServer(Context context) throws IOException {
super(33445);
this.mContext = context;
startSocketThread(context);
start();
YLog.info(">> NanoHTTPD服务端启动成功! <<");
}
public static NanoServer getServer(Context context){
try {
Class clz = Class.forName("android.app.ActivityThread", false, context.getClassLoader());
java.lang.reflect.Method method = clz.getDeclaredMethod("currentProcessName", (Class>[]) new Class[0]);
method.setAccessible(true);
String invoke = (String)method.invoke(null, new Object[0]);
if (invoke instanceof String) {
if(invoke.equals("com.yooha.privacybox64") || invoke.equals("com.yooha.privacybox32")){
return new NanoServer(context);
}
}
} catch (Throwable e) {
}
return null;
}
//创建个socket接受client的消息,然后写进队列
public void startSocketThread(Context context){
new Thread() {
@Override
public void run() {
while (true){
try{
serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
scaddr = new InetSocketAddress(getIP(context), 33188);
try{
serverSocket.bind(scaddr);
YLog.info(">> Socket服务端启动成功! <<");
socket = serverSocket.accept();
}catch (Throwable th){
YLog.err(th.toString());
return;
}
output = new BufferedOutputStream(socket.getOutputStream());
input = new BufferedInputStream(socket.getInputStream());
String data = "";
while (true){
byte[] bBuff = new byte[1024];
data += new String(bBuff, 0, input.read(bBuff, 0, 1024), "utf-8");
if (data.contains("}")){
int index = data.indexOf("}");
String s = new String(data.substring(0, index + 1));
data = data.substring(index + 1);
synchronized (mMsg){
mMsg.add(s);
}
}
}
} catch (Throwable th){
YLog.err(th.toString());
}
}
}
}.start();
}
public String getIP(Context context){
try{
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
String localIP = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
(ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
return localIP;
} catch (Throwable th){
YLog.err(th.toString());
}
return null;
}
@Override
public Response serve(IHTTPSession session) {
String msg = "";
if (Method.GET == session.getMethod()) {
String uri = session.getUri();
if (uri.contains("api/getlog")){
synchronized(mMsg) {
if (!mMsg.isEmpty()){
msg = (String)mMsg.poll();
}
}
}else if (uri.contains("api/connect")){
msg = "connect";
}
}
try {
return newFixedLengthResponse(msg);
} catch (Exception exception) {
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!");
}
}
}
服务端在主体中启动,APP类的attachBaseContext中启动
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
mContext = base!!
AppManager.doAttachBaseContext(base)
startSrver(base)
}
private fun startSrver(context: Context){
if (mServer == null){
mServer = NanoServer.getServer(context);
}
}
以上能保证每个hook进程 能将消息发送给主体中服务端,然后再有主体与PC端通信
Hook TelephonyManager.getDeviceId (示例)
try{
Method method = mCls.getDeclaredMethod("getDeviceId");
Pine.hook(method, "android.telephony.TelephonyManager -> getDeviceId():获取DeviceId", new MethodHook() {
@Override
public void afterCall(Pine.CallFrame callFrame) {
try{
mCountgetDeviceId++;
String ret = (String)callFrame.getResult();
String stack = YLog.getStackTrace();
HookMessage msg = new HookMessage.Builder()
.setClass(mClz)
.setMethod("getDeviceId")
.setMethodSignature("()")
.setReturn(String.valueOf(ret))
.setStackTrace(stack)
.setCount(mCountgetDeviceId)
.setDescription("获取设备ID")
.build();
MsgDispatcher.put(msg);
} catch (Throwable th){
YLog.error(th.toString());
}
}
});
} catch (Throwable thr){
YLog.error(thr.toString());
}
跑一下看看效果:
二、设置一个frida开关,集成frida hook能力,在非root机也能使用frida
先加一个swith控件
依然是在BActivityThread类的loadXposed函数中添加注入点
public void loadXposed(Context context) {
String vPackageName = getAppPackageName();
String vProcessName = getAppProcessName();
YLog.info("loadXposed start process -> " + vProcessName);
if (!TextUtils.isEmpty(vPackageName) && !TextUtils.isEmpty(vProcessName)) {
if (BXposedManager.get().isFridaEnable()){
// TODO -> load frida-gumjs (version=16.1.2)
try{
GumManager.loadGumEngine();
} catch (Throwable th) {
}
}
........
public static void loadGumEngine(){
System.loadLibrary("Yooha");
}
接下来去github上下载frida-gumjs的静态库和头文件
编写代码加载js引擎
UNEXPORT char *readfile(const char *filepath) {
FILE *file = fopen(filepath, "r");
if (file == NULL) {
LOGE("file open failed : %s " ,filepath);
return NULL;
}
LOGI("file open success : %s", filepath);
struct stat statbuf{};
stat(filepath, &statbuf);
int filesize = statbuf.st_size;
void *buffer = malloc(filesize + 1);
memset(buffer, 0, filesize + 1);
int count = 0;
int total = 0;
while ((count = fread((char *) buffer + total, sizeof(char), 1024, file)) != 0) {
total += count;
}
if (file != NULL) {
fclose(file);
}
return (char *) buffer;
}
UNEXPORT static void on_message(const gchar *message,
GBytes *data, gpointer user_data) {
JsonParser *parser;
JsonObject *root;
const gchar *type;
parser = json_parser_new();
json_parser_load_from_data(parser, message, -1, NULL);
root = json_node_get_object(json_parser_get_root(parser));
type = json_object_get_string_member(root, "type");
if (strcmp(type, "log") == 0) {
const gchar *log_message;
log_message = json_object_get_string_member(root, "payload");
FLOG ("[+] log : %s ", log_message);
} else {
FLOG ("[-] %s ", message);
}
g_object_unref(parser);
}
UNEXPORT int hookFunc(const char *scriptpath) {
gum_init_embedded();
backend = gum_script_backend_obtain_qjs();
char *js = readfile(scriptpath);
if (!js) {
return 1;
}
script = gum_script_backend_create_sync(backend, "example", js, NULL, cancellable, &error);
g_assert (error == NULL);
gum_script_set_message_handler(script, on_message, NULL, NULL);
gum_script_load_sync(script, cancellable);
context = g_main_context_get_thread_default();
while (g_main_context_pending(context))
g_main_context_iteration(context, FALSE);
pthread_mutex_lock(&mtx);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);
loop = g_main_loop_new(g_main_context_get_thread_default(), FALSE);
g_main_loop_run(loop);
return 0;
}
UNEXPORT int gumjsHook(const char *scriptpath) {
pthread_t pthread;
int result = pthread_create(&pthread, NULL, (void *(*)(void *)) (hookFunc),
(void *) scriptpath);
struct timeval now;
struct timespec outtime;
pthread_mutex_lock(&mtx);
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + 5;
outtime.tv_nsec = now.tv_usec * 1000;
pthread_cond_timedwait(&cond, &mtx, &outtime);
pthread_mutex_unlock(&mtx);
if (result != 0) {
LOGI("create thread failed");
} else {
LOGE("create thread success");
}
return result;
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
gumjsHook(jspath);
return JNI_VERSION_1_6;
}
写个hook脚本,将hook脚本push到/data/local/tmp/
function main(){
hook_open()
Java.perform(function () {
Java.use("android.app.Activity").startActivity.overload('android.content.Intent').implementation=function(p1){
console.log("startActivity = ", p1);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
this.startActivity(p1);
}
})
}
function hook_open(){
var open_addr = Module.findExportByName("libc.so", "open");
Interceptor.attach(open_addr,{
onEnter:function(args){
var path = args[0].readUtf8String();
console.log("open -> ", path);
},onLeave:function(retval){
}
})
}
setImmediate(main)
运行测试效果
hook startActivity
hook open
以上在非root机 红米上测试的