4

[原创]FartExt超进化之奇奇怪怪的新ROM工具MikRom

 2 years ago
source link: https://bbs.pediy.com/thread-271358.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
[原创]FartExt超进化之奇奇怪怪的新ROM工具MikRom
2022-1-31 23:35 15973

​ 一眨眼就到春节了,好久没有写文章了,趁着新年空闲,赶紧把自己折腾了一段时间的东西整理了一下。在折腾的过程中,碰到了不少问题,感谢大佬们的帮助。目前这个工具不算是怎么完善吧,但是感觉能凑合使用了,剩下的部分在使用中再慢慢完善吧,其中部分代码我会开源,其实感觉实现的核心并不怎么复杂。算是一个萌新学习定制ROM过程的一个作品把。而且还有个调试超级慢的BUG,如果有大佬知道是啥原因,还请指导一下。万分感谢。

看雪高研班课程

FART脱壳王课程

珍惜大佬的android进阶课程

FartExt

​ 详细的介绍请看:FartExt之优化更深主动调用的FART10

​ FartExt是我之前学习脱壳实践时做的一个自动脱壳机,是基于FART的主动调用思想实现对特定的抽取壳进行优化处理的工具。由于原本的FART没有配置相关的,所以我增加了配置对指定app脱壳。大致就是对FART的简单优化。由于感觉当时做的功能并不怎么完善,所以只是短暂的放了下载地址,就删掉了。不知道有没有人实际使用。使用的效果到底咋样。现在放出开源代码。

github:FartExt

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注入
  • 注入dex(实现对应的接口触发调用。目前还未测试)

github:MikRom

MikManager

​ 由于功能做的更加复杂了,我们自行编辑配置文件不再那么方便了,所以我特地做了一个界面化的工具来操作。最后将我们的设置保存到文件,然后MikRom会在打开app时读取文件,解析后做对应的操作。MikManager就是用来管理这个配置的。界面较为简陋,如果对MikRom感兴趣的,但是感觉我的界面太丑,也可以自己编写一个界面管理工具。我正向开发比较渣,所以代码较为粗糙。不过目前使用没啥问题。

github:MikManager

开发的始末

​ 虽然功能不是很多,但是做这个工具却折腾了我很长的时间,这里我将记录下这样一个简陋的Rom工具开发的历程,由于并不是很清楚其他大佬是如何处理的,所以有些功能都是我不断的试错中找到的方案。在试错的过程中,走了不少弯路,希望能帮到一些小伙伴。如果我采用的方案犯了什么比较低级的错误,还请大佬能指点一下。另外有大佬说,做越简单化的工具,越危险。如果有什么和谐的风险或者是法律的问题,还请联系我进行修改。

​ 早先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来开发了。

配置管理优化

​ 最早先在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/目录中,然后每个应用打开时再通过这个系统服务来读取配置。

添加系统服务

​ 这个系统服务目前主要就是为配置管理提供读写权限。可能这样干会有些漏洞的问题,不过我这个rom本身就是逆向使用的工具,而面向正常用户,所以就暂时不考虑漏洞问题了。

参考文章:Android AOSP 添加系统服务【aidl接口服务】Java层

参考文章:android 10 添加系统服务步骤

​ 下面贴上我定义的系统服务

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<PackageItem> 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的权限依赖,以及能全局应用都可以访问到。具体的详细相关代码可参考我放出的部分源码。

脱壳相关优化

​ 由于我走向了另外一条偏向更易于使用的优化方向,所以关于脱壳的关键部分我并没有做什么优化,所以如果脱壳需求较大的朋友可以考虑看看hanbingle大佬的脱壳王是否能满足需求。

​ 我对脱壳的优化主要分为两点,由于FartExt当时的脱壳结果保存是在sdcard中,所以对于权限有一定依赖,所以我优化掉了这块的依赖,让我们不用再手动开启sdcard权限,也能保存下脱壳结果。另外一点就是当时如果是抽取壳的情况,我们需要拿出两个文件来手动修复一下。我这里也优化了一下,会直接将函数执行完的dex重新再保存一份,这样就无需我们再手动修复了。当然同时也保留了原本的做法,仍然保存大佬说的几个重要元素。

优化1方案:解决sdcard权限问题

​ 我尝试了很多种办法来避免我们手动开启sdcard权限的情况下保存脱壳结果文件。最后测试成功了两种办法。这两种办法我都简单说下。

​ 第一种办法,我们直接将数据可以写入应用本身的内部空间中。也就是/data/data/<PackageName>中。但是这样有权限写入,但是没有root的话就无法访问到保存的结果了,当然没有root的情况也是能访问到应用内部数据的。我们可以通过命令run-as <PackageName>来直接进入应用数据内部。但是也有人会问,如果对方应用没有开启debuggable,不就没办法使用run-as了吗?这就是改Rom的优势所在了。我们可以直接修改PackageParser中的函数parseBaseApplication。在里面为我们默认打开debuggable即可。这样即使对方没有设置,在加载xml的时候,也会打开这个功能。

​ 第二种办法,既然第一个办法最后可以直接改xml为我们默认打开debuggable,那么我们解决sdcard权限,也可以修改PackageParser中的函数parseBaseApplication来直接打开sdcard权限。不过由于安卓10的特殊性,即使打开了sdcard权限,也只能在sdcard中自己的目录写入数据,所以使用这个办法时,脱壳结果应保存在/sdcard/Android/data/<PackageName>/目录中。

​ 下面贴上相关的代码,其中包含了两种解决方案

//这里我判断如果非系统应用才增加这些权限
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);
...
}
}
//主动调用完成时,重新保存到文件名<dexSize>_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文件也是不会生成的。

1、修改内核反调试

​ 关于修改内核反调试已经有相当多的文章讲解了,我这里也是和他们用的同一个方案,基本都是根据正向检测调试的方式,来进行反调试,当然如果有人用其他方式来判断是否被调试,这个反调试就无效了。详细的过反调试原理可以看参考文章。

参考文章:修改内核源码绕过反调试检测(Android10)

​ 虽然我改了。但是没测试效果咋样。修改的代码如下,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/<pid>/status文件时,获取到的TracerPid就会一直是0,并且应用的状态不会出现stoppedtracing stop。下面是status的部分内容

Name:    .kanxue.crackme
State:    S (sleeping)
Tgid:    12142
Pid:    12142
PPid:    545
TracerPid:    0
Uid:    10027    10027    10027    10027
Gid:    10027    10027    10027    10027
Ngid:    0
FDSize:    128
Groups:    9997 20027 50027
VmPeak:     5657524 kB
VmSize:     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<uint32_t>(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:"<<methodname<<" wait debug sleep 60...";
LOG(ERROR)<<oss1.str();
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 "<<jniMethodName<<"\t"<<ArtField::PrettyField(f);
LOG(ERROR)<<oss.str();
}
}
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 "<<jniMethod<<"\t"<<str1<<"\t"<<str2;
}else if(str1!=nullptr ){
oss << "mikrom jni "<<jniMethod<<"\t"<<str1;
}else{
oss << "mikrom jni "<<jniMethod;
}
LOG(ERROR)<<oss.str();
}
}

处理完判断部分,接下来就是对于参数的打印部分了,这里我只处理了下将字符串参数打印出来,其他特殊的参数我就没有处理了。

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 "<<jniMethodName<<"\t"<<methodName<<"\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"<<i<<":"<<va_arg(ap, jint)<<"\t";
break;
case 'F':
ss<<"arg"<<i<<":"<<va_arg(ap, jdouble)<<"\t";
break;
case 'L':{
jobject obj=va_arg(ap, jobject);
ObjPtr<mirror::Object> receiver =soa.Decode<mirror::Object>(obj);
if(receiver==nullptr){
LOG(ERROR)<<"mikrom  "<<jniMethodName<<" receiver =nullptr";
break;
}
ObjPtr<mirror::Class> cls=receiver->GetClass();
if (cls->DescriptorEquals("Ljava/lang/String;")){
ObjPtr<mirror::String> argStr =soa.Decode<mirror::String>(obj);
ss<<"arg"<<i<<":"<<(const char*)argStr->GetValue();
}else{
ss<<"arg"<<i<<":"<<receiver<<"\t";
}
break;
}
case 'D':
ss<<"arg"<<i<<":"<<va_arg(ap, jdouble)<<"\t";
break;
case 'J':
ss<<"arg"<<i<<":"<<va_arg(ap, jlong)<<"\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 "<<jniMethodName<<"\t"<<methodName<<"\t";
for (size_t i = 1, args_offset = 0; i < shorty_len_; ++i, ++args_offset) {
switch (shorty_[i]) {
case 'Z':
ss<<"arg"<<i<<":"<<args[args_offset].z<<"\t";
break;
case 'B':
ss<<"arg"<<i<<":"<<args[args_offset].b<<"\t";
break;
case 'C':
ss<<"arg"<<i<<":"<<args[args_offset].c<<"\t";
break;
case 'S':
ss<<"arg"<<i<<":"<<args[args_offset].s<<"\t";
break;
case 'I':
FALLTHROUGH_INTENDED;
case 'F':
ss<<"arg"<<i<<":"<<args[args_offset].i<<"\t";
break;
case 'L':{
jobject obj=args[args_offset].l;
ObjPtr<mirror::Object> receiver =soa.Decode<mirror::Object>(obj);
if(receiver==nullptr){
LOG(ERROR)<<"mikrom  "<<jniMethodName<<" receiver =nullptr";
break;
}
ObjPtr<mirror::Class> cls=receiver->GetClass();
if (cls->DescriptorEquals("Ljava/lang/String;")){
ObjPtr<mirror::String> argStr =soa.Decode<mirror::String>(obj);
ss<<"arg"<<i<<":"<<(const char*)argStr->GetValue();
}else{
ss<<"arg"<<i<<":"<<receiver<<"\t";
}
break;
}
case 'D':
FALLTHROUGH_INTENDED;
case 'J':
ss<<"arg"<<i<<":"<<args[args_offset].j<<"\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

参考文章:从0开始实现一个简易的主动调用框架

​ 不过我的加载时机和他的不太一样,并且我扩展了可自行切换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();//用完就删否则不会更新
}
}
//注入so
public 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("\n");
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<mirror::Object> 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)<<oss.str();
}
}
}

