反調試android
❶ Android無線開發的幾種常用技術(阿里巴巴資深
完整的開發一個android移動App需要經過從分解需求、架構設計到開發調試、測試、上線發布等多個階段,在發布後還會有產品功能上的迭代演進,此外還會面對性能、安全、無線網路質量等多方面的問題。
移動App的產品形態各不相同,有的是內容類,有的是工具類,有的是社交類,所以它們的業務邏輯所偏重的核心技術有些差別,但它們都會用到一些常用的技術方案。今天我們就先來簡單介紹一下這些常用技術,以後會專門分專題來詳細介紹這些技術的原理和使用場景。
1. Multidex
在Dalvik虛擬機所使用的dex文件格式中,用原生類型short來索引文件中的方法數,也就是最多隻能有4個位元組65536個method,在打包apk的過程中會把工程所需要的全部class文件都合並壓縮到一個dex文件中,也就是說自己開發的代碼加上外部引用的庫的方法總數不能超過65535。
隨著業務邏輯的不斷增長,很容易就會超過這個限制,在編譯期間就會遇到這樣一個錯誤:
還好google官方給出了一個解決方案Multidex,它會把dex文件拆成兩個或多個,第二個dex文件叫classes2.dex,在Application實例化後會從apk中解壓出classes2.dex並將其拷貝到應用的目錄下,通過反射將其注入到當前的ClassLoader中。但是這個方案非但不能解決一切問題也不能直接拿來用,而要加入自己的一些改造,來解決NoClassDefFoundError、INSTALL_FAILED_DEXOPT等問題,以保證自己的dex被順利的載入流暢的執行。
2. Plugin
Multidex雖然可以解決方法數的限制,但隨著業務邏輯越來越多,apk的大小也變得越來越多,而且有一些功能並非全部用戶都想用的,所以會把一些功能模塊獨立出來做成插件,讓用戶可以按需下載更新,這樣既減小了包大小,又改善了用戶體驗。
插件類似於windows的dll文件,放在某個特定目錄,應用程序主框架會用LoadLibrary載入各dll文件,按插件介面去訪問插件。Android的插件技術也是這樣,利用一個進程可以運行多個apk的機制,用ClassLoader將宿主apk之外的類載入進來,插件的context可以通過createPackageContext方法創建。因為插件中的activity,service等組件如果沒有在AndroidManifest.xml中聲明將不能運行,所以需要預先在AndroidManifest.xml中聲明一個代理類(ProxyActivity),將這個ProxyActivity傳給插件,讓插件的activity也有訪問資源的能力。
3. Hot Patch
有時一些嚴重的crash bug或漏洞需要緊急修復,但有些用戶不會或不願意立即升級,而且頻繁升級,沒有特別的功能更新只是修復bug的升級,對活躍用戶是一種傷害。熱補丁就可以解決這樣的窘境,它是一種可以線上修復的技術方案,有動態改變方法的能力,一般大型的移動應用都會使用熱補丁來處理緊急事件。
Hot Patch可以通過hook來修改java的method,注入自己的代碼,實現非侵入式的runtime修改,或者採用正向編程,通過工具生成patch文件,通過jni bridge指向補丁文件中的方法。還有就是利用ClassLoader,在dex中查找class時,如果找到類則返回,找不到就從下一個dex文件中繼續查找,由此可以想到,在把問題修復後,可以單獨生成一個dex,通過反射插入到dexElements數組的最前面,這樣就能讓dalvik載入補丁里的類了。
4. Push通道
Push是移動App常用的一種無線技術,基礎是基於TCP的心跳機制,和客戶端維持一個長連接。用處是向客戶端推送消息,或者代替客戶端定時去從伺服器pull的策略,改為客戶端接收到push消息後再去pull。
如果每個應用都自己實現push通道的話,cpu就會不定時地經常被喚醒,耗電量達到難以容忍的程度,而且自己搭建push平台的成本也很大,實時性和效率也存在問題,一般都直接使用一些服務商提供的push方案,這些push平台一般都經過了優化設計,在跨平台和網路穿透性、長連接心跳包、多客戶端App鏈路復用、服務和連接保活等技術上做了優化。比如Agoo最初是淘寶無線事業部開發的push服務,在逐漸完善和支撐淘系其他app後,通過服務端容量、通訊協議優化、業務和開放能力的拓展改進後,與友盟等合作,開始向第三方提供推送服務。
5. 應用加固
一款熱門的移動app或游戲發布後會受到很多的關注,經常會遇到二次打包的盜版行為,破解者要麼修改游戲的資源文件、道具、分值甚至直接把訪問的站點指向自己架設的伺服器,損害了開發者的利益;要麼偷偷植入自己的惡意代碼,表面上看起來跟正版的app完全一樣,在後台卻盜取用戶隱私,植入木馬;要麼通過反向工程學習原app的核心技術,打破技術上的競爭壁壘。
為了防止被破解只通過混淆是遠遠不夠的,即使是在native層混淆也還是會被人熟練的反編譯,所以需要一套對apk的保護方案來反調試、防逆向和防篡改。一般的加固方法都是對原apk先進行加密,然後和殼合並生成新的apk。殼是用來解密apk的dex文件。當應用啟動時,殼先解密原apk,准備好自己定義的ClassLoader,然後獲取源程序中的Application名稱,通過反射找到正確的Application對象,運行它的onCreate方法,這樣原apk才能被真正運行。其他一些反調試的方法有針對反編譯工具,在源程序中加入一些無效的指令或無效的指針,引發反編譯工具的崩潰,還有就是加花指令,利用一些跳轉,堆棧操作等指令,讓破解者無法清楚地理解反匯編後的內容。
6. 其他
除了上述幾點外,在服務端還會涉及灰度策略、鏈路流量優化、動態更新配置、防DNS劫持等技術,在客戶端會涉及用戶埋點上報、在線監控、進程保活、H5和native混合開發、注入框架等。
❷ Android APP的破解技術有哪些如何防止反編譯
AndroidAPP破解主要依靠利用現有的各種工具,如下:1)APKtool2)dex2jar3)jd-gui4)簽名工具防止反編譯,介紹一種有效對抗native層代碼分析的方法——代碼混淆技術。代碼混淆的學術定義如下:代碼混淆(codeobfuscation)是指將計算機程序的代碼,轉換成一種功能上等價,所謂功能上的等價是指其在變換前後功能相同或相近。其解釋如下:程序P經過混淆變換為P『,若P沒有結束或錯誤結束,那麼P』也不能結束或錯誤結束;而且P『程序的結果應與程序P具有相同的輸出。否則P』不是P的有效的混淆。目前對於混淆的分類,普遍是以Collberg的理論為基礎,分為布局混淆(layoutobfuscation)、數據混淆(dataobfuscation)、控制混淆(controlobfuscation)和預防混淆(preventiveobfuscation)這四種類型。騰訊御安全保護方案提供了以上所述四種混淆分類的多維度的保護,布局混淆方面,御安全提供了針對native代碼層中的函數名進行了混淆刪除調試信息等功能;數據混淆方面,御安全提供了針對常量字元串加密及全局變數的混淆的功能;控制混淆方面,御安全針對代碼流程上,提供了扁平化,插入bogus分支以及代碼等價變換等功能;預防混淆方面,御安全在混淆過程中加入了針對主流反編譯器的預防混淆的代碼,能夠有效地抵抗其分析。御安全還對應用開發者提供不同等級的保護力度及多種混淆方式的功能的選擇,用戶可以根據自己的需求定製不同的混淆功能保護。同時,御安全保護方案除了提供代碼混淆保護方面的技術,還提供代碼虛擬化技術及反逆向、反調試等其他安全保護方案,綜合使用多種保護方案可以有效地提高代碼安全。
❸ 安卓app360加固怎麼反編譯
1 對比
上傳demo進行加固,解包後對比下原包和加固包,發現加固包在assets文件夾下多了libjiagu.so,libjiagu_x86,lib文件夾下多了libjiagu_art.so,同時修改了dex文件和AndroidManifest文件
打開manifest文件,看到xxx加固對Application標簽做了修改,添加了殼入口,也就是我們反編譯後看到的StubApplication.smali這個文件。
相比於之前版本的加固,自從1.x.x.x加固版本之後,多了幾次反調試,使得動態難度稍微增大了一些,不過針對脫殼機脫殼,再多了反調試也是無用。或者通過修改系統源碼,也能達到消除反調試的作用。
2 動態調試
(1)把app安裝到手機,以調試模式打開app
(2)以shell模式root許可權打開IDA的android_server監聽
(3)tcp轉發
(4)打開IDA,修改配置為在進程開始時下斷
(5)搜索到進程後jdwp轉發,pid值即為我們進程號,並在命令行下附加。
成功附加後,可以下段了,打開Debugger Option
我們選擇在線程開始和庫載入時下斷,修改成功後,jdb附加,點擊運行
程序會斷在elf頭處,按下G鍵,搜索mmap,在mmap函數的段首和斷尾下段
F9運行,來到斷尾時F8單步,
來到此處時,在 BLunk_5C999C2C下斷,F9一下,F7跟進去
跟進去今後在BLX LR處進行下斷,此處就是進行反調試的地方,原理依然是獲取TracePid的值判斷當前是不是處於調試狀態,建議第一次調試的人在fgets和fopen處下斷,再f7跟進此調用就可以看到TracePid的值了。
跟進去之後,我們直接把方法移到最下方,就可以看到kill符號了,這就是殺進程的地方,如果當前處於調試狀態,則直接結束進程。
我們在此函數的所有cmpR0,#0處下斷,F9一下後即斷在斷點處,觀察寄存器窗口的R0值,實質就是當前的TracePid的16進制的值
不確定的可以使用cat /proc/pid/status進行對比一下,我們直接把R0置0,右鍵選擇Zero Value即可清0,繼續F9
我們看到程序又來到了mmap處,繼續f9
當繼續斷在調用反調試功能的方法時,繼續F7跟進,依然在所有的cmp R0,#0處下斷,斷下後把R0清0後繼續F9運行
目前的規律是,調用BLXLR的第一次,第二次和第四次是進行反調試判斷的,第三次並不影響,可以直接f9跳過去,三次反調試搞定後,就可以愉快的F9運行並觀察堆棧窗口了
當看到出現如下所示時:
說明殼已經開始解密並釋放dex文件了,我們直接F8單步十幾步,最後F9一下就可以看到我們需要的dex頭了
直接腳本mp出來即可,最後把libjiagu的所有文件刪除,並修復下Application標,如果存在則修復,不存在刪除即可
❹ Android studio 開發app,如何抵抗動態調試,反調試代碼怎麼寫請寫上詳細代碼。
為了保護關鍵代碼被逆向分析,一般放在應用程序初始化過程中,如init_array,或jni_onload函數里進行檢查代碼執行。
1.調試檢測
對調試器的檢測(ida,gdb,strace, ltrace等調試工具)
a.父進程檢測
b.當前運行進程檢測
例如對android_server進程檢測。針對這種檢測只需將android_server改名就可繞過
[objc] view plain
pid_t GetPidByName(const charchar *as_name) {
DIR *pdir = NULL;
struct dirent *pde = NULL;
FILEFILE *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
下圖中第一個紅色框值為非調試狀態值,第二個紅色框值為調試狀態:
[objc] view plain
static void get_process_status(pid_t pid,const char* info,charchar *outline)
{
FILEFILE *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);
charchar *pidnum = strstr(readline,":");
result = atoi(pidnum + 1);
return result;
}
static int getWchanStatus(int pid)
{
FILEFILE *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);
FILEFILE *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
[objc] view plain
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. 防止mp
利用Inotify機制,對/proc/pid/mem和/proc/pid/pagemap文件進行監視。inotify API提供了監視文件系統的事件機制,可用於監視個體文件,或者監控目錄。具體原理可參考:http://man7.org/linux/man- pages/man7/inotify.7.html
偽代碼:
[objc] view plain
void __fastcall anitInotify(int flag)
{
MemorPagemap = flag;
charchar *pagemap = "/proc/%d/pagemap";
charchar *mem = "/proc/%d/mem";
pagemap_addr = (charchar *)malloc(0x100u);
mem_addr = (charchar *)malloc(0x100u);
ret = sprintf(pagemap_addr, &pagemap, pid_);
ret = sprintf(mem_addr, &mem, pid_);
if ( !MemorPagemap )
{
ret = pthread_create(&th, 0, (voidvoid *(*)(voidvoid *)) inotity_func, mem_addr);
if ( ret >= 0 )
ret = pthread_detach(th);
}
if ( MemorPagemap == 1 )
{
ret = pthread_create(&newthread, 0, (voidvoid *(*)(voidvoid *)) inotity_func, pagemap_addr);
if(ret > 0)
ret = pthread_detach(th);
}
}
void __fastcall __noreturn inotity_func(const charchar *inotity_file)
{
const charchar *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
因為一般的內存mp都會調用到read函數,所以對read做內存hook,檢測read數據是否在自己需要保護的空間來阻止mp
h. 設置單步調試陷阱
[objc] view plain
int handler()
{
return bsd_signal(5, 0);
}
int set_SIGTRAP()
{
int result;
bsd_signal(5, (int)handler);
result = raise(5);
return result;
}