android trap攻防
0x01 反編譯出錯
1.插入無效指令是部分逆向工具崩潰
原理:大部分逆向工具都是線性讀取字節碼並解析, 如dex2jar,baksmali,apktool等,當遇到無效字節碼時,就會引起反編譯工具解析失敗。例如:新版的dex2jar 遇到這種情況任然沒法轉化成jar,在新版本的baksmail和apktool已修復此問題。
010editor查看,紅色框中就是加入的陷阱類,繞過方法很簡單,只要將這個三個類刪除,重編譯即可
2.利用apk包本質上是zip/jar包進行保護
a.僞加密
在Android4.2.x前的一種保護方 式,通過APK(壓縮文件)進行僞加密,其修改原理是修改連續4位字節標記爲“P K 01 02”後的第5字節(ps:一般在文件末尾有多處),奇數表示加密偶數不加密,這種保護方式一般只會出現在一些cm裏,因爲:1.對系統不兼容;
2.僞加密 處理後的apk市場也無法對其進行安全檢測,部分市場會拒絕這類APK上傳市場
b. 文件名長度操作
android平臺對文件名的長度是沒有限制的,但是操作系統要求不能大於255;於是可以構建超長字符的類名達到反編譯錯誤目的。
3.axml文件保護
目的:防止/檢測二次打包android在解析AXMl文件時,是用過屬性的資源id,而不是資源名,當android系統遇到非法資源id時,並不會做解析,說以可以對axml文件解析,添加無用的屬性。但是對於破解者來說,一般會在java層增加log信息,然後打包apk, 此時如apktool之類的逆向工具,就會無法解析無效屬性,或進入trap類(檢測二次打包)
0x02 運行環境檢測
環境檢測主要包括
- ▏運行在調試狀態下
- ▏系統代碼被hook
- ▏以及運行環境在模擬器中
目的:爲了保護關鍵代碼被逆向分析,一般放在應用程序初始化過程中,如init_array,或jni_onload函數裏進行檢查代碼執行。
1.調試檢測
對調試器的檢測(ida,gdb,strace, ltrace等調試工具)a.父進程檢測
b.當前運行進程檢測
例如對android_server進程檢測。針對這種檢測只需將android_server改名就可繞過
pid_t GetPidByName(const char *as_name) {
DIR *pdir = NULL;
struct dirent *pde = NULL;
FILE *pf = NULL;
char buff[128];
pid_t pid;
char szName[128];
// 遍歷/proc目錄下所有pid目錄
pdir = opendir("/proc");
if (!pdir) {
perror("open /proc fail.\n");
return -1;
}
while ((pde = readdir(pdir))) {
if ((pde->d_name[0] < '0') || (pde->d_name[0] > '9')) {
continue;
}
sprintf(buff, "/proc/%s/status", pde->d_name);
pf = fopen(buff, "r");
if (pf) {
fgets(buff, sizeof(buff), pf);
fclose(pf);
sscanf(buff, "%*s %s", szName);
pid = atoi(pde->d_name);
if (strcmp(szName, as_name) == 0) {
closedir(pdir);
return pid;
}
}
}
closedir(pdir);
return 0;
}
c.讀取進程狀態(/proc/pid/status)
State屬性值T 表示調試狀態,TracerPid 屬性值正在調試此進程的pid,在非調試情況下State爲S或R, TracerPid等於0
d.讀取 /proc/%d/wchan
下圖中第一個紅色框值爲非調試狀態值,第二個紅色框值爲調試狀態:
static void get_process_status(pid_t pid,const char* info,char *outline)
{
FILE *fp;
char filename;
char line = {0};
snprintf( filename, sizeof(filename), "/proc/%d/status", pid );
fp = fopen( filename, "r" );
if ( fp != NULL )
{
while ( fgets( line, sizeof(line), fp ) )
{
if ( strstr( line, info ) )
strcpy(outline,line);
}
fclose( fp ) ;
}
return ;
}
static int getProcessStatus(int pid)
{
char readline = {0};
int result = STATUS_ELSE;
get_process_status(pid,"State",readline);
if(strstr(readline,"R"))
result = STATUS_RUNNING;
else if(strstr(readline,"S"))
result = STATUS_SLEEPING;
else if(strstr(readline,"T"))
result = STATUS_TRACING;
return result;
}
static int getTracerPid(int pid)
{
char readline = {0};
int result = INVALID_PID;
get_process_status(pid,"TracerPid",readline);
char *pidnum = strstr(readline,":");
result = atoi(pidnum + 1);
return result;
}
static int getWchanStatus(int pid)
{
FILE *fp= NULL;
char filename;
char wchaninfo = {0};
int result = WCHAN_ELSE;
char cmd = {0};
sprintf(cmd,"cat /proc/%d/wchan",pid);
LOGANTI("cmd= %s",cmd);
FILE *ptr; if((ptr=popen(cmd, "r")) != NULL)
{
if(fgets(wchaninfo, 128, ptr) != NULL)
{
LOGANTI("wchaninfo= %s",wchaninfo);
}
}
if(strncasecmp(wchaninfo,"sys_epoll\0",strlen("sys_epoll\0")) == 0)
result = WCHAN_RUNNING;
else if(strncasecmp(wchaninfo,"ptrace_stop\0",strlen("ptrace_stop\0")) == 0)
result = WCHAN_TRACING;
return result;
}
e. ptrace 自身或者fork子進程相互ptrace
ptrace me
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
printf("DEBUGGING... Bye\n");
return 1;
}
void anti_ptrace(void)
{
pid_t child;
child = fork();
if (child)
wait(NULL);
else {
pid_t parent = getppid();
if (ptrace(PTRACE_ATTACH, parent, 0, 0) < 0)
while(1);
sleep(1);
ptrace(PTRACE_DETACH, parent, 0, 0);
exit(0);
}
}
f. 防止dump
利用Inotify機制,對/proc/pid/mem和/proc/pid/pagemap文件進行監視。inotify API提供了監視文件系統的事件機制,可用於監視個體文件,或者監控目錄。具體原理可參考:http://man7.org/linux/man- pages/man7/inotify.7.html僞代碼:
void __fastcall anitInotify(int flag)
{
MemorPagemap = flag;
char *pagemap = "/proc/%d/pagemap";
char *mem = "/proc/%d/mem";
pagemap_addr = (char *)malloc(0x100u);
mem_addr = (char *)malloc(0x100u);
ret = sprintf(pagemap_addr, &pagemap, pid_);
ret = sprintf(mem_addr, &mem, pid_);
if ( !MemorPagemap )
{
ret = pthread_create(&th, 0, (void *(*)(void *)) inotity_func, mem_addr);
if ( ret >= 0 )
ret = pthread_detach(th);
}
if ( MemorPagemap == 1 )
{
ret = pthread_create(&newthread, 0, (void *(*)(void *)) inotity_func, pagemap_addr);
if(ret > 0)
ret = pthread_detach(th);
}
}
void __fastcall __noreturn inotity_func(const char *inotity_file)
{
const char *name; // r4@1
signed int fd; // r8@1
bool flag; // zf@3
bool ret; // nf@3
ssize_t length; // r10@3
ssize_t i; // r9@7
fd_set readfds; // @2
char event; // @1
name = inotity_file;
memset(buffer, 0, 0x400u);
fd = inotify_init();
inotify_add_watch(fd, name, 0xFFFu);
while ( 1 )
{
do
{
memset(&readfds, 0, 0x80u);
}
while ( select(fd + 1, &readfds, 0, 0, 0) <= 0 );
length = read(fd, event, 0x400u);
flag = length == 0;
ret = length < 0;
if ( length >= 0 )
{
if ( !ret && !flag )
{
i = 0;
do
{
inotity_kill((int)&event);
i += *(_DWORD *)&event + 16;
}
while ( length > i );
}
}
else
{
while ( *(_DWORD *)_errno() == 4 )
{
length = read(fd, buffer, 0x400u);
flag = length == 0;
ret = length < 0;
if ( length >= 0 )
}
}
}
}
g. 對read做hook
因爲一般的內存dump都會調用到read函數,所以對read做內存hook,檢測read數據是否在自己需要保護的空間來阻止dumph. 設置單步調試陷阱
int handler()
{
return bsd_signal(5, 0);
}
int set_SIGTRAP()
{
int result;
bsd_signal(5, (int)handler);
result = raise(5);
return result;
}
2. 模擬器檢測
用戶層行爲和數據檢測,模擬器特有屬性值,以及模擬器體系結構特徵- ▏電池狀態和電流,模擬器默認電話號碼檢測,檢測設備IDS 是不是“000000000000000”, 檢測imsi id是不是“310260000000000“,手機運營商等
- ▏API Demo,Dev tool一般模擬器上纔有的應用,檢測安裝應用,短信箱,通信錄,相冊等
- ▏讀取/system/build.prop文件
- ▏調用__system_property_get,或反射調用Systemproperty.get獲取系統屬性值
- ▏通過執行shell命令檢測模擬器,如getprop
- ▏檢查模擬器特有文件如 /dev/socket/qemud","/dev/qemu_pipe","/sysrtem/bin/qemud",/dev/qemu_pipe,/dev/qemu_trace等
- ▏模擬器cpu信息值差異,如hardware,Revision等
- ▏系統屬性值等(android.os.build)
- ▏基於Qemu二進制翻譯技術(ps:真機具有真正的物理CPU,在執行一段指令的時候只能一條一條的去執行指令(編譯器沒有對指令進行優化的前提下)。模擬器沒有真正的物理CPU,所以,他在執行一段指令的時候,這段指令已經被人爲的優化掉)http://www.dexlabs.org/blog/btdetect
- ▏通過觀察低級別的緩存行爲。檢測方法: 默認情況下,Android模擬器提供了Android SDK是基於QEMU,仿真器不具備分裂緩存。而在真實設備上存在兩個不同的緩存,一個用於數據訪問,一個用於指令。https://bluebox.com/technical/android-emulator-detection-by-observing-low-level-caching-behavior/
0x03防app運行環境被hook
hook代碼肯定是在app自身模塊加載之前運行的,那麼在app的maps表裏會首先加載hook框架的dex,我們只要對此dex做簡單的校驗,就會檢測到app被注入了。編碼思路:
遍歷maps表,查看子串是否存在“[email protected]”的字符串,若存在獲取該模塊的startAddr和endAddr, 然後檢驗此odex的頭部是否爲真正的dex文件。
0x4抗靜態分析(ida F5 以及執行流程圖)
1.arm 指令插花
2.通過棧修改程序調用過程
STDFD保存寄存器值到棧上,LDMFD將棧上數據賦值到寄存器中,這個過程修改了函數返回的地址。0x05如何繞過app環境檢測
1.調試過程中修改代碼
hook檢測點(重定向函數出關鍵文件操作,函數返回值修改等),修改源碼(改變字段屬性主要針對模擬器仿真,修改函數返回值,例如繞過簽名校驗等)eg:
ida patch 線程退出函數: patch 地址48D9668A處的函數調用
a.在數據窗口定位到機器碼位置
b.F2 編輯機器碼 00 00 0A EF (movs R0, R0)
c.F2保存修改
2.fopen函數相關的檢測
由於/proc/pid/status,/proc/pid/wchan,/proc/pid/mem等都是針對文件狀態的檢測,入口點函數一般都爲fopen, 我們可以事先攔截fopen,查看app是否左右這方面的防護。以某某app爲例:如下圖此app fopen了這些文件,我們就能猜測這是對調試檢測。
繞過方法:在指定目錄下(/data/local/tmp)新建一文件alimolisec, hook fopen函數檢測到文件名子串有/proc/self時,就重定位打開alimolisec文件
FILE * MyOpen( const char * filename, const char * mode ) {
FILE *file = NULL;
if (strstr(filename, "/proc/self")){
LOGI("fileName:%s", filename);
file = oldFopen("/data/local/tmp/hone", mode );
}else
file = oldFopen( filename, mode );
return file;
}
0x06 CM中的那些殼子
加固手法:
1.不替換源classes.dex,也沒有做任何加密的處理,對classes.dex中的Activity,service, receiver等的oncreate,onReceive,加密替換,殼首先拿到執行權,在自身so裏完成對源dex還原。
2.不替換源classes.dex,也沒有做任何加密的處理, 修改原Dex的Class_Data,將
MyContentProvider,Application,Activity,service類的入口函數 onCreate方法標記爲native方
法,但是原始字節碼仍然未加密保存在dex文件中。
3.對classes.dex整包加密,使用殼加載器內存解密classes.dex,並替換原始成源classes.dex
4.對classes.dex整包加密,並將原dex拆分成兩部分,在內存中分兩塊區域存儲。
脫殼手法:
1.對部分整包加密的可以通過運行時memory dump,部分通過攔截dvmDexFileOpenPartial函數即可獲取完整dex。(dex連續)
2.對於修改了dex的Class_data和classes.dex做了拆分的,可以通過找到dex對應的pDvmDex
結構,重建dex(dex不連續或不完整的)
0x07 android平臺常用的hook框架
▏cydia substrate。原理:框架注入zygote進程,採用inline hook( 修改目標函數前N字節,跳轉到自定義函數入口,備份目標函數前N個字節,跳轉回目標函數)▏Xposed。原理:替換app_process,將需要hook的java函數替換成JNI函數,所有需要HOOK的函數首先由xposedCallHandler處 理,xposedCallHandler負責調用註冊的beforeHookMethod和afterHookedMethod
▏adbi。原理:利用ptrace()函數attach到一個進程上,然後在其調用序列中插入一個調用dlopen()函數的步驟,將一個實現預先備好的.so文件加載到要hook的進程中,最終由這個加載的.so文件在初始化化函數中hook指定的函數。
參考文檔
http://man7.org/linux/man-pages/man7/inotify.7.htmlhttp://www.dexlabs.org/blog/btdetect
https://bluebox.com/technical/android-emulator-detection-by-observing-low-level-caching-behavior/
https://github.com/crmulliner/adbi
http://www.cydiasubstrate.com/