另外就是强制以解释器执行的部分

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:"<<method->PrettyMethod().c_str()<<"\t"<<ArtMethod::GetTraceMethod();
LOG(ERROR) <<oss.str();
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:"<<method->PrettyMethod().c_str();
LOG(ERROR)<<oss.str();
enter_interpreter=true;
}
...
}

变量enter_interpreter决定了是否使用解释器执行,如果是quick快速模式执行,将无法执行到我们修改的函数。最后测试发现,函数第一次调用的时候,是走的解释执行,成功打印出trace记录,第二次调用时,又被优化到快速模式执行了。不过我们已经拿到想要的结果了,我就不继续优化了。关于LinkCode这里的修改如果不理解的,可以看看下面的参考文章,里面有详细的流程图了。我就不重复了。

参考文章:将FART和Youpk结合来做一次针对函数抽取壳的全面提升

7、dex注入(未完成)

​ 这个功能我原本是想内置一个IO重定向,或者是内置一个Sandhook,然后直接通过配置勾选就能自动注入使用了。然后我们修改的dex直接注入进去即可使用。不过目前还没想好怎么设计里面的一些交互部分。所以只是简单的做了下注入的步骤。后续也不一定有时间继续实现,先暂时搁置把。

MikManager展示

​ 操作起来非常简单,选择想要处理的应用,然后选择对应功能即可。记得勾选完后,回到应用栏目点保存,如果是要使用frida脚本,或者是要选择so,需要把文件放在sdcard中的对应应用的目录中。如果sdcard中没有对应目录,可以打开一下应用会自动生成的。

