前言
最近对某银行app进行了重新逆向,发现他的安全防护措施几个月来并没有更新,还是很容易就搞定了反调试,它的表现是在一开头就把自己ptrace上导致你无法使用Frida进行hook,但是用-f参数进行spawn模式启动就能成功hook上了。现在的app如果在so层做加壳反调之类的一般都会加上ollvm,对逆向人员极不友好,我遇到的这个样本是难得的没有用ollvm的样本,所以虽然成功绕过了它的反调,那就顺便拿它来学习一下它是如何做防护的。
0x01 SO Header破坏与修复
首先这个so是没有对头部进行破坏的,直接拖到IDA中就能看,所以顺便说下破坏so是啥情况正好也遇到了一个case。这个case叫的壳叫vdog,但是lib目录没有这个so,然后在它的生命周期函数里找到这个初始化函数,发现它的so藏在asset的main000目录下,还有dex文件也是。直接把so拖到ida发现无法识别,肯定是被处理过了。看看下面的函数
public static void a(Context context) {
String soPath;
try {
Defines.context = context;
StringBuilder v16 = new StringBuilder().append(context.getApplicationInfo().dataDir);
a.a = v16.append("/").toString();
StringBuilder v16_1 = new StringBuilder().append(a.a);
a.a(v16_1.append(".cache/").toString());
StringBuilder v16_2 = new StringBuilder().append(a.a);
a.a(v16_2.append(".local/").toString());
StringBuilder v16_3 = new StringBuilder().append(a.a);
a.a(v16_3.append(".meta-inf/").toString());
String soName = a.b("libvdog.so");
StringBuilder v16_4 = new StringBuilder().append(a.a);
StringBuilder v16_5 = v16_4.append(".cache/");
soPath = v16_5.append("libvdog.so").toString();
String soSqliteName = a.b("libsqlite_encrypt.so");
StringBuilder v16_6 = new StringBuilder().append(a.a);
StringBuilder v16_7 = v16_6.append(".cache/");
String soSqilitePath = v16_7.append("libsqlite_encrypt.so").toString();
String soNmgName = a.b("libnMg.so");
StringBuilder v16_8 = new StringBuilder().append(a.a);
StringBuilder v16_9 = v16_8.append(".cache/");
String soNmgPath = v16_9.append("libnMg.so").toString();
StringBuilder v16_10 = new StringBuilder().append(a.a);
StringBuilder v16_11 = v16_10.append(".cache/");
String _main_data = v16_11.append("main.data").toString();
StringBuilder v16_12 = new StringBuilder().append(a.a);
StringBuilder v16_13 = v16_12.append(".cache/");
String _data_rc = v16_13.append("datarc").toString();
StringBuilder v16_14 = new StringBuilder().append(a.a);
StringBuilder v16_15 = v16_14.append(".cache/");
String _res_data = v16_15.append("res.data").toString();
StringBuilder v16_16 = new StringBuilder().append(a.a);
StringBuilder v16_17 = v16_16.append(".cache/");
String _cert0 = v16_17.append("cert0").toString();
StringBuilder v16_18 = new StringBuilder().append(a.a);
StringBuilder v16_19 = v16_18.append(".meta-inf/");
String _enc_mf = v16_19.append("enc.mf").toString();
StringBuilder v16_20 = new StringBuilder().append(a.a);
StringBuilder v16_21 = v16_20.append(".cache/");
String tablePath = v16_21.append("table.dat").toString();
StringBuilder v16_22 = new StringBuilder().append(a.a);
StringBuilder v16_23 = v16_22.append(".cache/");
String screenTable = v16_23.append("scrtable.dat").toString();
if(!a.b(context) && (new File(soPath).exists())) {
StringBuilder v17 = new StringBuilder().append(a.a);
String v17_1 = v17.append(".cache/").toString();
if(new File(v17_1, "classes.dex").exists()) {
goto label_261;
}
goto label_152;
}
else {
label_152:
StringBuilder v16_24 = new StringBuilder().append(a.a);
a.c(v16_24.append(".cache/").toString());
StringBuilder v16_25 = new StringBuilder().append(a.a);
a.c(v16_25.append(".local/").toString());
StringBuilder v16_26 = new StringBuilder().append(a.a);
a.c(v16_26.append(".meta-inf/").toString());
a.a(_main_data, "main000/main.data", context);
a.a(_data_rc, "main000/datarc", context);
a.a(_res_data, "main000/res.data", context);
a.a(_cert0, "main000/cert0", context);
a.a(_enc_mf, "meta-inf/enc.mf", context);
a.a(tablePath, context);
a.a(screenTable, "main000/scrtable.dat", context);
StringBuilder v16_27 = new StringBuilder();
a.a(soPath, v16_27.append("main000/").append(soName.substring(0, soName.length() - 3)).toString(), context);
StringBuilder v16_28 = new StringBuilder();
a.a(soSqilitePath, v16_28.append("main000/").append(soSqliteName.substring(0, soSqliteName.length() - 3)).toString(), context);
StringBuilder v16_29 = new StringBuilder();
a.a(soNmgPath, v16_29.append("main000/").append(soNmgName.substring(0, soNmgName.length() - 3)).toString(), context);
StringBuilder v16_30 = new StringBuilder().append(a.a);
a.a(context, v16_30.append(".cache/").toString());
a.c(context);
}
}
catch(Exception e) {
goto label_270;
}
try {
label_261:
System.loadLibrary("libvdog.so".substring(3, "libvdog.so".length() - 3));
return;
}
catch(Throwable e) {
}
try {
System.load(soPath);
return;
}
catch(Exception e) {
}
label_270:
e.printStackTrace();
}
最后so文件的读取是这个函数
private static void a(String output, String input, Context context) {
try {
InputStream inputStream = context.getAssets().open(input);
File localFile = new File(output);
byte[] bytes = new byte[0x10000];
BufferedInputStream bufferedInput = new BufferedInputStream(inputStream);
BufferedOutputStream bufferedOutput = new BufferedOutputStream(new FileOutputStream(localFile));
int first = 1;
byte[] v7 = new byte[]{0x7F, 69, 76, 70};
while(true) {
int i = bufferedInput.read(bytes);
if(first != 0) {
first = 0;
if(output.endsWith(".so")) {
System.arraycopy(((Object)v7), 0, ((Object)bytes), 0, v7.length);
}
}
if(i <= 0) {
bufferedOutput.flush();
bufferedOutput.close();
bufferedInput.close();
return;
}
bufferedOutput.write(bytes, 0, i);
}
}
catch(Exception v8) {
return;
}
}
原来它把elf的magic number改了,拖到010editor里发现被改成了GODV,根据这个代码new byte[]{0x7F, 69, 76, 70}
改回去就行了,然后拖到ida里还是会报错,因为它把header改的面目全非了,参考这篇文章,虽然一直点确认也勉强能看但是还原一下更方便我们看。如果对ELF格式很熟悉的话可以手动还原,不过懒狗找到了这个工具,很方便能直接修复SO,修复完成后拖到IDA里就能不报错了。但是很可惜这个SO基本上所有函数都有ollvm的控制流平坦化,放弃了。
0x02 入口函数
SO文件被加载以后首先会执行init函数(如果有的话),然后执行JNI_Onload,这个顺序是由安卓的linker决定的,在安卓源码中可以看到,相关的文章有很多,随便列一篇。看了下init.array里只有一个叫setup_opt_hookv
的函数,和反调试无关,所以直接快进到JNI_Onload
函数
JNI_Onload
函数开头一堆初始化相关代码具体干啥的没注意,不过在最底下有个很显眼的protect_self函数,直接快进康康它是怎么实现的。
0x03 protect_self
完整代码如下:
__pid_t __fastcall protect_self(char a1, int a2, int a3)
{
//省略无关代码
prctl(4, 1, 0, 0, 0);
result = getpid();
if ( result == javapid )
{
v4 = getpid();
javapid = v4;
if ( (a1 & 2) != 0 )
{
v4 = sub_3902C(a2);
if ( !v4 )
{
v5 = getpid();
v4 = sub_3A5F8(v5, a3, a2);
}
}
if ( (a1 & 4) != 0 )
sub_38D90(v4);
if ( (a1 & 8) != 0 )
sub_3B54C(a2);
if ( (a1 & 1) != 0 )
{
result = protect_polling(javapid);
}
else
{
pipe(&v21);
pipe(v23);
pipe(v24);
v14 = sub_38F8C(a2);
v15 = fork();
if ( v15 <= 0 )
{
if ( v15 )
{
result = protect_polling(javapid);
}
else
{
close(v21);
close(v24[0]);
close(v23[1]);
anti_thread_of_process_debug(javapid);
monitor_env(javapid);
if ( !v14 )
{
v19 = dlopen("/system/lib/libc.so", 0);
hookFun(v19, "ptrace", (int)ptrace_stub, (int)&libc_ptrace);
}
v12 = 0;
if ( libc_ptrace(16, javapid, 0, 0) < 0 )
v12 = -1;
if ( !v12 )
{
wait(&v10);
libc_ptrace(7, javapid, 0, 0);
}
v25[0] = v12;
write(fd, v25, 1u);
arg = malloc(8u);
memset(arg, 0, 8u);
*(_DWORD *)arg = v23[0];
*((_DWORD *)arg + 1) = getpid();
if ( v12 )
{
result = keep_pipe_connect(arg);
}
else
{
for ( i = 30; pthread_create(&v10, 0, (void *(*)(void *))keep_pipe_connect, arg) && i > 0; --i )
sleep(1u);
handle_catched_signal(javapid);
libc_ptrace(17, javapid, 0, 0);
v6 = getpid();
mykill(v6, 9);
result = mykill(javapid, 9);
}
}
}
else
{
close(fd);
close(v24[1]);
close(v23[0]);
v16 = dlopen("/system/lib/libc.so", 0);
hookFun(v16, "ptrace", (int)ptrace_stub, (int)&libc_ptrace);
anti_thread_of_process_debug(v15);
monitor_env(v15);
result = read(v21, v25, 1u);
v17 = result;
if ( result != -1 && !v25[0] )
{
s = malloc(8u);
memset(s, 0, 8u);
*(_DWORD *)s = v24[0];
*((_DWORD *)s + 1) = javapid;
for ( j = 30; ; --j )
{
result = pthread_create(&v10, 0, (void *(*)(void *))keep_pipe_connect, s);
if ( !result || j <= 0 )
break;
sleep(1u);
}
}
}
}
}
return result;
}
先来看下第一个函数sub_3902C
bool __fastcall sub_3902C(int a1)
{
char v3[64]; // [sp+Ch] [bp+Ch] BYREF
if ( a1 != 23 )
return 0;
sub_38F44("ro.product.model", v3, &unk_671F8);
return strcmp(v3, "HUAWEI eH880") == 0;
}
只是查看机型是否为HUAWEI eH880意义不大,下一个
sub_3A5F8
int __fastcall sub_3A5F8(__pid_t a1, int a2, int a3)
{
int result; // r0
pthread_t v6; // [sp+10h] [bp+10h] BYREF
pthread_t v7; // [sp+14h] [bp+14h] BYREF
int i; // [sp+18h] [bp+18h]
void *arg; // [sp+1Ch] [bp+1Ch]
void *v10; // [sp+20h] [bp+20h]
if ( check_xposed(a2) )
mykill(a1, 9);
arg = malloc(4u);
*(_DWORD *)arg = a1;
for ( i = 30; pthread_create(&v6, 0, (void *(*)(void *))scan_proc_jars, arg) && i > 0; --i )
sleep(1u);
result = sub_3A55C(a3);
if ( !result )
{
v10 = malloc(4u);
*(_DWORD *)v10 = a1;
for ( i = 30; ; --i )
{
result = pthread_create(&v7, 0, (void *(*)(void *))scan_proc_libs, v10);
if ( !result || i <= 0 )
break;
sleep(1u);
}
}
return result;
}
可以看到这边对xposed进行了检测,康康具体是怎么做的
check_xposed里有sub_3CBE8、sub_3CD48和sub_3CE04三个函数,一个一个看
sub_3CBE8
{
_BOOL4 v1; // r3
int v4; // [sp+8h] [bp+8h]
int v5; // [sp+Ch] [bp+Ch]
int v6; // [sp+10h] [bp+10h]
int v7; // [sp+14h] [bp+14h]
int v8; // [sp+18h] [bp+18h]
int v9; // [sp+1Ch] [bp+1Ch]
int v10; // [sp+20h] [bp+20h]
int v11; // [sp+24h] [bp+24h]
int v12; // [sp+28h] [bp+28h]
int v13; // [sp+2Ch] [bp+2Ch]
char *haystack; // [sp+30h] [bp+30h]
int v15; // [sp+34h] [bp+34h]
int v16; // [sp+38h] [bp+38h]
char *v17; // [sp+3Ch] [bp+3Ch]
v4 = _JNIEnv::FindClass(a1, "java/lang/Thread");
v5 = _JNIEnv::GetStaticMethodID(a1, v4, "currentThread", "()Ljava/lang/Thread;");
v6 = _JNIEnv::CallStaticObjectMethod(a1, v4, v5);
v7 = _JNIEnv::GetMethodID(a1, v4, "getStackTrace", "()[Ljava/lang/StackTraceElement;");
v8 = _JNIEnv::CallObjectMethod(a1, v6, v7);
v9 = _JNIEnv::GetArrayLength(a1, v8);
v10 = _JNIEnv::GetObjectArrayElement(a1, v8, v9 - 1);
v11 = _JNIEnv::FindClass(a1, "java/lang/StackTraceElement");
v12 = _JNIEnv::GetMethodID(a1, v11, "getClassName", "()Ljava/lang/String;");
v13 = _JNIEnv::CallObjectMethod(a1, v10, v12);
haystack = (char *)_JNIEnv::GetStringUTFChars(a1, v13, 0);
v15 = _JNIEnv::GetObjectArrayElement(a1, v8, v9 - 2);
v16 = _JNIEnv::CallObjectMethod(a1, v15, v12);
v17 = (char *)_JNIEnv::GetStringUTFChars(a1, v16, 0);
if ( strcasestr(haystack, "xposed") )
v1 = 1;
else
v1 = strcasestr(v17, "xposed") != 0;
return v1;
}
通过打印函数调用栈检测是否有xposed特征
sub_3CD48
int __fastcall sub_3CD48(_JNIEnv *a1)
{
int v5; // [sp+8h] [bp+8h]
int v6; // [sp+Ch] [bp+Ch]
int v7; // [sp+10h] [bp+10h]
int v8; // [sp+14h] [bp+14h]
v5 = _JNIEnv::FindClass(a1, "java/lang/ClassLoader");
v6 = _JNIEnv::GetStaticMethodID(a1, v5, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
v7 = _JNIEnv::CallStaticObjectMethod(a1, v5, v6);
v8 = _JNIEnv::GetMethodID(a1, v5, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
_JNIEnv::NewStringUTF(a1, "de.robv.android.xposed.XposedHelpers");
if ( _JNIEnv::CallObjectMethod(a1, v7, v8) && !_JNIEnv::ExceptionCheck(a1) )
return 1;
_JNIEnv::ExceptionClear(a1);
return 0;
}
通过类加载器主动加载Xposed根据加载结果来判断是否存在Xposed框架
sub_3CE04
int __fastcall sub_3CE04(_JNIEnv *a1)
{
int v5; // [sp+8h] [bp+8h]
int v6; // [sp+Ch] [bp+Ch]
v5 = _JNIEnv::FindClass(a1, "android/os/ServiceManager");
v6 = _JNIEnv::GetStaticMethodID(a1, v5, "getService", "(Ljava/lang/String;)Landroid/os/IBinder;");
_JNIEnv::NewStringUTF(a1, "user.xposed.system");
if ( _JNIEnv::CallStaticObjectMethod(a1, v5, v6) && !_JNIEnv::ExceptionCheck(a1) )
return 1;
_JNIEnv::ExceptionDescribe(a1);
_JNIEnv::ExceptionClear(a1);
return 0;
}
通过判断ServiceManager里是否有Xposed的Service来检测
回到sub_3A5F8,如果check_xposed返回true则调用mykill函数来关闭自身进程,来康康mykill函数
unsigned int __fastcall mykill(__pid_t a1, int a2)
{
unsigned int result; // r0
result = linux_eabi_syscall(__NR_kill, a1, a2);
if ( result > 0xFFFFF000 )
result = j_my_set_errno(-result);
return result;
}
可以看到是用系统调用来关闭自己的,可以防止exit函数被hook的情况。看下它的汇编代码
.text:0003895C EXPORT mykill
.text:0003895C mykill ; CODE XREF: monitor_pid+2E↑p
.text:0003895C ; monitor_pid+3C↑p ...
.text:0003895C ; __unwind {
.text:0003895C PUSH {R4-R7,R12,LR}
.text:00038960 MOV R7, #0x25 ; '%'
.text:00038964 SVC 0
.text:00038968 POP {R4-R7,R12,LR}
.text:0003896C CMN R0, #0x1000
.text:00038970 BXLS LR
.text:00038974 RSB R0, R0, #0
.text:00038978 B j_my_set_errno
.text:00038978 ; } // starts at 3895C
.text:00038978 ; End of function mykill
里边有个SVC指令,就是用来执行系统调用的,具体的调用号通过R7寄存器传参,这边0x25就是指__NR_kill,有个系统调用的表来映射调用号与其具体的功能。可以参考这篇文章
接下来看sub_3A5F8中的scan_proc_jars函数
scan_proc_jars
int __fastcall scan_proc_jars(__pid_t *a1)
{
int v2; // [sp+Ch] [bp+Ch]
FILE *v3; // [sp+10h] [bp+10h]
char v4[256]; // [sp+14h] [bp+14h] BYREF
char v5[1024]; // [sp+114h] [bp+114h] BYREF
char v6[1024]; // [sp+514h] [bp+514h] BYREF
v2 = *a1;
free(a1);
sub_5B390((int)v4, 256, "/proc/%d/maps", v2);
v3 = fopen(v4, "r");
if ( v3 )
{
memset(v5, 0, sizeof(v5));
memset(v6, 0, sizeof(v6));
sub_39E0C(v2, (int)v6);
while ( fscanf(v3, "%*p-%*p %*c%*c%*c%*c %*s %*s %*d%1023[^\n]", v5) == 1 )
{
readLine((int)v5);
if ( v5[0] && jar_filter(v5, v6) && is_xposed_att(v5) )
mykill(v2, 9);
}
fclose(v3);
}
return 0;
}
可以知道就是从/proc/pid/maps
中读取内容然后进行jar_filter和is_xposed_att两个函数的检测。jar_filter没懂是干啥的,来看看is_xposed_att
is_xposed_att
bool __fastcall is_xposed_att(const char *a1)
{
_BOOL4 v1; // r3
if ( a1 )
v1 = strcasestr(a1, "xposedbridge") || strcasestr(a1, ".xposed.") || strcasestr(a1, "xposed_art");
else
v1 = 0;
return v1;
}
也是xposed的一些特征(为啥一直针对xposed)
接下来看sub_3A5F8中的scan_proc_libs函数
int __fastcall scan_proc_libs(int *a1)
{
size_t v1; // r0
char v3; // [sp+Fh] [bp+Fh] BYREF
int v4; // [sp+10h] [bp+10h]
FILE *v5; // [sp+14h] [bp+14h]
const char *v6; // [sp+18h] [bp+18h]
char v7[256]; // [sp+1Ch] [bp+1Ch] BYREF
char v8[1024]; // [sp+11Ch] [bp+11Ch] BYREF
_BYTE v9[1024]; // [sp+51Ch] [bp+51Ch] BYREF
v4 = *a1;
free(a1);
sub_5B390((int)v7, 256, "/proc/%d/maps", v4);
v5 = fopen(v7, "r");
if ( v5 )
{
memset(v8, 0, sizeof(v8));
memset(v9, 0, sizeof(v9));
sub_39E0C(v4, (int)v9);
v3 = 0;
while ( fscanf(v5, "%*p-%*p %*c%*c%c%*c %*s %*s %*d%1023[^\n]", &v3, v8) == 2 )
{
readLine((int)v8);
if ( v3 == 120 )
{
v6 = "/system/bin/app_process";
v1 = strlen("/system/bin/app_process");
if ( !strncmp(v8, v6, v1) )
{
if ( sub_3A30C(v8) == 1 )
mykill(v4, 9);
}
else if ( so_filter(v8, v9) && find_hook_feature(v8) == 1 )
{
mykill(v4, 9);
}
}
}
fclose(v5);
}
return 0;
}
和之前的差不多,也是读maps,看看find_hook_feature有啥宝贝
find_hook_feature
int __fastcall find_hook_feature(const char *a1)
{
// 此处省略无关代码
v6 = 0;
if ( strcasestr(a1, "substrate") || strcasestr(a1, "frida") )
{
v6 = 1;
v1 = 1;
}
else
{
v10 = "substrate";
v11 = "adbi_hook";
v12 = "ALLINONEs_arthook";
v13 = "ddi_hook";
v14 = "dexposed";
v15 = "frida";
v16 = "MSHookFunction";
v17 = "MSFindSymbol";
v18 = "MSCloseFunction";
v19 = "hook_postcall";
v20 = "hook_precall";
v21 = "dalvik_java_method_hook";
v22 = "art_java_method_hook";
v23 = "art_quick_call_entrypoint";
v24 = "artQuickToDispatcher";
v25 = "dexstuff_defineclass";
v26 = "dexstuff_loaddex";
v27 = "dexstuff_resolv_dvm";
v28 = "DexposedBridge";
v29 = "dexposedIsHooked";
v30 = "dexposedCallHandler";
v31 = "frida_agent_main";
v32 = "MSHookFunction";
v33 = "substrate";
v34 = "MSFindSymbol";
v35 = "substrate";
v36 = "MSCloseFunction";
v37 = "substrate";
v38 = "hook_postcall";
v39 = "adbi_hook";
v40 = "hook_precall";
v41 = "adbi_hook";
v42 = "dalvik_java_method_hook";
v43 = "ALLINONEs_arthook";
v44 = "art_java_method_hook";
v45 = "ALLINONEs_arthook";
v46 = "art_quick_call_entrypoint";
v47 = "ALLINONEs_arthook";
v48 = "artQuickToDispatcher";
v49 = "ALLINONEs_arthook";
v50 = "dexstuff_defineclass";
v51 = "ddi_hook";
v52 = "dexstuff_loaddex";
v53 = "ddi_hook";
v54 = "dexstuff_resolv_dvm";
v55 = "ddi_hook";
v56 = "DexposedBridge";
v57 = "dexposed";
v58 = "dexposedIsHooked";
v59 = "dexposed";
v60 = "dexposedCallHandler";
v61 = "dexposed";
v62 = "frida_agent_main";
v63 = "frida";
ptr = 0;
v5 = 0;
if ( !sub_39930(a1, &ptr, &v5) )
{
v7 = 0;
i = 0;
while ( !v6 && v7 <= 0xF )
{
for ( i = 0; i < v5; ++i )
{
if ( !strcmp((const char *)*(&v64 + 2 * v7 - 33), *((const char **)ptr + 3 * i)) )
{
v6 = 1;
break;
}
}
++v7;
}
if ( ptr )
{
for ( j = 0; j < v5; ++j )
free(*((void **)ptr + 3 * j));
free(ptr);
}
}
v1 = v6;
}
return v1;
}
好家伙,还是列了不少特征的,但是不顶用啊,谁用frida-server不改个名字?
还有个so_filter暂时不知道干啥的
int __fastcall so_filter(const char *a1, const char *a2)
{
int v2; // r3
size_t v3; // r0
size_t v4; // r0
size_t v5; // r0
size_t v6; // r0
size_t v7; // r0
size_t v8; // r0
size_t v9; // r0
size_t v10; // r0
size_t v11; // r0
size_t v12; // r0
if ( !*a1 || !a1 || !a2 )
return 0;
v3 = strlen("/system/");
if ( !strncmp(a1, "/system/", v3) )
goto LABEL_16;
v4 = strlen("/dev/");
if ( !strncmp(a1, "/dev/", v4) )
goto LABEL_16;
v5 = strlen("/data/dalvik-cache/");
if ( !strncmp(a1, "/data/dalvik-cache/", v5)
|| (v6 = strlen("/cache/dalvik-cache/"), !strncmp(a1, "/cache/dalvik-cache/", v6))
|| (v7 = strlen("/vendor/"), !strncmp(a1, "/vendor/", v7))
|| (v8 = strlen("[vector]"), !strncmp(a1, "[vector]", v8))
|| (v9 = strlen("[vectors]"), !strncmp(a1, "[vectors]", v9))
|| (v10 = strlen("[sigpage]"), !strncmp(a1, "[sigpage]", v10))
|| (v11 = strlen("[vdso]"), !strncmp(a1, "[vdso]", v11))
|| (v12 = strlen("[vsyscall]"), !strncmp(a1, "[vsyscall]", v12))
|| strstr(a1, a2) )
{
LABEL_16:
v2 = 0;
}
else
{
v2 = 1;
}
return v2;
}
看完了,回到protect_self看下一个函数:sub_38D90
sub_38D90
int sub_38D90()
{
int result; // r0
pthread_t v1; // [sp+4h] [bp+4h] BYREF
int i; // [sp+8h] [bp+8h]
for ( i = 30; ; --i )
{
result = pthread_create(&v1, 0, (void *(*)(void *))scan_gettimeofday, 0);
if ( !result || i <= 0 )
break;
sleep(1u);
}
return result;
}
看到scan_gettimeofday应该是和时间相关的反调了,网上也能找到相关的内容
scan_gettimeofday
void __noreturn scan_gettimeofday()
{
__pid_t v0; // r0
double v1; // [sp+10h] [bp+8h]
double v2; // [sp+18h] [bp+10h]
int v3; // [sp+24h] [bp+1Ch] BYREF
struct timeval v4; // [sp+2Ch] [bp+24h] BYREF
int v5; // [sp+34h] [bp+2Ch] BYREF
struct timezone v6; // [sp+3Ch] [bp+34h] BYREF
int v7[2]; // [sp+44h] [bp+3Ch] BYREF
int v8; // [sp+4Ch] [bp+44h] BYREF
int v9; // [sp+50h] [bp+48h]
v7[0] = 0;
v7[1] = 0;
v8 = 0;
v9 = 0;
while ( 1 )
{
sub_38BB0(78, &v3, &v5, 0, 0);
gettimeofday(&v4, &v6);
if ( v4.tv_sec == v3 || sub_38B94(v4.tv_sec - v3) <= 1 )
v1 = (double)v4.tv_sec / (double)v3;
else
v1 = 0.0;
sub_38BB0(263, 1, v7, 0, 0);
clock_gettime(1, (struct timespec *)&v8);
if ( v8 == v7[0] || sub_38B94(v8 - v7[0]) <= 1 )
v2 = (double)v8 / (double)v7[0];
else
v2 = 0.0;
if ( v1 > 1.001 || v1 < 0.99 || v2 > 1.001 || v2 < 0.99 )
{
v0 = getpid();
mykill(v0, 9);
}
sleep(2u);
}
}
这边可以看到它不仅同时使用了gettimeofday和clock_gettime这两个函数,他还调用了两次sub_38BB0这个函数分别传参78和263,即系统调用表中的__NR_gettimeofday
和__NR_clock_gettime
,以防止有人hook libc
再往下看sub_3B54C,这个暂时不知道要干啥了,貌似是hook了dlopen,原因还没仔细看
int __fastcall sub_3B54C(int a1)
{
void *(**v3)(const char *, int); // [sp+Ch] [bp+Ch]
dword_84A14 = a1;
v3 = &dlopen;
sub_3B190();
if ( dword_84A04 && (dword_84A04 > (unsigned int)&dlopen || dword_84A08 < (unsigned int)&dlopen) && dword_849E0 )
v3 = (void *(**)(const char *, int))dword_849E0;
if ( a1 > 23 && dword_849E8 )
v3 = (void *(**)(const char *, int))dword_849E8;
return hookFunEx(v3, sub_3B4C4, &dword_84A18);
}
接下来看这个protect_polling函数,往里跟发现两个主要函数check_process_stopped和scan_process_threads
check_process_stopped
int __fastcall check_process_stopped(int a1)
{
//省略
v7 = "/proc/%ld/status";
sub_5B390((int)v15, 256, "/proc/%ld/status", a1);
v6 = 0;
v8 = fopen(v15, "r");
if ( !v8 )
return v6;
v5 = 0;
v9 = "TracerPid:";
v10 = "State:";
v11 = "T (stopped)";
v12 = "(zombie)";
v13 = "t (tracing stop)";
while ( fgets(v16, 1024, v8) )
{
v2 = strlen(v10);
if ( !strncmp(v16, v10, v2) && (strcasestr(v16, v11) || strcasestr(v16, v12) || strcasestr(v16, v13)) )
{
while ( fgets(v17, 1024, v8) )
{
v3 = strlen(v9);
if ( !strncmp(v17, v9, v3) )
{
sscanf(v17, "%s %d", &v14, &v5);
break;
}
}
if ( v5 && getpid() != v5 )
v6 = 1;
break;
}
}
fclose(v8);
return v6;
}
经典反调操作,读status看自己是否被ptrace
看看scan_process_threads
int __fastcall scan_process_threads(int a1)
{
int v3; // [sp+4h] [bp+4h]
int v4; // [sp+8h] [bp+8h]
DIR *v5; // [sp+Ch] [bp+Ch]
struct dirent *v6; // [sp+10h] [bp+10h]
const char *v7; // [sp+14h] [bp+14h]
int v8; // [sp+18h] [bp+18h]
char v9[256]; // [sp+1Ch] [bp+1Ch] BYREF
v3 = a1;
sub_5B3D8(v9, "/proc/%ld/task/", a1);
v5 = opendir(v9);
if ( !v5 )
return 0;
v4 = 0;
while ( 1 )
{
v6 = readdir(v5);
if ( !v6 )
break;
v7 = &v6->d_name[8];
if ( v6 != (struct dirent *)-19 )
{
if ( strcmp(v7, ".") )
{
if ( strcmp(v7, "..") )
{
v8 = atol(&v6->d_name[8]);
if ( v3 != v8 )
{
if ( sub_3C41C(v3, v8) )
{
v4 = 1;
break;
}
}
}
}
}
}
closedir(v5);
return v4;
}
这个不清楚读task有啥用,下一个
sub_38F8C又是检查手机型号是三星或者华为的,原因不明再下一个,anti_thread_of_process_debug和protect_polling是同一个,再下一个。。。好像很多都是重复了
再往下看大概就是hook了ptrace函数替换成自己的,然后调用ptrace将自己进行ptrace就结束了。
结语
总体来说这个样本还算比较适合入门的,很多反调方法网上都有提及,可以用作学习材料。