FartExt超進化之奇奇怪怪的新ROM工具MikRom
1
前言
目前這個工具不怎么完善,能湊合使用,剩下的部分使用中再慢慢完善。其中部分代碼我會開源,其實感覺實現的核心并不怎么復雜,算是一個萌新學習定制ROM過程的一個作品。而且還有個調試超級慢的BUG,如果有大佬知道是啥原因,還請指導一下。
感謝:看雪高研班課程、FART脫殼王課程、珍惜大佬的android進階課程
2
FartExt
詳細的介紹請看:FartExt之優化更深主動調用的FART10(https://bbs.pediy.com/thread-268760.htm)
FartExt是我之前學習脫殼實踐時做的一個自動脫殼機,是基于FART的主動調用思想實現對特定的抽取殼進行優化處理的工具。由于原本的FART沒有配置相關的,所以我增加了配置對指定app脫殼。
大致就是對FART的簡單優化。由于感覺當時做的功能并不怎么完善,所以只是短暫的放了下載地址,就刪掉了。不知道有沒有人實際使用。使用的效果到底咋樣。現在放出開源代碼。
github:FartExt(https://github.com/dqzg12300/FartExt)
3
MikRom
首先回顧一下當時FartExt文章最后的思考部分:
整個流程梳理完成后,我們可以由此來借鑒來思考延伸一下。
比如,包裝一些屬于自己的系統層api調用。便于我們使用xposed或者是frida來調用一些功能。
再比如,加載應用時,讀取配置文件作為開關,我們來對網絡流量進行攔截寫入保存,或者對所有的jni函數調用,或者是java函數調用進行trace。這種就屬于是rom級別的打樁。
再比如,可以做一個應用來讀寫作為開關的配置文件,而rom讀取配置文件后,對一些流程進行調整。例如控制FART是否使用更深調用。控制是否開啟rom級別的打樁。
以上純屬個人瞎想。剛剛入門,想的有點多,以后了解更深了,我再看看如何定制一個專屬的rom逆向集合。
MikRom就是當時個人瞎想的成果,做一個Rom層面的逆向工具,為我們提供比較常用的插樁、注入、脫殼等一系列功能。下面列上目前MikRom中包含的功能
內核修改過反調試
開啟硬件斷點
USB調試默認連接
脫殼(黑名單、白名單過濾、更深的主動調用鏈)
ROM打樁(ArtMethod調用、RegisterNative調用、JNI函數調用)
frida持久化(支持listen,wait,script三種模式)
支持自行切換frida-gadget版本
反調試(通過sleep目標函數,再附加進程來過掉起始的反調試)
trace java函數(smali指令的trace)
內置dobby注入
注入so
注入dex(實現對應的接口觸發調用。目前還未測試)
github:MikRom(https://github.com/dqzg12300/MikRom)
4
MikManager
由于功能做的更加復雜了,我們自行編輯配置文件不再那么方便了,所以我特地做了一個界面化的工具來操作。最后將我們的設置保存到文件,然后MikRom會在打開app時讀取文件,解析后做對應的操作。MikManager就是用來管理這個配置的。
界面較為簡陋,如果對MikRom感興趣的,但是感覺我的界面太丑,也可以自己編寫一個界面管理工具。我正向開發比較渣,所以代碼較為粗糙。不過目前使用沒啥問題。
github:MikManager(https://github.com/dqzg12300/MikManager)
5
開發的始末
雖然功能不是很多,但是做這個工具卻折騰了我很長的時間,這里我將記錄下這樣一個簡陋的Rom工具開發的歷程,由于并不是很清楚其他大佬是如何處理的,所以有些功能都是我不斷的試錯中找到的方案。在試錯的過程中,走了不少彎路,希望能幫到一些小伙伴。
如果我采用的方案犯了什么比較低級的錯誤,還請大佬能指點一下。另外有大佬說,做越簡單化的工具,越危險。如果有什么和諧的風險或者是法律的問題,還請聯系我進行修改。
6
編譯版本
早先FartExt我采用的是aosp10r2的源碼進行修改編譯。后來有同學覺得界面太簡陋了,看起來就像山寨系統。確實如此,然后我就參考了hanbingle老師在Fart脫殼王使用的rom,選擇了PixelExperience來進行修改。編譯的marlin版本,測試手機是pixel XL。
參考文章:https://blog.csdn.net/weixin_42443075/article/details/118084535
上面是我當時編譯參考的文章。另外在編譯PixelExperience時編譯碰到錯誤,需要修改build/blueprint/Blueprints。
bootstrap_go_package { name: "blueprint-pathtools", pkgPath: "github.com/google/blueprint/pathtools", deps: [ "blueprint-deptools", ], srcs: [ "pathtools/lists.go", "pathtools/fs.go", "pathtools/glob.go", ], testSrcs: [ //修改處,這里的內容刪掉 ],}
另外一個問題就是這個rom居然沒法在mac進行編譯,最后沒辦法只能用ubuntu來開發了。
7
配置管理優化
最早先在FartExt中的配置是我們自己在/data/local/tmp/中寫入一個fext.config的文件,然后在應用啟動過程中的handleBindApplication調用時,解析這個配置文件,來決定是否要脫殼。
而現在配置更加復雜了,我們需要使用一個app來對配置進行管理生成,而app沒有對/data/local/tmp寫入文件的權限。所以我們這里就有了一個簡單的需求,要將配置文件落地到一個所有應用都有權限訪問的地方。
有人就要說了,我們可以落地到sdcard,是的,早期我就是這么干的。配置文件落地到sdcard后,所有的app要使用功能,就必須先打開sdcard,并且,由于我使用的rom版本是安卓10的,而安卓10中,你想要直接訪問sdcard的任意目錄,是需要設置requestLegacyExternalStorage="true"。
所以這就導致了,即使我們不嫌麻煩,每個想處理的app都打開sdcard,也會出現有些app無法訪問到配置文件。
在這里我使用的方案是,創建一個系統服務,這個系統服務提供一個讀和寫的函數,然后通過調用系統服務將配置文件落地到/data/system/目錄中,然后每個應用打開時再通過這個系統服務來讀取配置。
8
添加系統服務
這個系統服務目前主要就是為配置管理提供讀寫權限。可能這樣干會有些漏洞的問題,不過我這個rom本身就是逆向使用的工具,而面向正常用戶,所以就暫時不考慮漏洞問題了。
參考文章:Android AOSP 添加系統服務【aidl接口服務】Java層(https://bbs.pediy.com/thread-260472.htm)
參考文章:android 10 添加系統服務步驟(https://blog.csdn.net/a546036242/article/details/118221349)
下面貼上我定義的系統服務
interface IMikRom{ //讀取文件 String readFile(String path); //寫入文件 void writeFile(String path,String data); //執行shell命令 String shellExec(String cmd);}
至于詳細的讀寫方法我就不貼了,就是和android正常的讀寫文件處理一樣。我就說一下在這里我碰到的坑,我按照參考文章一樣的方式做完之后,發現無法找到服務,但是service list|grep mikrom是可以匹配到我自己定義的服務的。
最后經過排查日志發現selinux提示缺少了find權限,于是我修改文件system/sepolicy/public/untrusted_app.te,內容如下:
type untrusted_app, domain;type untrusted_app_27, domain;type untrusted_app_25, domain;allow untrusted_app mikrom_service:service_manager find;allow untrusted_app_27 mikrom_service:service_manager find;allow untrusted_app_25 mikrom_service:service_manager find;
最后成功找到服務,將配置文件寫入到了/data/system/目錄中。
這里我只簡單的貼一下相關。
MikManager的寫入的相關代碼:
//獲取服務public class ServiceUtils { private static IMikRom iMikRom = null; public static IMikRom getiMikRom() { if (iMikRom == null) { try { Class localClass = Class.forName("android.os.ServiceManager"); Method getService = localClass.getMethod("getService", new Class[] {String.class}); if(getService != null) { Object objResult = getService.invoke(localClass, new Object[]{"mikrom"}); if (objResult != null) { IBinder binder = (IBinder) objResult; iMikRom = IMikRom.Stub.asInterface(binder); } } } catch (Exception e) { Log.d("MikManager",e.getMessage()); e.printStackTrace(); } } return iMikRom; }}//將json數據保存到指定路徑public static void SaveMikromConfig(List packageList){ Log.e(ConfigUtil.TAG,"SaveMikromConfig"); Gson gson = new Gson(); String savejson=gson.toJson(packageList); try { ServiceUtils.getiMikRom().writeFile(ConfigUtil.configPath,savejson); } catch (RemoteException e) { Log.e(ConfigUtil.TAG,"writeConfig err:"+e.getMessage()); }}
MikRom中讀取數據:
private static IMikRom iMikRom=null;public static IMikRom getiMikRom() { if (iMikRom == null) { try { Class localClass = Class.forName("android.os.ServiceManager"); Method getService = localClass.getMethod("getService", new Class[] {String.class}); if(getService != null) { Object objResult = getService.invoke(localClass, new Object[]{"mikrom"}); if (objResult != null) { IBinder binder = (IBinder) objResult; iMikRom = IMikRom.Stub.asInterface(binder); } } } catch (Exception e) { Log.d("MikManager",e.getMessage()); e.printStackTrace(); } } return iMikRom;} public static String getMikConfig(){ try { IMikRom mikrom=getiMikRom(); if(mikrom==null){ return ""; } return mikrom.readFile("/data/system/mik.conf"); } catch (RemoteException e) { e.printStackTrace(); } return "";}
測試的配置文件內容如下:
[{"appName":"crackme","breakClass":"","dexClassName":"","dexPath":"","enabled":true,"fridaJsPath":"","gadgetArm64Path":"","gadgetPath":"","isDeep":false,"isDobby":false,"isInvokePrint":false,"isJNIMethodPrint":true,"isRegisterNativePrint":false,"isTuoke":false,"packageName":"com.kanxue.crackme","port":0,"sleepNativeMethod":"","soPath":"","traceMethod":"","whiteClass":"","whitePath":""}]
到這里第一步的優化就完成了,讀寫配置成功脫離了對sdcard的權限依賴,以及能全局應用都可以訪問到。具體的詳細相關代碼可參考我放出的部分源碼。
9
脫殼相關優化
由于我走向了另外一條偏向更易于使用的優化方向,所以關于脫殼的關鍵部分我并沒有做什么優化,所以如果脫殼需求較大的朋友可以考慮看看hanbingle大佬的脫殼王是否能滿足需求。
我對脫殼的優化主要分為兩點,由于FartExt當時的脫殼結果保存是在sdcard中,所以對于權限有一定依賴,所以我優化掉了這塊的依賴,讓我們不用再手動開啟sdcard權限,也能保存下脫殼結果。
另外一點就是當時如果是抽取殼的情況,我們需要拿出兩個文件來手動修復一下。我這里也優化了一下,會直接將函數執行完的dex重新再保存一份,這樣就無需我們再手動修復了。當然同時也保留了原本的做法,仍然保存大佬說的幾個重要元素。
優化1方案:解決sdcard權限問題
我嘗試了很多種辦法來避免我們手動開啟sdcard權限的情況下保存脫殼結果文件。最后測試成功了兩種辦法。這兩種辦法我都簡單說下。
第一種辦法,我們直接將數據可以寫入應用本身的內部空間中。也就是/data/data/中。但是這樣有權限寫入,但是沒有root的話就無法訪問到保存的結果了,當然沒有root的情況也是能訪問到應用內部數據的。我們可以通過命令run-as 來直接進入應用數據內部。但是也有人會問,如果對方應用沒有開啟debuggable,不就沒辦法使用run-as了嗎?
這就是改Rom的優勢所在了。我們可以直接修改PackageParser中的函數parseBaseApplication。在里面為我們默認打開debuggable即可。這樣即使對方沒有設置,在加載xml的時候,也會打開這個功能。
第二種辦法,既然第一個辦法最后可以直接改xml為我們默認打開debuggable,那么我們解決sdcard權限,也可以修改PackageParser中的函數parseBaseApplication來直接打開sdcard權限。
不過由于安卓10的特殊性,即使打開了sdcard權限,也只能在sdcard中自己的目錄寫入數據,所以使用這個辦法時,脫殼結果應保存在/sdcard/Android/data//目錄中。
下面貼上相關的代碼,其中包含了兩種解決方案:
//這里我判斷如果非系統應用才增加這些權限if((ai.flags&ApplicationInfo.FLAG_SYSTEM)!=1){ //試錯中發現,開啟這些權限的時候,有個google和message相關的進程會崩潰,所以過濾一下 if(!ai.packageName.contains("google") && !ai.packageName.contains("message")){ //下面是sdcard權限開啟,以及debuggable權限開啟。如果擔心檢測,可以關掉debuggable的默認開啟 ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE; ai.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE; // Debuggable implies profileable ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL; } }
優化2方案:解決抽取殼在脫殼完成后還需要手動修復
在優化這個問題前,首先要意識到為什么會需要手動修復,當我們理解了大佬的處理之后,就發現hanbingle大佬為了避免每個函數主動調用都將dex給保存,所以只有文件不存在的時候才保存。也就意味著我們保存的dex是第一個主動調用執行時的dex。
如果這個抽取殼是必須函數執行后才會恢復的,那么后面的函數在這個保存dex中都依然是被抽取的。FART的做法是將codeitem保存出來后,然后再修復。所以我將這里優化了一下。
知道問題所在后,優化的思路就清晰了,我采用了比較簡單的一種優化方式,就是每個dex文件保存時,將這個dex的地址以及長度給保存下來。最后在所有主動調用完成時,重新將所有dex文件再保存一次。下面看看優化后的相關代碼:
//存放dex的指針和長度static std::map<void*,size_t> dex_map; //主動調用函數的dump處理extern "C" void dumpArtMethod(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) { ... int dexfilefp=open(dexfilepath,O_RDONLY,0666); ///dex文件存在則不處理,避免主動調用每次都要重新保存dex if(dexfilefp>0){ close(dexfilefp); dexfilefp=0; }else{ LOG(ERROR) << "mikrom ArtMethod::dumpdexfilebyArtMethod save dex_map"; //將這個地址給保存下來 dex_map.insert(std::pair<void*,size_t>((void*)begin_,size_)); int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666); ... }}//主動調用完成時,重新保存到文件名_dexfile_repair.dex中extern "C" void dumpDexOver() REQUIRES_SHARED(Locks::mutator_lock_) { if(dex_map.size()<=0){ LOG(ERROR) << "mikrom dumpDexOver dex_map.size()<=0"; return; } char *dexfilepath=(char*)malloc(sizeof(char)*1000); LOG(ERROR) << "mikrom ArtMethod::dumpDexOver"; int result=0; char* packageName=ArtMethod::GetPackageName(); std::map<void*, size_t>::iterator iter; for(iter = dex_map.begin(); iter != dex_map.end(); iter++) { void* begin_=iter->first; size_t size_=iter->second; int size_int_=(int)size_; memset(dexfilepath,0,1000); sprintf(dexfilepath,"/sdcard/Android/data/%s/files/dump",packageName); mkdir(dexfilepath,0777); memset(dexfilepath,0,1000); sprintf(dexfilepath,"/sdcard/Android/data/%s/files/dump/%d_dexfile_repair.dex",packageName,size_int_); int dexfilefp=open(dexfilepath,O_RDONLY,0666); if(dexfilefp>0){ close(dexfilefp); dexfilefp=0; }else{ int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666); if(fp>0) { result=write(fp,(void*)begin_,size_); if(result<0) { LOG(ERROR) << "mikrom ArtMethod::dumpDexOver,open dexfilepath error"; } fsync(fp); close(fp); memset(dexfilepath,0,1000); } } } if(dexfilepath!=nullptr) { free(dexfilepath); dexfilepath=nullptr; }}
最后生成的_repair.dex文件就是無需再修復的了,如果想要手動修復按照原來的方式也可以。當然,如果主動調用未能成功跑完,這個repair文件也是不會生成的。
10
新增功能
1、修改內核反調試
關于修改內核反調試已經有相當多的文章講解了,我這里也是和他們用的同一個方案,基本都是根據正向檢測調試的方式,來進行反調試,當然如果有人用其他方式來判斷是否被調試,這個反調試就無效了。詳細的過反調試原理可以看參考文章。
參考文章:修改內核源碼繞過反調試檢測(Android10)(https://blog.csdn.net/u011426115/article/details/113144592)
雖然我改了。但是沒測試效果咋樣。修改的代碼如下,kernel/google/marlin/fs/proc/array.c
static const char * const task_state_array[] = { "R (running)", /* 0 */ "S (sleeping)", /* 1 */ "D (disk sleep)", /* 2 */ "S (sleeping)", /* 4 */ //這里之前是 "T (stopped)", "S (sleeping)", /* 8 */ //這里之前是 "t (tracing stop)" "X (dead)", /* 16 */ "Z (zombie)", /* 32 */}; static inline void task_state(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *p){ struct user_namespace *user_ns = seq_user_ns(m); struct group_info *group_info; int g; struct fdtable *fdt = NULL; const struct cred *cred; pid_t ppid = 0, tpid = 0; struct task_struct *leader = NULL; rcu_read_lock(); if (pid_alive(p)) { struct task_struct *tracer = ptrace_parent(p); if (tracer) tpid = task_pid_nr_ns(tracer, ns); ppid = task_tgid_nr_ns(rcu_dereference(p->real_parent), ns); leader = p->group_leader; } //這個直接強制改tpid為0。 tpid=0; cred = get_task_cred(p); ...}
當對方檢測/proc//status文件時,獲取到的TracerPid就會一直是0,并且應用的狀態不會出現stopped和tracing stop。下面是status的部分內容:
Name: .kanxue.crackmeState: S (sleeping)Tgid: 12142Pid: 12142PPid: 545TracerPid: 0Uid: 10027 10027 10027 10027Gid: 10027 10027 10027 10027Ngid: 0FDSize: 128Groups: 9997 20027 50027VmPeak: 5657524 kBVmSize: 5210040 kB
還有一處修改文件是kernel/google/marlin/fs/proc/base.c
static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *task){ unsigned long wchan; char symname[KSYM_NAME_LEN]; wchan = get_wchan(task); if (lookup_symbol_name(wchan, symname) < 0) if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) return 0; else return seq_printf(m, "%lu", wchan); else if(strstr(symname,"trace")){ //這里是新增的,如果符號名稱包含trace,就固定改成sys_epoll_wait return seq_printf(m,"%s","SyS_epoll_wait"); } return seq_printf(m, "%s", symname);}
2、sleep反調試
通過sleep的反調試方式是在看雪高研班中學習到的,據說這個辦法可以過掉一些在前期檢測的native的反調試。
思路就是需要調試的JNI函數執行前會調用JniMethodStart,那么我們只要在這里進行判斷,如果這個函數是目標函數,就sleep睡眠若干秒。在睡眠期間,我們再調試附加上即可。相關代碼如下:
extern uint32_t JniMethodStart(Thread* self) { JNIEnvExt* env = self->GetJniEnv(); DCHECK(env != nullptr); uint32_t saved_local_ref_cookie = bit_cast(env->GetLocalRefCookie()); env->SetLocalRefCookie(env->GetLocalsSegmentState()); ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); // TODO: Introduce special entrypoint for synchronized @FastNative methods? // Or ban synchronized @FastNative outright to avoid the extra check here? DCHECK(!native_method->IsFastNative() || native_method->IsSynchronized()); const char* methodname=native_method->PrettyMethod().c_str(); if(ArtMethod::GetDebugMethod()!=nullptr && strlen(ArtMethod::GetDebugMethod())>0){ if(strstr(methodname,ArtMethod::GetDebugMethod())!=nullptr){ std::ostringstream oss1; oss1<< "fartext JniMethodStart methodname:"<" wait debug sleep 60..."; LOG(ERROR)< sleep(60); } } if (!native_method->IsFastNative()) { // When not fast JNI we transition out of runnable. self->TransitionFromRunnableToSuspended(kNative); } return saved_local_ref_cookie;}
3、ROM級打樁
這個其實沒什么難的,在關鍵的函數位置直接打印即可。我只講一下jni的打樁部分,我查資料的時候看到了兩種做法,第一種是打印jni部分是在jni函數中找比較通用的調用函數。
例如InvokeVirtualOrInterfaceWithJValues、InvokeWithVarArgs、InvokeWithJValues,然后在里面加上打印。第二種就是像jnitracer一樣,通過偏移,找到所有的函數指針,然后再包裝處理,比如加一個外層函數調用。
由于我c++非常爛,所以我采用最笨的方法,我寫了幾個較為通用的打印函數,jni_internal.cc中的所有我關心的jni函數,就調用各自對應的打印函數。下面貼上我的例子。
void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, const char* jniMethod, jmethodID mid, va_list args){ if(ArtMethod::IsJNIMethodPrint()){ ArtMethod* method = jni::DecodeArtMethod(mid); uint32_t shorty_len = 0; const char* shorty = method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len); JValue result; ArgArray arg_array(shorty, shorty_len); arg_array.VarArgsShowArg(soa, args,jniMethod,method->PrettyMethod().c_str()); }}void ShowJValue(const ScopedObjectAccessAlreadyRunnable& soa, const char* jniMethod, jmethodID mid, const jvalue* args){ if(ArtMethod::IsJNIMethodPrint()){ ArtMethod* method = jni::DecodeArtMethod(mid); uint32_t shorty_len = 0; const char* shorty = method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len); JValue result; ArgArray arg_array(shorty, shorty_len); arg_array.JValuesShowArg(soa, args,jniMethod,method->PrettyMethod().c_str()); }} void ShowJniField(const char* jniMethodName,jfieldID field) { if(ArtMethod::IsJNIMethodPrint()){ ArtField* f = jni::DecodeArtField(field); std::ostringstream oss; oss << "mikrom jni "<"\t"< LOG(ERROR)< } } void ShowArgStr(const char* jniMethod,const char* str1,const char* str2){ if(ArtMethod::IsJNIMethodPrint()){ std::ostringstream oss; if(str1!=nullptr && str2!=nullptr){ oss << "mikrom jni "<"\t"<"\t"< }else if(str1!=nullptr ){ oss << "mikrom jni "<"\t"< }else{ oss << "mikrom jni "< } LOG(ERROR)< }}
處理完判斷部分,接下來就是對于參數的打印部分了,這里我只處理了下將字符串參數打印出來,其他特殊的參數我就沒有處理了。
void VarArgsShowArg(const ScopedObjectAccessAlreadyRunnable& soa, va_list ap,const char* jniMethodName,const char* methodName) REQUIRES_SHARED(Locks::mutator_lock_) { std::stringstream ss; ss << "mikrom jni "<"\t"<"\t"; for (size_t i = 1; i < shorty_len_; ++i) { switch (shorty_[i]) { case 'Z': case 'B': case 'C': case 'S': case 'I': ss<<"arg"<":"<"\t"; break; case 'F': ss<<"arg"<":"<"\t"; break; case 'L':{ jobject obj=va_arg(ap, jobject); ObjPtr receiver =soa.Decode(obj); if(receiver==nullptr){ LOG(ERROR)<<"mikrom "<" receiver =nullptr"; break; } ObjPtr cls=receiver->GetClass(); if (cls->DescriptorEquals("Ljava/lang/String;")){ ObjPtr argStr =soa.Decode(obj); ss<<"arg"<":"<<(const char*)argStr->GetValue(); }else{ ss<<"arg"<":"<"\t"; } break; } case 'D': ss<<"arg"<":"<"\t"; break; case 'J': ss<<"arg"<":"<"\t"; break; } } LOG(ERROR) << ss.str(); } void JValuesShowArg(const ScopedObjectAccessAlreadyRunnable& soa, const jvalue* args,const char * jniMethodName,const char* methodName) REQUIRES_SHARED(Locks::mutator_lock_) { std::stringstream ss; ss << "mikrom jni "<"\t"<"\t"; for (size_t i = 1, args_offset = 0; i < shorty_len_; ++i, ++args_offset) { switch (shorty_[i]) { case 'Z': ss<<"arg"<":"<"\t"; break; case 'B': ss<<"arg"<":"<"\t"; break; case 'C': ss<<"arg"<":"<"\t"; break; case 'S': ss<<"arg"<":"<"\t"; break; case 'I': FALLTHROUGH_INTENDED; case 'F': ss<<"arg"<":"<"\t"; break; case 'L':{ jobject obj=args[args_offset].l; ObjPtr receiver =soa.Decode(obj); if(receiver==nullptr){ LOG(ERROR)<<"mikrom "<" receiver =nullptr"; break; } ObjPtr cls=receiver->GetClass(); if (cls->DescriptorEquals("Ljava/lang/String;")){ ObjPtr argStr =soa.Decode(obj); ss<<"arg"<":"<<(const char*)argStr->GetValue(); }else{ ss<<"arg"<":"<"\t"; } break; } case 'D': FALLTHROUGH_INTENDED; case 'J': ss<<"arg"<":"<"\t"; break; } } LOG(ERROR) << ss.str(); }
搞定了封裝部分,就看看調用部分是怎么使用的:
static jdouble CallDoubleMethodA(JNIEnv* env, jobject obj, jmethodID mid, const jvalue* args) { CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(obj); CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(mid); ScopedObjectAccess soa(env); //add ShowJValue(soa,__FUNCTION__,mid,args); //add end return InvokeVirtualOrInterfaceWithJValues(soa, obj, mid, args).GetD();} static jfloat CallFloatMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) { va_list ap; va_start(ap, mid); ScopedVAArgs free_args_later(&ap); CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(obj); CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(mid); ScopedObjectAccess soa(env); //add ShowVarArgs(soa,__FUNCTION__,mid,ap); //add end JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap)); return result.GetF();}
到這里就搞定了jni的打印以及參數的打印,大多數調用我都打印了,由于我實戰的少,所以可能會漏掉一些常用的打印,如果有發現什么比較有用的打印,也可以告訴我優化下。
4、frida-gadget持久化
這個frida持久化的問題,論壇里面也發過一遍又一遍了。我也是參考他們的文章,然后測試優化。先貼上參考文章。
參考文章:ubuntu 20.04系統AOSP(Android 11)集成Frida(https://www.mobibrw.com/2021/28588)
參考文章:從0開始實現一個簡易的主動調用框架(https://bbs.pediy.com/thread-270028.htm)
不過我的加載時機和他的不太一樣,并且我擴展了可自行切換frida-gadget的版本,默認集成到系統中的版本是15.1,如果你喜歡用14.2的版本,可以自己上傳到手機進行切換,如果你想不root的情況下,也可以通過MikManager設置切換的版本。下面貼上加載的部分代碼。
public static void loadGadget(){ String processName = ActivityThread.currentProcessName(); for(PackageItem item : mikConfigs){ if(item.packageName.equals(processName)){ try{ boolean flag=true; if(item.fridaJsPath.length()<=0){ continue; } String configPath="/data/data/"+processName+"/libfdgg.config.so"; String configPath2="/data/data/"+processName+"/libfdgg32.config.so"; if(item.fridaJsPath.equals("listen")||item.fridaJsPath.equals("listen_wait")){ WriteConfig(configPath,item.fridaJsPath,item.port); WriteConfig(configPath2,item.fridaJsPath,item.port); }else{ File file = new File(item.fridaJsPath); if(!file.exists()){ file = new File( "/data/data/" + processName +"/"+file.getName()); } if(!file.exists()){ Log.e("mikrom", "initConfig package:" + processName+" frida js path:"+item.fridaJsPath+" not found"); continue; } WriteConfig(configPath,item.fridaJsPath,item.port); WriteConfig(configPath2,item.fridaJsPath,item.port); } String tagPath = "/data/data/" + processName + "/libfdgg.so";//64位so的目錄 String tagPath2 = "/data/data/" + processName + "/libfdgg32.so";//32位的so目錄 //使用用戶自己設置的gadget路徑 if(item.gadgetPath!=null&&item.gadgetPath.length()>0){ mycopy(item.gadgetArm64Path, tagPath);//復制so到私有目錄 mycopy(item.gadgetPath, tagPath2); }else{ mycopy("/system/lib64/libfdgg.so", tagPath);//復制so到私有目錄 mycopy("/system/lib/libfdgg.so", tagPath2); } int perm = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO; FileUtils.setPermissions(tagPath, perm, -1, -1);//將權限改為777 FileUtils.setPermissions(tagPath2, perm, -1, -1); FileUtils.setPermissions(configPath, perm, -1, -1); FileUtils.setPermissions(configPath2, perm, -1, -1); File file1 = new File(tagPath); File file2 = new File(tagPath2); if (file1.exists()) { Log.e("mikrom", "app: " +System.getProperty("os.arch"));//判斷是64位還是32位 if (System.getProperty("os.arch").indexOf("64") >= 0) { Log.e("mikrom", "initConfig package:" + processName+" frida js path:"+item.fridaJsPath+" load arch64"); System.load(tagPath); file1.delete();//用完就刪否則不會更新 } else { Log.e("mikrom", "initConfig package:" + processName+" frida js path:"+item.fridaJsPath+" load 32"); System.load(tagPath2); file2.delete(); } } Log.e("mikrom", "initConfig package:" + processName+" initConfig over"); }catch(Exception ex){ Log.e("mikrom", "initConfig package:" + processName+" frida js path:"+item.fridaJsPath+" load err:"+ex.getMessage()); } break; } } }
同時也是支持以三種模式加載gadget的,比如默認加載腳本,或者監聽模式,也就是可以frida連接上去,或者是監聽并阻塞等待。不過我留意到,有些自己寫的簡單app似乎使用gadget注入似乎并不成功。好像需要app對native有什么實現才行。
5、so注入以及dobby的內置
前面gadget的部分實際就是注入so了,但是前面的部分主要是針對gadget的注入相關處理的。我又特地單獨處理了一下對so的注入,如果我們自己寫了一個so想要單獨注入進去也是可以的。
為了方便演示,我特地將dobby內置進去。由于dobby是一個hook框架,并且沒有注入功能,往往都是需要通過其他手段注入到應用中。所以我直接內置進來了。下面貼上相關代碼:
public static void loadSo(String path){ String processName = ActivityThread.currentProcessName(); String fName = path.trim(); String fileName = fName.substring(fName.lastIndexOf("/")+1); String tagPath = "/data/data/" + processName + "/"+fileName;//64位so的目錄 mycopy(path, tagPath); int perm = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO; FileUtils.setPermissions(tagPath, perm, -1, -1);//將權限改為777 File file = new File(tagPath); if (file.exists()){ Log.e("mikrom", "load so:"+tagPath); System.load(tagPath); file.delete();//用完就刪否則不會更新 }} //注入sopublic static void loadConfigSo(){ String processName = ActivityThread.currentProcessName(); for(PackageItem item : mikConfigs){ if(!item.packageName.equals(processName)) continue; if(item.soPath.length()<=0) continue; if(item.isDobby){ if(System.getProperty("os.arch").indexOf("64") >= 0) { loadSo("/system/lib64/libdby_64.so"); }else{ loadSo("/system/lib/libdby.so"); } } String[] soList=item.soPath.split(""); for(String sopath :soList){ loadSo(sopath); } }}
6、smali trace
實際上這個功能在ROM中是有的,就是TraceExecution函數,只是需要控制打開就行了,但是必須走switch解釋器執行才能執行到便于我們修改的地方。
所以這個就有兩個地方需要修改,第一個是判斷打印的部分,第二個是強制以解釋器執行的部分。下面看看我的修改部分:
static inline void TraceExecution(const ShadowFrame& shadow_frame, const Instruction* inst, const uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) { if(shadow_frame.GetMethod()!=nullptr && ArtMethod::GetTraceMethod()!=nullptr&&strlen(ArtMethod::GetTraceMethod())>0){ const char* methodName=shadow_frame.GetMethod()->PrettyMethod().c_str(); if(strstr(methodName,ArtMethod::GetTraceMethod())) { std::ostringstream oss; oss << android::base::StringPrintf("mikrom smaliTrace 0x%x: ", dex_pc) << inst->DumpString(shadow_frame.GetMethod()->GetDexFile()) << "\t//"; for (uint32_t i = 0; i < shadow_frame.NumberOfVRegs(); ++i) { uint32_t raw_value = shadow_frame.GetVReg(i); ObjPtr ref_value = shadow_frame.GetVRegReference(i); oss << android::base::StringPrintf(" vreg%u=0x%08X", i, raw_value); if (ref_value != nullptr) { if (ref_value->GetClass()->IsStringClass() && !ref_value->AsString()->IsValueNull()) { oss << "/java.lang.String \"" << ref_value->AsString()->ToModifiedUtf8() << "\""; } else { oss << "/" << ref_value->PrettyTypeOf(); } } } LOG(ERROR)< } }}
另外就是強制以解釋器執行的部分:
static inline JValue Execute( Thread* self, const CodeItemDataAccessor& accessor, ShadowFrame& shadow_frame, JValue result_register, bool stay_in_interpreter = false, bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!shadow_frame.GetMethod()->IsAbstract()); DCHECK(!shadow_frame.GetMethod()->IsNative()); ... if(ArtMethod::GetTraceMethod()!=nullptr && strlen(ArtMethod::GetTraceMethod())>0){ if(strstr(method->PrettyMethod().c_str(),ArtMethod::GetTraceMethod())){ std::ostringstream oss; oss<< "mikrom Execute strstr:"<PrettyMethod().c_str()<<"\t"< LOG(ERROR) < return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register,false); } } //add end ...}
除了這里,在LinkerCode的地方也需要修改:
static void LinkCode(ClassLinker* class_linker, ArtMethod* method, const OatFile::OatClass* oat_class, uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) { ... const void* quick_code = method->GetEntryPointFromQuickCompiledCode(); bool enter_interpreter = class_linker->ShouldUseInterpreterEntrypoint(method, quick_code); if(strlen(ArtMethod::GetTraceMethod())>0){ std::ostringstream oss; oss<<"mikrom LinkCode method:"<PrettyMethod().c_str(); LOG(ERROR)< enter_interpreter=true; } ...}
變量enter_interpreter決定了是否使用解釋器執行,如果是quick快速模式執行,將無法執行到我們修改的函數。最后測試發現,函數第一次調用的時候,是走的解釋執行,成功打印出trace記錄。
第二次調用時,又被優化到快速模式執行了。不過我們已經拿到想要的結果了,我就不繼續優化了。關于LinkCode這里的修改如果不理解的,可以看看下面的參考文章,里面有詳細的流程圖了。我就不重復了。
參考文章:將FART和Youpk結合來做一次針對函數抽取殼的全面提升(https://bbs.pediy.com/thread-260052.htm)
7、dex注入(未完成)
這個功能我原本是想內置一個IO重定向,或者是內置一個Sandhook,然后直接通過配置勾選就能自動注入使用了。然后我們修改的dex直接注入進去即可使用。不過目前還沒想好怎么設計里面的一些交互部分。所以只是簡單的做了下注入的步驟。后續也不一定有時間繼續實現,先暫時擱置吧。
11
MikManager展示
(圖片太大微信不支持顯示,可點擊文末閱讀原文查看)
12
使用說明
操作起來非常簡單,選擇想要處理的應用,然后選擇對應功能即可。記得勾選完后,回到應用欄目點保存,如果是要使用frida腳本,或者是要選擇so,需要把文件放在sdcard中的對應應用的目錄中。如果sdcard中沒有對應目錄,可以打開一下應用會自動生成的。
脫殼功能的保存結果在/sdcard/Android/data//dump中查看。
查看logcat輸出日志統一搜索mikrom,jni調用的輸出的格式如下:
2022-01-30 19:43:30.198 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni CallNonvirtualVoidMethodV void java.lang.ClassNotFoundException.(java.lang.String, java.lang.Throwable) arg1:android.widget.ViewStubarg2:0x6fcc6f50 2022-01-30 19:43:30.198 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetArrayLength2022-01-30 19:43:30.198 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars android.widget.ViewStub2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni CallNonvirtualVoidMethodV void java.lang.ClassNotFoundException.(java.lang.String, java.lang.Throwable) arg1:android.widget.ViewStubarg2:0x12c2a5e8 2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars android.webkit.ViewStub2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni NewStringUTF android.webkit.ViewStub2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni CallObjectMethodV java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String) arg1:android.webkit.ViewStub2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars android.webkit.ViewStub2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars android.webkit.ViewStub2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni CallNonvirtualVoidMethodV void java.lang.ClassNotFoundException.(java.lang.String, java.lang.Throwable) arg1:android.webkit.ViewStubarg2:0x6fcc6f50 2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetArrayLength
RegisterNative的輸出格式如下:
2022-01-30 19:48:43.219 4576-4576/com.kanxue.crackme E/.kanxue.crackm: mikrom RomPrint RegisterNative name:boolean com.kanxue.crackme.MainActivity.jnicheck(java.lang.String) native_ptr:0x75a5735904 method_idx:5682022-01-30 19:48:43.219 4576-4576/com.kanxue.crackme E/.kanxue.crackm: mikrom RomPrint RegisterNative name:boolean com.kanxue.crackme.MainActivity.crypt2(java.lang.String) native_ptr:0x75a5739750 method_idx:2
ArtInvoke的輸出格式如下:
2022-01-30 19:50:16.247 4686-4686/com.kanxue.crackme E/.kanxue.crackm: mikrom [PerformCall] caller:boolean sun.misc.Unsafe.compareAndSwapInt(java.lang.Object, long, int, int)---called:boolean sun.misc.Unsafe.compareAndSwapInt(java.lang.Object, long, int, int)2022-01-30 19:50:16.247 4686-4686/com.kanxue.crackme E/.kanxue.crackm: mikrom [PerformCall] caller:void android.graphics.Paint.nSetFlags(long, int)---called:void android.graphics.Paint.nSetFlags(long, int)2022-01-30 19:50:16.247 4686-4686/com.kanxue.crackme E/.kanxue.crackm: mikrom [PerformCall] caller:int java.lang.String.hashCode()---called:int java.lang.String.hashCode()2022-01-30 19:50:16.247 4686-4686/com.kanxue.crackme E/.kanxue.crackm: mikrom [PerformCall] caller:void android.graphics.Paint.nSetTextLocalesByMinikinLocaleListId(long, int)---called:void android.graphics.Paint.nSetTextLocalesByMinikinLocaleListId(long, int)2022-01-30 19:50:16.247 4686-4686/com.kanxue.crackme E/.kanxue.crackm: mikrom [PerformCall] caller:void android.graphics.Paint.nSetDither(long, boolean)---called:void android.graphics.Paint.nSetDither(long, boolean)2022-01-30 19:50:16.247 4686-4686/com.kanxue.crackme E/.kanxue.crackm: mikrom [PerformCall] caller:void android.graphics.Paint.nSetDither(long, boolean)---called:void android.graphics.Paint.nSetDither(long, boolean)
smali trace日志的輸出結果如下:
2022-01-30 20:06:11.003 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x0: invoke-static {}, void com.mik.miktest.CommonTools.Test() // method@7 // vreg0=0x00000000 vreg1=0x00000000 vreg2=0x00000000 vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.003 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x3: const-string v0, "MainActivity" // string@30 // vreg0=0x00000000 vreg1=0x00000000 vreg2=0x00000000 vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.008 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x5: const-string v1, "TraceTest" // string@33 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000000 vreg2=0x00000000 vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.008 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x7: invoke-static {v0, v1}, int android.util.Log.i(java.lang.String, java.lang.String) // method@0 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x12C896A8/java.lang.String "TraceTest" vreg2=0x00000000 vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.008 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0xa: add-int v1, v5, v6 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x12C896A8/java.lang.String "TraceTest" vreg2=0x00000000 vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.008 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0xc: new-instance v2, java.lang.StringBuilder // type@TypeIndex[16] // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x00000000 vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0xe: invoke-direct {v2}, void java.lang.StringBuilder.() // method@17 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x11: const-string v3, "TraceTest," // string@34 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x13: invoke-virtual {v2, v3}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) // method@19 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x16: move-result-object v2 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x17: invoke-virtual {v2, v1}, java.lang.StringBuilder java.lang.StringBuilder.append(int) // method@18 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x1a: move-result-object v2 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x1b: invoke-virtual {v2}, java.lang.String java.lang.StringBuilder.toString() // method@20 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x1e: move-result-object v2 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x1f: invoke-static {v0, v2}, int android.util.Log.i(java.lang.String, java.lang.String) // method@0 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C89738/java.lang.String "TraceTest,16" vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x22: mul-int v2, v5, v6 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C89738/java.lang.String "TraceTest,16" vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x24: new-instance v4, java.lang.StringBuilder // type@TypeIndex[16] // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x26: invoke-direct {v4}, void java.lang.StringBuilder.() // method@17 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x29: invoke-virtual {v4, v3}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) // method@19 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x2c: move-result-object v3 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89708/java.lang.String "TraceTest," vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x2d: invoke-virtual {v3, v2}, java.lang.StringBuilder java.lang.StringBuilder.append(int) // method@18 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89758/java.lang.StringBuilder vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.010 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x30: move-result-object v3 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89758/java.lang.StringBuilder vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.011 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x31: invoke-virtual {v3}, java.lang.String java.lang.StringBuilder.toString() // method@20 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89758/java.lang.StringBuilder vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.011 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x34: move-result-object v3 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C89758/java.lang.StringBuilder vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.011 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x35: invoke-static {v0, v3}, int android.util.Log.i(java.lang.String, java.lang.String) // method@0 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C897A8/java.lang.String "TraceTest,15" vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.011 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x38: add-int v0, v1, v2 // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C897A8/java.lang.String "TraceTest,15" vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F2022-01-30 20:06:11.011 5157-5157/com.mik.miktest E/com.mik.miktes: mikrom smaliTrace 0x3a: return v0 // vreg0=0x0000001F vreg1=0x00000010 vreg2=0x0000000F vreg3=0x12C897A8/java.lang.String "TraceTest,15" vreg4=0x12C89758/java.lang.StringBuilder vreg5=0x00000001 vreg6=0x0000000F
so注入如果勾選自動注入dobby后,就會注入系統中自帶的,如果你想注入自己編譯的dobby,也可以在so注入里面導入dobby,注入順序會默認按照導入順序。
13
完結撒花
整理一遍之后,感覺好像做了很多,又感覺好像也沒搞點啥。也碰到很多問題讓我一遍又一遍的編譯測試。不過總算大致實現了當時的想法。后續應該不會有啥升級了。調轉方向研究點其他東西了。
如果以后內核功底深一些了,可能會回頭擴展把。重要的事情再提一次,不知道修改到了art部分的哪個點,導致了調試超級慢,如果有大佬知道啥原因的,麻煩指導一下。