​ 脱壳功能的保存结果在/sdcard/Android/data/<PackageName>/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.<init>(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 GetArrayLength
2022-01-30 19:43:30.198 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars    android.widget.ViewStub
2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni CallNonvirtualVoidMethodV    void java.lang.ClassNotFoundException.<init>(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.ViewStub
2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni NewStringUTF    android.webkit.ViewStub
2022-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.ViewStub
2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars    android.webkit.ViewStub
2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni GetStringUTFChars    android.webkit.ViewStub
2022-01-30 19:43:30.199 4105-4105/com.kanxue.crackme E/.kanxue.crackm: mikrom jni CallNonvirtualVoidMethodV    void java.lang.ClassNotFoundException.<init>(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:568
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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.<init>() // method@17    // vreg0=0x12C89670/java.lang.String "MainActivity" vreg1=0x00000010 vreg2=0x12C896C8/java.lang.StringBuilder vreg3=0x00000000 vreg4=0x00000000 vreg5=0x00000001 vreg6=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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.<init>() // 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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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=0x0000000F
2022-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,注入顺序会默认按照导入顺序。

​ 整理一遍之后,感觉好像做了很多,又感觉好像也没搞点啥。也碰到很多问题让我一遍又一遍的编译测试。不过总算大致实现了当时的想法。后续应该不会有啥升级了。调转方向研究点其他东西了。如果以后内核功底深一些了,可能会回头扩展把。重要的事情再提一次,不知道修改到了art部分的哪个点,导致了调试超级慢,如果有大佬知道啥原因的,麻烦指导一下。

【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

点赞・15

最新回复 (51)

财神爷999 活跃值 2022-2-1 09:48

2 楼

笑对VS人生 活跃值 2022-2-1 11:19

3 楼

不吃早饭 活跃值 2022-2-1 12:22

4 楼

乐不思蜀1 活跃值 2022-2-1 14:04

5 楼

乐不思蜀1 活跃值 2022-2-1 16:27
6 楼
MikRom,pixel sailfish能用吗?
爱吃菠菜 活跃值 2 2022-2-1 16:41
7 楼

安全厂商连夜赶工将ROM特征加入ANTI列表(新年玩笑)

最后于 2022-2-1 16:44 被爱吃菠菜编辑 ,原因:
misskings 活跃值 4 2022-2-1 17:14
8 楼

MikRom,pixel sailfish能用吗?

应该可以的咯,我没测试sailfish.这两个好像差不多。
misskings 活跃值 4 2022-2-1 17:31
9 楼

安全厂商连夜赶工将ROM特征加入ANTI列表(新年玩笑)

哈哈。不至于。感觉我这玩意挺简陋的。有感兴趣的功能,单独改改用都可以用咯。
mb_yvvzfcdo 活跃值 2022-2-2 00:03
10 楼
感谢大佬!
01deadbeef10 活跃值 2022-2-3 13:02
11 楼
大佬,能否告知一下MiRom的编译方法呢?具体是aosp哪个版本,还有内核是哪个版本?谢谢
misskings 活跃值 4 2022-2-3 17:19
12 楼

大佬,能否告知一下MiRom的编译方法呢?具体是aosp哪个版本,还有内核是哪个版本?谢谢

里面有说,使用的是Pixel Experience,https://github.com/PixelExperience。你选择aosp10也可以。相差不大的。我前一个版本是用aosp10r2的版本做的。
01deadbeef10 活跃值 2022-2-3 21:51
13 楼

里面有说,使用的是Pixel Experience,https://github.com/PixelExperience。你选择aosp10也可以。相差不大的。我前一个版本是用aosp10r2的版本做 ...

[email protected] (SHARED_LIBRARIES android-arm64) missing bootctrl.msm8996 (STATIC_LIBRARIES android-arm64) 你有遇到这个错误吗?我现在编译的是https://github.com/PixelExperience 
misskings 活跃值 4 2022-2-3 22:06
14 楼

[email protected] (SHARED_LIBRARIES android-arm64) missing bootctrl.ms ...

没碰到这个问题。device和kernel有拉取不。我就碰到一个tests的问题,就编译成功了
万里星河 活跃值 2022-2-4 12:09

15 楼

快乐小牛 活跃值 2022-2-4 20:58
16 楼
感谢感谢,以后有条件一定试试
万里星河 活跃值 2022-2-6 12:00
17 楼
大佬 我有个问题 如果壳是函数执行前解密 执行完后又加密 那么即便是主动调用完后再dump 会不会也是存在问题 感觉先保存codeitem再修复更稳妥一些
misskings 活跃值 4 2022-2-6 13:09
18 楼

大佬 我有个问题 如果壳是函数执行前解密 执行完后又加密 那么即便是主动调用完后再dump 会不会也是存在问题 感觉先保存codeitem再修复更稳妥一些

原先的主动调用后保存codeitem依然保留的。只是多了个保存修复结果的功能。想继续用codeitem修复也是可以的
小黄鸭爱学习 活跃值 2022-2-8 16:41
19 楼
厂商新增anti列表
包括fart以及fart的各种魔改版本
检测到fart闪退
检测到FartExt闪退
检测到MikRom闪退
...
TUGOhost 活跃值 2022-2-8 19:41
20 楼
大佬,收藏了,春节的时候就在复现fartext,那会还没公开源码,这次竟然一步到位了,感谢。
Skyart 活跃值 2022-2-9 09:09

21 楼

amwpecel 活跃值 2022-2-13 14:59
22 楼
是用pixel XL一代手机刷ROM嘛 大佬14.gif
零丶 活跃值 2022-2-14 03:32
23 楼
41.gif做好的手机多少钱我要了
misskings 活跃值 4 2022-2-14 12:09
24 楼

是用pixel XL一代手机刷ROM嘛 大佬[em_14]

是的。我就是用这个手机测试的
misskings 活跃值 4 2022-2-14 12:13
25 楼

[em_41]做好的手机多少钱我要了

13.gif暂时没有商用卖手机的打算。你直接拿twrp刷就行了。以后糅合更实用的功能了,再考虑搞不搞商用或者卖手机吧
游客
登录 | 注册 方可回帖
返回

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK