参考文章:
865K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6B7K9i4c8U0L8%4u0Q4x3V1k6k6L8%4g2H3K9K6R3`.
7f6K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6k6L8%4g2D9L8%4u0Q4x3V1k6k6L8%4g2H3K9H3`.`.
https://bbs.kanxue.com/thread-271358-1.htm#msg_header_h3_0
48dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2B7K9h3q4F1M7$3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0p5^5k6X3j5H3j5U0S2W2x3r3t1H3x3b7`.`.
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
解决文档:
717K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6N6r3q4U0K9$3!0$3k6i4u0X3L8r3!0%4i4K6u0W2j5$3!0E0i4K6u0r3M7i4g2W2M7%4c8A6L8$3&6K6i4K6u0r3x3e0R3K6x3e0M7$3z5o6u0Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1j5h3!0K6M7q4)9J5k6s2u0W2M7r3!0Q4x3X3c8A6L8X3W2@1i4K6u0V1k6X3q4@1j5h3I4Q4x3X3c8W2M7Y4u0G2M7W2)9J5k6s2g2F1K9$3&6G2N6$3&6Q4x3X3c8#2M7X3I4Q4x3X3c8@1P5i4m8W2i4K6u0V1K9s2c8@1M7s2x3`.
解决:在编译的时候配置ssl
找到一个趁手的编译环境+IDE环境是成功编译的第一步骤
由于7.1→8.0→10.0有多版本跨度,我们选择youpk8的源码进行移植,来减少api差异
第一步,导入unpacker类到

这里我们可以第一步去除指纹,修改包名
我这里的包名是com.jiqiu

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

这个文件里加上自己的包名,否则编译不过
比如我打的包名是com.jiqiu
在里面就是

之后进入
core/java/android/app/ActivityThread.java
导入自己的包名

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

注意:如果你修改了类名,这里的导入和调用也需要修改
想比于fart,youpk的主动调用部分在native层实现,java层仅仅是启动一个线程 启动native函数

第一步修改dexopt.cc 路径如上
注意,这里的config路径一定要与java层设置的一致,见去指纹2
const char* UNPACK_CONFIG = "/data/local/tmp/gagaga";

并且修改Android.bp
添加目标编译文件

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

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

在新版系统中,dex相关库文件移动到了libdexfile文件夹下,我们只需改动libdexfile/Android.bp
导出其依赖的库,并按新版文件调用,即可解决依赖问题

globals位置发生改变
#include "base/globals.h”
mirror::Class*指针修改为ObjPtrmirror::Class
setstatus的状态码发生改变 mirror::Class::kStatusInitialized变为ClassStatus::kInitialized
删除size_t Unpacker::getCodeItemSize(ArtMethod* method)方法 新版有api可以直接实现


uint32_t code_item_size = method->GetDexFile()->GetCodeItemSize(*code_item);
可以直接获取到codeitem的size 省去了上面的函数

method->GetCodeItem()->insns_;
新版本的codeitem没有insns_属性,需要迭代器访问
见参考文章4
修改为 const uint16_t* const insns = CodeItemInstructionAccessor(*method->GetDexFile(),method->GetCodeItem()).Insns();即可编译通过
最后修改注册函数

REGISTER_NATIVE_METHODS("com/jiqiu/Unpacker");
包名和类名修改过的可以去修改下
a/runtime/interpreter/interpreter_switch_impl-inl.h
注意这个不是在cpp中实现了,在interpreter_switch_impl-inl.h头中实现
而且youpk插桩的宏定义在aosp10中改为了函数判断,无法在函数中插桩
只需要在相应位置插桩即可解决(已给出patch)






818K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3f1#2m8G2M7$3g2V1i4K6u0r3b7h3&6V1M7X3!0A6k6p5S2A6k6r3c8W2L8V1q4H3K9f1u0&6M7r3q4K6M7H3`.`.
解决方法:
换一个注册的名字,可以选定一些厂商的特殊包名注册,如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代好太多了)
2b7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6X3L8%4u0@1N6h3&6S2N6r3g2Q4x3X3c8V1k6h3y4A6L8h3q4D9i4K6u0V1y4K6x3H3i4K6u0W2L8X3!0@1K9h3!0F1i4K6u0W2M7$3W2@1k6g2)9J5c8W2W2G2N6i4m8C8i4K6u0V1b7h3!0K6M7o6p5H3i4K6u0V1k6h3q4V1z5o6u0T1j5U0f1&6z5e0m8U0y4o6f1%4y4r3p5&6k6X3x3H3k6e0g2V1z5o6V1&6j5X3g2S2j5e0q4Q4x3@1k6H3N6Y4y4Q4x3@1b7@1
移植后的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.
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!
最后于 2024-7-29 13:58
被棕熊编辑
,原因: