首页
社区
课程
招聘
[原创]移植Youpk到Aosp10上
发表于: 2024-7-26 10:03 19912

[原创]移植Youpk到Aosp10上

2024-7-26 10:03
19912

参考文章:

https://github.com/jitcor/Youpk8

https://github.com/Youlor/Youpk

https://bbs.kanxue.com/thread-271358-1.htm#msg_header_h3_0

https://www.jianshu.com/p/18ff0b8e0b01

Fart脱壳王课程

本文章会定时更新,有问题直接向我提问,我会补充到文章

同时我会在完善编译的所有流程(为小白铺路)

并完善移植原理的解读(为想要了解原理的大佬解答)

同时我会完善我如何从aosp8移植到10的过程,如何去寻找变更api(授人以渔,大家学会可以自行把我目前的移植到aosp12

Youpk是一个很强大的框架,他的模块化组织形式非常新颖,但是随着安卓系统的不断更新,移植难度也非常大,由于使用了大量的api,导致移植有一定的难度,与fart相比,模块化的插桩更加优雅。

已经有大佬做了fart10的移植(见参考文章3),我这里就不和他重复了,来尝试下youpk的移植,并去除特征指纹

测试设备:pixel1

aosp移植版本:aosp10.0.0_r2(本来想移植fartext,失败了)

(aosp11与10的api相关类似,可以自己尝试)

1.需要你有基础的aosp编译修改经验,简单修改能编译成功

硬盘需要大于1TB 我是32G+2TB 编译体验非常差 有条件推荐上4TB+64G

如何开启clion和android studio导入项目源码:

编译的时候要注意repo的python版本,最好大于3.7 如果在低版本ubuntu系统,需要自己编译python

repo fatal: error unknown url type: https

原因:python没有设置ssl

./configure --prefix=/usr/local/python3 --with-ssl

解决文档:

https://stackoverflow.com/questions/18317682/android-aosp-repo-init-fatal-error-unknown-url-type-https

解决:在编译的时候配置ssl

找到一个趁手的编译环境+IDE环境是成功编译的第一步骤

由于7.1→8.0→10.0有多版本跨度,我们选择youpk8的源码进行移植,来减少api差异

第一步,导入unpacker类到

Untitled

这里我们可以第一步去除指纹,修改包名

我这里的包名是com.jiqiu

Untitled

类名也完全可以修改,在修改后要在

Untitled

这个文件里加上自己的包名,否则编译不过

比如我打的包名是com.jiqiu

在里面就是

Untitled

之后进入

core/java/android/app/ActivityThread.java

导入自己的包名

Untitled

在app启动后,注入自己的脱壳线程

Untitled

注意:如果你修改了类名,这里的导入和调用也需要修改

想比于fart,youpk的主动调用部分在native层实现,java层仅仅是启动一个线程 启动native函数

Untitled

第一步修改dexopt.cc 路径如上

注意,这里的config路径一定要与java层设置的一致,见去指纹2

const char* UNPACK_CONFIG = "/data/local/tmp/gagaga";

Untitled

并且修改Android.bp

添加目标编译文件

Untitled

添加编译文件后我们就可以初步处理youpk的所有不兼容api,附件提供了修改前后的youpk文件夹,在数十次的编译中,已经修改成安卓系统最新支持api

Untitled

在新版系统编译,这个宏定义视为不安全,直接使用math库的同名函数即可过编译,记得注释原来的

Untitled

在新版系统中,dex相关库文件移动到了libdexfile文件夹下,我们只需改动libdexfile/Android.bp

导出其依赖的库,并按新版文件调用,即可解决依赖问题

Untitled

globals位置发生改变

#include "base/globals.h”

mirror::Class*指针修改为ObjPtrmirror::Class

setstatus的状态码发生改变 mirror::Class::kStatusInitialized变为ClassStatus::kInitialized

删除size_t Unpacker::getCodeItemSize(ArtMethod* method)方法 新版有api可以直接实现

Untitled

Untitled

uint32_t code_item_size = method->GetDexFile()->GetCodeItemSize(*code_item);

可以直接获取到codeitem的size 省去了上面的函数

Untitled

method->GetCodeItem()->insns_;

新版本的codeitem没有insns_属性,需要迭代器访问

见参考文章4

修改为 const uint16_t* const insns = CodeItemInstructionAccessor(*method->GetDexFile(),method->GetCodeItem()).Insns();即可编译通过

最后修改注册函数

Untitled

REGISTER_NATIVE_METHODS("com/jiqiu/Unpacker");

包名和类名修改过的可以去修改下

a/runtime/interpreter/interpreter_switch_impl-inl.h

注意这个不是在cpp中实现了,在interpreter_switch_impl-inl.h头中实现

而且youpk插桩的宏定义在aosp10中改为了函数判断,无法在函数中插桩

只需要在相应位置插桩即可解决(已给出patch)

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

https://github.com/LSPosed/AndroidHiddenApiBypass

解决方法:

换一个注册的名字,可以选定一些厂商的特殊包名注册,如xiaomi meizu huawei等特殊包名

修改config名字或落地地方挑选app不可达路径(之后会集中讨论)

DexFIile静态注册了一个函数,作为与native桥接的函数,由于比较独特,直接可以通过反射调用,甚至可以做进一步下沉,在libart.so的导出表中发现这个函数

在ActivityThred中出现了很多工具类函数,可以反射调用检测,以及在art_method中有额外的导出函数,可以通过扫描libart.so的导出表来扫描制定名字

解决方法:

难点:

权限的申请(脱壳机一般都有)

用户隐私的保护 (厂商一般不管)

解决办法

修改系统selinux,注册全新的selinux标签,编写系统应用,进行文件的存储和获取(小肩膀沙盒定制思路)

注册系统服务,app通过调用,存储到/system 目录下

以上两种方法均可进行dex和config文件的落地

难点:

各大厂商对于rom均有定制,libart.so数量均不一致

无法太大的做到导出函数数量的特征(这个是真的致命打击)

解决办法:

建立机型库,对机型的各个文件进行模型建立,检测是否为异常libart,判断是否为异常机型

脱壳机一般使用pixel以及nexus等机型做定制rom,可以针对这些机型做风控(误杀率高)

所以可以对aosp的定制进行检测,对aosp的指纹进行检测(目前大部分脱壳机过不去企业壳都折在了这里)

解决办法:

使用开源的lineageos 以及pixel experience系统进行定制,这些系统都已经去除了很多aosp的特征

(除非日后国内哪家厂商开源了他们的操作系统)

使用支持这些rom的手机进行定制,这里我强烈推荐一加手机,简直无敌 随便刷随便解锁 还能9008救砖(比某xel6代好太多了)

https://fortunate-decimal-730.notion.site/Youpk-Aosp10-ead82bb5990c4574a9fc0e5d899beaa1?pvs=4

移植后的unpcker模块

所有patch:

Untitled

package com.jiqiu;
import android.app.ActivityThread;
import android.os.Looper;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
 
public class Unpacker {
//    public static String UNPACK_CONFIG = "/data/local/tmp/unpacker.config";
    //  去指纹位置2,修改配置名文件,不一定需要config尾缀
    public static String UNPACK_CONFIG = "/data/local/tmp/gagaga";
    public static int UNPACK_INTERVAL = 10 * 1000;
    public static Thread unpackerThread = null;
 
    public static boolean shouldUnpack() {
        boolean should_unpack = false;
        String processName = ActivityThread.currentProcessName();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(UNPACK_CONFIG));
            String line;
            while ((line = br.readLine()) != null) {
                if (line.equals(processName)) {
                    should_unpack = true;
                    break;
                }
            }
            br.close();
        }
        catch (Exception ignored) {
 
        }
        return should_unpack;
    }
 
    public static void unpack() {
        if (Unpacker.unpackerThread != null) {
            return;
        }
 
        if (!shouldUnpack()) {
            return;
        }
 
        //开启线程调用
        Unpacker.unpackerThread = new Thread() {
            @Override public void run() {
                while (true) {
                    try {
                        Thread.sleep(UNPACK_INTERVAL);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Unpacker.unpackNative();
                }
            }
        };
        Unpacker.unpackerThread.start();
    }
 
    public static native void unpackNative();
}
package com.jiqiu;
import android.app.ActivityThread;
import android.os.Looper;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
 
public class Unpacker {
//    public static String UNPACK_CONFIG = "/data/local/tmp/unpacker.config";
    //  去指纹位置2,修改配置名文件,不一定需要config尾缀
    public static String UNPACK_CONFIG = "/data/local/tmp/gagaga";
    public static int UNPACK_INTERVAL = 10 * 1000;
    public static Thread unpackerThread = null;
 
    public static boolean shouldUnpack() {
        boolean should_unpack = false;
        String processName = ActivityThread.currentProcessName();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(UNPACK_CONFIG));
            String line;
            while ((line = br.readLine()) != null) {
                if (line.equals(processName)) {
                    should_unpack = true;
                    break;
                }
            }
            br.close();
        }
        catch (Exception ignored) {
 
        }
        return should_unpack;
    }
 
    public static void unpack() {
        if (Unpacker.unpackerThread != null) {
            return;
        }
 
        if (!shouldUnpack()) {
            return;
        }
 
        //开启线程调用
        Unpacker.unpackerThread = new Thread() {
            @Override public void run() {
                while (true) {
                    try {
                        Thread.sleep(UNPACK_INTERVAL);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Unpacker.unpackNative();
                }
            }
        };
        Unpacker.unpackerThread.start();
    }
 
    public static native void unpackNative();
}
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1036,6 +1036,8 @@ class Dex2Oat final {
         CompilerFilter::NameOfFilter(compiler_options_->GetCompilerFilter()));
     key_value_store_->Put(OatHeader::kConcurrentCopying,
                           kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+
+
     if (invocation_file_.get() != -1) {
       std::ostringstream oss;
       for (int i = 0; i < argc; ++i) {
@@ -1089,7 +1091,23 @@ class Dex2Oat final {
       *out = true;
     }
   }
-
+    //patch by Youlor
+    //++++++++++++++++++++++++++++
+    const char* UNPACK_CONFIG = "/data/local/tmp/gagaga";
+    bool ShouldUnpack() {
+        std::ifstream config(UNPACK_CONFIG);
+        std::string line;
+        if(config) {
+            while (std::getline(config, line)) {
+                std::string package_name = line.substr(0, line.find(':'));
+                if (oat_location_.find(package_name) != std::string::npos) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    //++++++++++++++++++++++++++++
   // Parse the arguments from the command line. In case of an unrecognized option or impossible
   // values/combinations, a usage error will be displayed and exit() is called. Thus, if the method
   // returns, arguments have been successfully parsed.
@@ -1240,7 +1258,14 @@ class Dex2Oat final {
     ProcessOptions(parser_options.get());
  
     // Insert some compiler things.
+
     InsertCompileOptions(argc, argv);
+    //patch by Youlor
+    //++++++++++++++++++++++++++++
+      if (ShouldUnpack()) {
+          compiler_options_->SetCompilerFilter(CompilerFilter::kVerify);
+      }
//++++++++++++++++++++++++++++
   }
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1036,6 +1036,8 @@ class Dex2Oat final {
         CompilerFilter::NameOfFilter(compiler_options_->GetCompilerFilter()));
     key_value_store_->Put(OatHeader::kConcurrentCopying,
                           kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+
+
     if (invocation_file_.get() != -1) {
       std::ostringstream oss;
       for (int i = 0; i < argc; ++i) {
@@ -1089,7 +1091,23 @@ class Dex2Oat final {
       *out = true;
     }
   }
-
+    //patch by Youlor
+    //++++++++++++++++++++++++++++
+    const char* UNPACK_CONFIG = "/data/local/tmp/gagaga";
+    bool ShouldUnpack() {
+        std::ifstream config(UNPACK_CONFIG);
+        std::string line;
+        if(config) {
+            while (std::getline(config, line)) {
+                std::string package_name = line.substr(0, line.find(':'));
+                if (oat_location_.find(package_name) != std::string::npos) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    //++++++++++++++++++++++++++++
   // Parse the arguments from the command line. In case of an unrecognized option or impossible
   // values/combinations, a usage error will be displayed and exit() is called. Thus, if the method
   // returns, arguments have been successfully parsed.
@@ -1240,7 +1258,14 @@ class Dex2Oat final {
     ProcessOptions(parser_options.get());
  
     // Insert some compiler things.

[峰会]看雪.第八届安全开发者峰会10月23日上海龙之梦大酒店举办!

最后于 2024-7-29 13:58 被mb_qzwrkwda编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (9)
雪    币: 3754
活跃值: (3299)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
附件下载不了呢
2024-7-26 11:14
0
雪    币: 313
活跃值: (2192)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
图看不了
2024-7-26 11:19
0
雪    币: 3996
活跃值: (1299)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
4
黑屏 附件下载不了呢
看一下最顶上的链接
2024-7-26 11:58
0
雪    币: 3996
活跃值: (1299)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
5
执念成狂 图看不了
看下最上面的链接 图床挂了
2024-7-26 12:02
0
雪    币: 3996
活跃值: (1299)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
6
执念成狂 图看不了
看一下 已经恢复了
2024-7-26 14:35
0
雪    币: 242
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7

com.xiaojianbang 肩膀叔的demo (doge)

最后于 2024-7-26 16:09 被北袅编辑 ,原因:
2024-7-26 16:08
0
雪    币: 3996
活跃值: (1299)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
8
北袅 com.xiaojianbang&nbsp;肩膀叔的demo&nbsp;(doge)
致敬肩膀叔
2024-7-26 17:41
0
雪    币: 17
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
太强了  有移植到安卓 13的吗 
2024-8-13 11:56
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
NATIVE层移植   第一步修改dexopt.cc 路径如上  这个位置路径和文件对不上 下面也对不上   大佬
2024-8-16 10:46
0
游客
登录 | 注册 方可回帖
返回
//