寒冰大佬的FART带动了不少新的主动调用思想的抽取壳方案,看了上面这篇文章感觉意犹未尽,当然是要动手实践一翻来优化一波。于是我重新翻阅FART和Youpk的源码,我准备和那位大佬一样,参考Youpk在FART的基础上进行升级改造。
开工前先明确出我的需求:
对指定进程脱壳,非目标进程不要执行脱壳线程
对指定类列表进行脱壳
将FART升级到aosp10实现
去FART指纹
FART保存出的函数修复合并为dex
实现FART更深的主动调用
更快的主动调用(暂未优化)
1
2
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
fartthread();
...
}
//判断这个进程是否应该脱壳
public static boolean shouldUnpack() {
boolean should_unpack = false;
String processName = ActivityThread.currentProcessName();
BufferedReader br = null;
String configPath="/data/local/tmp/fext.config";
try {
br = new BufferedReader(new FileReader(configPath));
String line;
while ((line = br.readLine()) != null) {
if (processName.equals(line))) {
should_unpack = true;
break;
}
}
br.close();
}
catch (Exception ignored) {
}
return should_unpack;
}
//启动FART脱壳线程
public static void fartthread() {
if (!shouldUnpack()) {
return;
}
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Log.e("ActivityThread", "start sleep......");
Thread.sleep(1 * 60 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.e("ActivityThread", "sleep over and start fart");
fart();
Log.e("ActivityThread", "fart run over");
}
}).start();
}
private void handleBindApplication(AppBindData data) {
...
app = data.info.makeApplication(data.restrictedBackupMode, null);
app.setAutofillOptions(data.autofillOptions);
app.setContentCaptureOptions(data.contentCaptureOptions);
mInitialApplication = app;
fartthread();
...
}
这里主要是做了两点修改:
3
//读取类列表
public static String getClassList() {
String processName = ActivityThread.currentProcessName();
BufferedReader br = null;
String configPath="/data/local/tmp/"+processName;
Log.e("ActivityThread", "getClassList processName:"+processName);
StringBuilder sb=new StringBuilder();
try {
br = new BufferedReader(new FileReader(configPath));
String line;
while ((line = br.readLine()) != null) {
if(line.length()>=2){
sb.append(line+"\n");
}
}
br.close();
}
catch (Exception ex) {
Log.e("ActivityThread", "getClassList err:"+ex.getMessage());
return "";
}
return sb.toString();
}
//对指定类进行主动调用
public static void fartWithClassList(String classlist){
ClassLoader appClassloader = getClassloader();
if(appClassloader==null){
Log.e("ActivityThread", "appClassloader is null");
return;
}
Class DexFileClazz = null;
try {
DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
Method dumpMethodCode_method = null;
for (Method field : DexFileClazz.getDeclaredMethods()) {
if (field.getName().equals("fartextMethodCode")) {
dumpMethodCode_method = field;
dumpMethodCode_method.setAccessible(true);
}
}
String[] classes=classlist.split("\n");
for(String clsname : classes){
String line=clsname;
if(line.startsWith("L")&&line.endsWith(";")&&line.contains("/")){
line=line.substring(1,line.length()-1);
line=line.replace("/",".");
}
loadClassAndInvoke(appClassloader, line, dumpMethodCode_method);
}
}
public static void fartthread() {
if (!shouldUnpack()) {
return;
}
//获取类列表。如果有的话就不要完整主动调用了
String classlist=getClassList();
if(!classlist.equals("")){
fartWithClassList(classlist);
return;
}
...
}
4
extern "C" void dumpArtMethod(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
...
const dex::CodeItem* code_item = artmethod->GetCodeItem();
const DexFile* dex_=artmethod->GetDexFile();
CodeItemDataAccessor accessor(*dex_, dex_->GetCodeItem(artmethod->GetCodeItemOffset()));
if (LIKELY(code_item != nullptr))
{
int code_item_len = 0;
uint8_t *item=(uint8_t *) code_item;
if (accessor.TriesSize()>0) {
const uint8_t *handler_data = accessor.GetCatchHandlerData();
uint8_t * tail = codeitem_end(&handler_data);
code_item_len = (int)(tail - item);
}else{
code_item_len = 16+accessor.InsnsSizeInCodeUnits()*2;
}
...
}
...
}
5
6
7
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
if (self== nullptr) {
dumpArtMethod(this);
return;
}
...
}
.method public constructor <init>()V
.registers 2
goto :goto_c
:goto_1
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
return-void
:goto_c
const v0, 0x1669
invoke-static {v0}, Ls/h/e/l/l/H;->i(I)V
goto :goto_1
.end method
static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind;
void Unpacker::invokeAllMethods() {
...
auto methods = klass->GetDeclaredMethods(pointer_size);
Unpacker::enableFakeInvoke();
for (auto& m : methods) {
ArtMethod* method = &m;
if (!method->IsProxyMethod() && method->IsInvokable()) {
//获取参数个数
uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(method->GetShorty());
if (!method->IsStatic()) {
args_size += 1;
}
//模拟参数
JValue result;
std::vector<uint32_t> args(args_size, 0);
if (!method->IsStatic()) {
mirror::Object* thiz = klass->AllocObject(self);
args[0] = StackReference<mirror::Object>::FromMirrorPtr(thiz).AsVRegValue();
}
method->Invoke(self, args.data(), args_size, &result, method->GetShorty());
}
}
Unpacker::disableFakeInvoke();
cJSON_ReplaceItemInObject(current, "status", cJSON_CreateString("Dumped"));
writeJson();
}
}
}
extern "C" void myfartInvoke(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
JValue *result=nullptr;
Thread *self=nullptr;
uint32_t temp=6;
uint32_t* args=&temp;
uint32_t args_size=6;
artmethod->Invoke(self, args, args_size, result, "fart");
}
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
...
//patch by Youlor
//++++++++++++++++++++++++++++
//如果是主动调用fake invoke并且不是native方法则强制走解释器
if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this)
|| (Unpacker::isFakeInvoke(self, this) && !this->IsNative()))) {
//++++++++++++++++++++++++++++
if (IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(
self, this, nullptr, args, result, /*stay_in_interpreter*/ true);
} else {
mirror::Object* receiver =
reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr();
art::interpreter::EnterInterpreterFromInvoke(
self, this, receiver, args + 1, result, /*stay_in_interpreter*/ true);
}
} else {
//patch by Youlor
//++++++++++++++++++++++++++++
//如果是主动调用fake invoke并且是native方法则不执行
if (Unpacker::isFakeInvoke(self, this) && this->IsNative()) {
// Pop transition.
self->PopManagedStackFragment(fragment);
return;
}
//++++++++++++++++++++++++++++
...
}
...
}
void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receiver,
uint32_t* args, JValue* result,
bool stay_in_interpreter) {
...
JValue r = Execute(self, code_item, *shadow_frame, JValue(), stay_in_interpreter);
...
}
static inline JValue Execute(
Thread* self,
const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame,
JValue result_register,
bool stay_in_interpreter = false) SHARED_REQUIRES(Locks::mutator_lock_) {
...
} else if (kInterpreterImplKind == kSwitchImplKind) {
if (transaction_active) {
return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register,
false);
} else {
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register,
false);
}
}
...
}
//patch by Youlor
//++++++++++++++++++++++++++++
#define PREAMBLE() \
do { \
inst_count++; \
bool dumped = Unpacker::beforeInstructionExecute(self, shadow_frame.GetMethod(), \
dex_pc, inst_count); \
if (dumped) { \
return JValue(); \
} \
if (UNLIKELY(instrumentation->HasDexPcListeners())) { \
instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), \
shadow_frame.GetMethod(), dex_pc); \
} \
} while (false)
//++++++++++++++++++++++++++++
//继续解释执行返回false, dump完成返回true
bool Unpacker::beforeInstructionExecute(Thread *self, ArtMethod *method, uint32_t dex_pc, int inst_count) {
if (Unpacker::isFakeInvoke(self, method)) {
const uint16_t* const insns = method->GetCodeItem()->insns_;
const Instruction* inst = Instruction::At(insns + dex_pc);
uint16_t inst_data = inst->Fetch16(0);
Instruction::Code opcode = inst->Opcode(inst_data);
//对于一般的方法抽取(非ijiami, najia), 直接在第一条指令处dump即可
if (inst_count == 0 && opcode != Instruction::GOTO && opcode != Instruction::GOTO_16 && opcode != Instruction::GOTO_32) {
Unpacker::dumpMethod(method);
return true;
}
//ijiami, najia的特征为: goto: goto_decrypt; nop; ... ; return; const vx, n; invoke-static xxx; goto: goto_origin;
else if (inst_count == 0 && opcode >= Instruction::GOTO && opcode <= Instruction::GOTO_32) {
return false;
} else if (inst_count == 1 && opcode >= Instruction::CONST_4 && opcode <= Instruction::CONST_WIDE_HIGH16) {
return false;
} else if (inst_count == 2 && (opcode == Instruction::INVOKE_STATIC || opcode == Instruction::INVOKE_STATIC_RANGE)) {
//让这条指令真正的执行
Unpacker::disableFakeInvoke();
Unpacker::enableRealInvoke();
return false;
} else if (inst_count == 3) {
if (opcode >= Instruction::GOTO && opcode <= Instruction::GOTO_32) {
//写入时将第一条GOTO用nop填充
const Instruction* inst_first = Instruction::At(insns);
Instruction::Code first_opcode = inst_first->Opcode(inst->Fetch16(0));
CHECK(first_opcode >= Instruction::GOTO && first_opcode <= Instruction::GOTO_32);
ULOGD("found najia/ijiami %s", PrettyMethod(method).c_str());
switch (first_opcode)
{
case Instruction::GOTO:
Unpacker::dumpMethod(method, 2);
break;
case Instruction::GOTO_16:
Unpacker::dumpMethod(method, 4);
break;
case Instruction::GOTO_32:
Unpacker::dumpMethod(method, 8);
break;
default:
break;
}
} else {
Unpacker::dumpMethod(method);
}
return true;
}
Unpacker::dumpMethod(method);
return true;
}
return false;
}
template<bool do_access_check, bool transaction_active>
JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register,
bool interpret_one_instruction) {
...
//patch by Youlor
//++++++++++++++++++++++++++++
int inst_count = -1;
//++++++++++++++++++++++++++++
do {
dex_pc = inst->GetDexPc(insns);
shadow_frame.SetDexPC(dex_pc);
TraceExecution(shadow_frame, inst, dex_pc);
inst_data = inst->Fetch16(0);
switch (inst->Opcode(inst_data)) {
...
case Instruction::GOTO: {
PREAMBLE();
int8_t offset = inst->VRegA_10t(inst_data);
BRANCH_INSTRUMENTATION(offset);
if (IsBackwardBranch(offset)) {
HOTNESS_UPDATE();
self->AllowThreadSuspension();
}
inst = inst->RelativeAt(offset);
break;
}
...
case Instruction::INVOKE_STATIC: {
PREAMBLE();
bool success = DoInvoke<kStatic, false, do_access_check>(
self, shadow_frame, inst, inst_data, &result_register);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
break;
}
case Instruction::INVOKE_STATIC_RANGE: {
PREAMBLE();
bool success = DoInvoke<kStatic, true, do_access_check>(
self, shadow_frame, inst, inst_data, &result_register);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
break;
}
...
}
//patch by Youlor
//++++++++++++++++++++++++++++
bool dumped = Unpacker::afterInstructionExecute(self, shadow_frame.GetMethod(), dex_pc, inst_count);
if (dumped) {
return JValue();
}
//++++++++++++++++++++++++++++
} while (!interpret_one_instruction);
// Record where we stopped.
shadow_frame.SetDexPC(inst->GetDexPc(insns));
return result_register;
} // NOLINT(readability/fn_size)
bool Unpacker::afterInstructionExecute(Thread *self, ArtMethod *method, uint32_t dex_pc, int inst_count) {
const uint16_t* const insns = method->GetCodeItem()->insns_;
const Instruction* inst = Instruction::At(insns + dex_pc);
uint16_t inst_data = inst->Fetch16(0);
Instruction::Code opcode = inst->Opcode(inst_data);
if (inst_count == 2 && (opcode == Instruction::INVOKE_STATIC || opcode == Instruction::INVOKE_STATIC_RANGE)
&& Unpacker::isRealInvoke(self, method)) {
Unpacker::enableFakeInvoke();
Unpacker::disableRealInvoke();
}
return false;
}
#if ART_USE_CXX_INTERPRETER
static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind;
#else
static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind;
#endif
if envTrue(ctx, "ART_USE_CXX_INTERPRETER") {
cflags = append(cflags, "-DART_USE_CXX_INTERPRETER=1")
}
cflags: [
// ART is allowed to link to libicuuc directly
// since they are in the same module
"-DANDROID_LINK_SHARED_ICU4C",
"-Wno-error",
"-DART_USE_CXX_INTERPRETER=1"
],
extern "C" void fartextInvoke(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
if(artmethod->IsNative()||artmethod->IsAbstract()){
return;
}
JValue result;
//模拟参数
Thread *self=Thread::Current();
uint32_t temp[100]={0};
uint32_t* args=temp;
uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(artmethod->GetShorty());
if (!artmethod->IsStatic()) {
args_size += 1;
}
//靠这个值,在后续来判断当前函数是否为主动调用。
result.SetI(111111);
LOG(ERROR) << "fartext fartextInvoke";
Unpacker_self_=self;
artmethod->Invoke(self, args, args_size, &result,artmethod->GetShorty());
}
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
...
//add
if (result!=nullptr && result->GetI()==111111){
LOG(ERROR) << "fartext artMethod::Invoke Method "<<this->PrettyMethod().c_str();
if (IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(
self, this, nullptr, args, result, /*stay_in_interpreter=*/ true);
}else{
//注意这里是把非静态的也当静态的方式处理的。避免使用引用类型参数。
art::interpreter::EnterInterpreterFromInvoke(
self, this, nullptr, args + 1, result, /*stay_in_interpreter=*/ true);
}
LOG(ERROR) << "fartext artMethod::Invoke Method Over "<<this->PrettyMethod().c_str();
self->PopManagedStackFragment(fragment);
return;
}
//add end
...
}
void EnterInterpreterFromInvoke(Thread* self,
ArtMethod* method,
ObjPtr<mirror::Object> receiver,
uint32_t* args,
JValue* result,
bool stay_in_interpreter) {
...
if (!method->IsStatic()) {
//add 避免使用引用类型的参数
if(result!=nullptr&&result->GetI()==111111){
shadow_frame->SetVReg(cur_reg, args[0]);
}else{
CHECK(receiver != nullptr);
shadow_frame->SetVRegReference(cur_reg, receiver);
}
//add end
//shadow_frame->SetVRegReference(cur_reg, receiver);
++cur_reg;
}
uint32_t shorty_len = 0;
const char* shorty = method->GetShorty(&shorty_len);
for (size_t shorty_pos = 0, arg_pos = 0; cur_reg < num_regs; ++shorty_pos, ++arg_pos, cur_reg++) {
DCHECK_LT(shorty_pos + 1, shorty_len);
switch (shorty[shorty_pos + 1]) {
case 'L': {
//add 避免使用引用类型的参数
if(result!=nullptr&&result->GetI()==111111){
shadow_frame->SetVReg(cur_reg, args[0]);
break;
}
//add end
ObjPtr<mirror::Object> o =
reinterpret_cast<StackReference<mirror::Object>*>(&args[arg_pos])->AsMirrorPtr();
shadow_frame->SetVRegReference(cur_reg, o);
break;
}
case 'J': case 'D': {
uint64_t wide_value = (static_cast<uint64_t>(args[arg_pos + 1]) << 32) | args[arg_pos];
shadow_frame->SetVRegLong(cur_reg, wide_value);
cur_reg++;
arg_pos++;
break;
}
default:
shadow_frame->SetVReg(cur_reg, args[arg_pos]);
break;
}
}
...
if (LIKELY(!method->IsNative())) {
//这里把我们主动调用函数的标志继续往后面传递
if(result!=nullptr&&result->GetI()==111111){
JValue r = Execute(self, accessor, *shadow_frame, *result, stay_in_interpreter);
if (result != nullptr) {
*result = r;
}
return;
}else{
JValue r = Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter);
if (result != nullptr) {
*result = r;
}
}
}
...
}
template<bool do_access_check, bool transaction_active>
ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) {
...
bool const interpret_one_instruction = ctx->interpret_one_instruction;
while (true) {
dex_pc = inst->GetDexPc(insns);
shadow_frame.SetDexPC(dex_pc);
TraceExecution(shadow_frame, inst, dex_pc);
inst_data = inst->Fetch16(0);
{
bool exit_loop = false;
InstructionHandler<do_access_check, transaction_active> handler(
ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, exit_loop);
//PREAMBLE变成这种方式调用了
if (!handler.Preamble()) {
if (UNLIKELY(exit_loop)) {
return;
}
if (UNLIKELY(interpret_one_instruction)) {
break;
}
continue;
}
}
switch (inst->Opcode(inst_data)) {
#define OPCODE_CASE(OPCODE, OPCODE_NAME, pname, f, i, a, e, v) \
case OPCODE: { \
bool exit_loop = false; \
InstructionHandler<do_access_check, transaction_active> handler( \
ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, exit_loop); \
handler.OPCODE_NAME(); \
/* TODO: Advance 'inst' here, instead of explicitly in each handler */ \
if (UNLIKELY(exit_loop)) { \
return; \
} \
break; \
}
DEX_INSTRUCTION_LIST(OPCODE_CASE)
#undef OPCODE_CASE
}
if (UNLIKELY(interpret_one_instruction)) {
break;
}
}
// Record where we stopped.
shadow_frame.SetDexPC(inst->GetDexPc(insns));
ctx->result = ctx->result_register;
return;
} // NOLINT(readability/fn_size)
template<bool do_access_check, bool transaction_active>
ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) {
...
//add
int32_t regvalue=ctx->result_register.GetI();
//这里很重要。需要把我们用来作为主动调用的值给改了。不然调用另外一个函数也会当成fart的主动调用的。
ctx->result_register=JValue();
int inst_count = -1; //当前第几个指令
bool flag=false; //第一个指令是否为goto
//add end
bool const interpret_one_instruction = ctx->interpret_one_instruction;
while (true) {
...
//add
inst_count++;
uint8_t opcode = inst->Opcode(inst_data)
//如果是主动调用
if(regvalue==111111){
//第一个指令是goto的处理
if(inst_count == 0 ){
if(opcode == Instruction::GOTO || opcode == Instruction::GOTO_16 || opcode == Instruction::GOTO_32){
LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=0 opcode==GOTO "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
flag=true;
}else{
LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=0 opcode!=GOTO "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
dumpArtMethod(shadow_frame.GetMethod());
break;
}
}
//第二个指令是const的处理
if(inst_count == 1){
if(opcode >= Instruction::CONST_4 && opcode <= Instruction::CONST_WIDE_HIGH16){
LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=1 opcode==CONST "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
flag=true;
}else{
LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=1 opcode!=CONST "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
dumpArtMethod(shadow_frame.GetMethod());
break;
}
}
}
//add end
switch (opcode) {
#define OPCODE_CASE(OPCODE, OPCODE_NAME, pname, f, i, a, e, v) \
case OPCODE: { \
bool exit_loop = false; \
InstructionHandler<do_access_check, transaction_active> handler( \
ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, exit_loop); \
handler.OPCODE_NAME(); \
/* TODO: Advance 'inst' here, instead of explicitly in each handler */ \
if (UNLIKELY(exit_loop)) { \
return; \
} \
break; \
}
DEX_INSTRUCTION_LIST(OPCODE_CASE)
#undef OPCODE_CASE
}
//add
//指令执行结束后,再判断一下是不是主动调用的
if(regvalue==111111){
//如果这是第3个指令
if(inst_count==2&&flag){
//如果是下面两种操作码,就可以脱壳并结束了。
if(opcode == Instruction::INVOKE_STATIC || opcode == Instruction::INVOKE_STATIC_RANGE){
dumpArtMethod(shadow_frame.GetMethod());
ArtMethod::disableFartextInvoke();
break;
}
}
//如果主动调用的情况还能执行到第4个指令。那就直接脱壳并结束掉。
if(inst_count>2){
dumpArtMethod(shadow_frame.GetMethod());
ArtMethod::disableFartextInvoke();
break;
}
}
//add end
if (UNLIKELY(interpret_one_instruction)) {
break;
}
}
// Record where we stopped.
shadow_frame.SetDexPC(inst->GetDexPc(insns));
ctx->result = ctx->result_register;
return;
} // NOLINT(readability/fn_size)
8
流程图
9
extern "C" void dumpArtMethod(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
//存放保存的dex路径
char *dexfilepath=(char*)malloc(sizeof(char)*1000);
if(dexfilepath==nullptr)
{
LOG(ERROR) << "ArtMethod::dumpArtMethodinvoked,methodname:"<<artmethod->PrettyMethod().c_str()<<"malloc 1000 byte failed";
return;
}
int result=0;
int fcmdline =-1;
char szCmdline[64]= {0};
char szProcName[256] = {0};
int procid = getpid();
//获取进程包名
sprintf(szCmdline,"/proc/%d/cmdline", procid);
fcmdline = open(szCmdline, O_RDONLY,0644);
if(fcmdline >0)
{
result=read(fcmdline, szProcName,256);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open cmdline file file error";
}
close(fcmdline);
}
if(szProcName[0])
{
const DexFile* dex_file = artmethod->GetDexFile();
const uint8_t* begin_=dex_file->Begin(); // Start of data.
size_t size_=dex_file->Size(); // Length of data.
memset(dexfilepath,0,1000);
int size_int_=(int)size_;
//创建目录
memset(dexfilepath,0,1000);
sprintf(dexfilepath,"%s","/sdcard/fart");
mkdir(dexfilepath,0777);
//创建目录
memset(dexfilepath,0,1000);
sprintf(dexfilepath,"/sdcard/fart/%s",szProcName);
mkdir(dexfilepath,0777);
//文件大小_dexfile.dex
memset(dexfilepath,0,1000);
sprintf(dexfilepath,"/sdcard/fart/%s/%d_dexfile.dex",szProcName,size_int_);
int dexfilefp=open(dexfilepath,O_RDONLY,0666);
//存在则略过
if(dexfilefp>0){
close(dexfilefp);
dexfilefp=0;
}else{
//dex的数据保存
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) << "ArtMethod::dumpdexfilebyArtMethod,open dexfilepath file error";
}
fsync(fp);
close(fp);
memset(dexfilepath,0,1000);
//保存对应的classlist
sprintf(dexfilepath,"/sdcard/fart/%s/%d_classlist.txt",szProcName,size_int_);
int classlistfile=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
if(classlistfile>0)
{
for (size_t ii= 0; ii< dex_file->NumClassDefs(); ++ii)
{
const DexFile::ClassDef& class_def = dex_file->GetClassDef(ii);
const char* descriptor = dex_file->GetClassDescriptor(class_def);
result=write(classlistfile,(void*)descriptor,strlen(descriptor));
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
}
const char* temp="\n";
result=write(classlistfile,(void*)temp,1);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
}
}
fsync(classlistfile);
close(classlistfile);
}
}
}
//获取codeItem
const DexFile::CodeItem* code_item = artmethod->GetCodeItem();
if (LIKELY(code_item != nullptr))
{
int code_item_len = 0;
uint8_t *item=(uint8_t *) code_item;
//计算codeitem的大小
if (code_item->tries_size_>0) {
const uint8_t *handler_data = (const uint8_t *)(DexFile::GetTryItems(*code_item, code_item->tries_size_));
uint8_t * tail = codeitem_end(&handler_data);
code_item_len = (int)(tail - item);
}else{
code_item_len = 16+code_item->insns_size_in_code_units_*2;
}
//下面就是获取codeitem的idx和偏移,大小之类的。然后写入数据保存了
memset(dexfilepath,0,1000);
int size_int=(int)dex_file->Size();
uint32_t method_idx=artmethod->GetDexMethodIndexUnchecked();
sprintf(dexfilepath,"/sdcard/fart/%s/%d_ins_%d.bin",szProcName,size_int,(int)gettidv1());
int fp2=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
if(fp2>0){
//跳到文件末尾写入
lseek(fp2,0,SEEK_END);
memset(dexfilepath,0,1000);
int offset=(int)(item - begin_);
sprintf(dexfilepath,"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",artmethod->PrettyMethod().c_str(),method_idx,offset,code_item_len);
int contentlength=0;
while(dexfilepath[contentlength]!=0) contentlength++;
result=write(fp2,(void*)dexfilepath,contentlength);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
}
long outlen=0;
char* base64result=base64_encode((char*)item,(long)code_item_len,&outlen);
result=write(fp2,base64result,outlen);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
}
result=write(fp2,"};",2);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
}
fsync(fp2);
close(fp2);
if(base64result!=nullptr){
free(base64result);
base64result=nullptr;
}
}
}
}
if(dexfilepath!=nullptr)
{
free(dexfilepath);
dexfilepath=nullptr;
}
}
10
11
function romClassesInvoke(classes){
Java.perform(function(){
klog("romClassesInvoke start load");
var fartExt=Java.use("cn.mik.Fartext");
if(!fartExt.fartWithClassList){
klog("fartExt中未找到fartWithClassList函数,可能是未使用Fartext的rom")
return ;
}
fartExt.fartWithClassList(classes);
})
}
function romFartAllClassLoader(){
Java.perform(function(){
var fartExt=Java.use("cn.mik.Fartext");
if(!fartExt.fartWithClassLoader){
klog("fartExt中未找到fartWithClassLoader函数,可能是未使用Fartext的rom");
return;
}
Java.enumerateClassLoadersSync().forEach(function(loader){
klog("romFartAllClassLoader to loader:"+loader);
if(loader.toString().indexOf("BootClassLoader")==-1){
klog("fart start loader:"+loader);
fartExt.fartWithClassLoader(loader);
}
})
});
}
12
思考
《安卓高级研修班(网课)》
春季班开始招生!
有一定基础的初、中级安卓逆向研究员,迫切希望提高自身能力、学习能力强,升职加薪意愿强烈、学习意愿强烈。
专属班主任,敦促学习、鼓励士气;良好的抱团学习的氛围;
可以参加《安卓高级研修班》线下班,鼓励线下交流,与大佬谈笑风生;
注意2W班和3W班是完全独立噢,没有交集;
开班时间:2022年春季开班
PS:以上为总体服务计划,具体课程时间(段)安排以最终合同约定的课程表为准。
高研网课 | 就业班 | 强化班 |
月薪三万计划 | 16999元 | 8599元 |
月薪两万计划 | 11199元 | 5599元 |
就业班附带包就业服务(须达到合同规定的毕业标准),签合同保证就业及薪资,达不到退全款;
就业班有入学考核,缴费成功后进入考核流程,考核不通过退全款;
考核流程会包括简历筛选、班主任和老师(电话)面试等环节;
强化班仅去除包就业服务,并且无入学考核,其余与就业班完全相同;
就业班与强化班一起授课,合计35人一个班,教学上不做任何区分。
《安卓高级研修班》全系列无任何金融计划,纯预付;无任何金融套路。
网络课程为虚拟商品,购买之前可以观看下述试看内容,购买成功之后不接受退款。
扫码立即报名!
扫码免费试看
扫码免费试看
免责条款
以上所有宣传稿件内容均不作为服务承诺,最终以实际签订培训合同为准。
课程大纲与细目会根据教学反馈不断优化、调整与更新,实际授课可能与宣传主题略有不同;
Q:有优惠么?!有优惠么?!有优惠么?!重要的事情说三遍!!
一部pixel手机(sailfish)(安卓8脱壳镜像)
安卓源码开发编译调试环境SSD移动硬盘500G
A:月薪三万计划的内容与线下班的内容是一样的,我们在线下班沉淀大家的切实的需求和疑问,重新编排和制作内容作为网课与大家分享。月薪两万计划的内容由三万计划的讲师全新制作,充分体现工作场景一线的需求,更加贴近实战、实用,有用、好用。
A:目前针对ollvm和vmp,任何所谓的自动化,都是带很多前提和条件限制的;目前最快的还原ollvm或vmp的方法,还是手动分析,一般快则两三日、慢则一两周,基本上可以还原出来。
A:月薪两万计划推荐至少有实际安卓安全岗位工作经验一年以上为宜。初学者可以先看我们安卓版主非虫大佬的《Android软件安全权威指南》等安卓安全书籍进行入门,在看雪论坛看帖发帖提升自身水平,本套课程建议有工作经验的老手前来充电学习。
月薪三万计划视大家实际需求而定,一般看得懂目录及想要学习的人自己就懂,大家不用盲目跟风。如果看不懂目录及不理解目录的具体含义及意义,建议先从两万计划学起,多积累技术和经验。
A:不需要,互相独立的。月薪两万计划的定位更加偏向工作岗位一线逆向需求,月薪三万计划则更加偏向于高级调试技巧,二者互为补充,相辅相成。有非常多地大佬两个计划一起报名了,我们也会确保直播时间不会冲突。
A:每一场直播都有回放,在看雪课程中可以观看。
A:就业班是需要考核的。考核流程是先缴费报名,然后开始。会经过简历、(远程)一面和二面。通过之后补差价,不通过退全款。
# 十一月
# 十月
《dexvmp后的算法逆向分析和还原》
《使用unicorn对ollvm字符串进行解密》
《Frida追踪定Socket接口自吐游戏APK的服务器IP和地址》
Frida hook Java/Native与init_array 自吐最终方案 》
# 九月
《macOS安装调试llvm入门》
《fart的理解和分析过程》
《使用ollvm自定义简单的字符串加密》
《使用ida trace来还原ollvm混淆的非标准算法》
# 八月
# 七月
# 六月
从三道题目入手入门frida
单纯使用Frida书写类抽取脱壳工具的一些心路历程和实践
某聊天app的音视频通话逆向
# 五月
初试IDA&FRIDA联合调试简单ollvm保护的加密函数源码
# 四月
某抽取壳的原理简析
frida辅助脱壳
# 三月
报 名 地 址
扫码立即报名!
球分享
球点赞
球在看
点击“阅读原文”,了解更多!