首页
社区
课程
招聘
[原创] LIAPP手遊保護分析
发表于: 2024-11-28 20:24 5771

[原创] LIAPP手遊保護分析

2024-11-28 20:24
5771

packagename:bmV0LmdhbWVkdW8udGJk
聲明:本文內容僅供學習交流之用

淺淺記錄一次對LIAPP的分析過程。

直接打開APP會提示debuggable

用frida注入後會提示ng1ok-64.so ( 一般的frida應該是frida-agent-64.so )

用frida hook dlopen,發現在閃退前只加載了libdyzzwwc.so,顯然anti frida的邏輯就在這個so中。

查看libdyzzwwc.so的.init_array,看上去有點奇怪。

手動按D幫助IDA重新解析,發現靜態分析.init_array只能看到有一個初始化函數,相關檢測邏輯大概就在這裡。

sub_B8080重命名為init_array_func1

進入init_array_func1,會發現有些函數調用IDA靜態分析時無法識別,像下圖這樣。

遇到這種情況時,只好動調看看了。

注:一些函數是經過我重命名的,並非原本就是這樣。

在動調前要先弄清楚主要的目的:

init_array_func1下斷點開始進行動調,一開始先判斷了v1中是否包含.sandbox,不清楚具體檢測的是什麼,或許是一些沙箱環境?

我的環境不會走這條if分支,繼續向下看。

注:v1如下,是從/proc/self/environ裡取的值,while循環會遍歷這其中的所有元素

然後v1是否包含com.lbe.parallel,查了下這個APP,相關描述是"使用 Parallel Space 輕鬆地複製和運行同一應用程式的多個帳戶",大概是一個APP多開工具,看來這個工具也是不允許的。

跳過中間的一些不太重要的邏輯,看到最後調用了幾個函數,逐一看看。

首先看check_blackdex,檢查了blackdex,這是一個著名的脫殼工具,被檢測到後會調用kill_func

繼續看check_something,中間幾個5C85F0A264指向app_process64某處,不用理會。

重點是do_something1do_something2

先看do_something1,一開始先打開了/proc/self/maps,算是比較經典的檢測點。

將從maps裡獲取的內容傳入check1函數。

check1檢測的東西如下( 只顯示一部份 ),包括frida、xposed等等。由於我魔改的frida-agent.so放在了/data/local目錄下,因此被檢測出來了。

當maps中存在以下字符串時代表檢測到,會返回-1,反之返回0代表檢測不到。

看完check1函數,回到do_something1繼續向下看,會發現另一層檢測邏輯。

首先通過scandir獲取/proc/<pid>/task下所有目錄。

然後遍歷這些線程目錄,讀取/proc/<tid>/comm的內容。

接著判斷/proc/<tid>/comm的內容是否與以下字符串相等,是則代表被檢測到。

可以看到pool-fridagum-js-loopgbusgamin這些熟悉的frida特徵。

回到check_something函數,繼續看do_something2

一開始先打開了/proc/<tid>/maps,然後調用check_maybe_io_redirect進行一些檢查,arg0是fopen返回的fp,arg1是"/proc/<tid>/maps"

深入check_maybe_io_redirect看看具體做了什麼。

check_maybe_io_redirect中調用了check_fd

check_fd的檢測邏輯如下:

注:cmp_func1類似strcmp,相等才返回0

綜上分析,感覺大概是在檢測IO重定向?我沒有進一步測試,所以也不太確定。

回到do_something2函數繼續看,中間是一大段對proc_maps的判斷和操作,感覺不太重要。

最後又調用了一次check_maybe_io_redirect,然後保存了base.apk的一些信息。

至此分析完init_array_func1的一些較為重要的函數。

在靜態分析時只能看到有一個.init_array函數,實際上有2個,在執行完init_array_func1後單步慢慢走就能走到init_array_func2 ( 或者在linker打斷點也可以 )。

只調用了一個函數,直接進去看看。

一開始是一段字符串解密邏輯,解密結果是/linker,然後調用like_strcpy賦給v24

然後會調用like_dlopen,它會打開一個新的linker並進行一些初始化。

進入like_dlopen看看它是如何實現的。

get_linker_startaddr中會獲取原linker的起始地址( 存放在*((_QWORD *)a1 + 3) ),然後調用openat + mmap將新的linker映射進內存。

之後是一個while循環,通過與linker的原文件對比( 用010來進行字節對比 )發現,這是在遍歷setion header tables,並根據s_type進行一些初始化。所以結果都保存在a1 + x

總的來說like_dlopen像是一個簡易版的dlopen

回到上一層,在like_dlopen後調用了數個like_dlsym獲取一些符號,並保存在不同的全局變量中。其中的g_solistg_soinfo_get_realpath_funcg_soinfo_get_soname在之後的分析中會出現。

而具體like_dlsym的實現就不看了,是一堆很抽象的計算,反正從結果來看它類似dlsym

最終調用一個函數清理一開始open + mmap映射進內存的那個linker,然後就返回了。

總的來說init_array_func2裡做了一堆與linker相關的操作,獲取了一些linker函數,大概會在之後的一些檢測點。

通過上述對.init_array函數的分析,可以發現一些經常出現與字符串有關的函數like_strcpya1_contain_a2cmp_func1cmp_func2等等。

其中的like_strcpy通常會在字符串解密邏輯執行後調用,可以算是最接近解密字符串的一個函數,因此嘗試hook like_strcpy看看。

在此之前先解決frida檢測的問題,從上述分析可以知道是如何檢測的,因此我一開始的想法是hook fgets抹寫/data/local特徵,同時fgets也是本例.init_array中執行時機較早的函數,因此可以以fgets為跳板去hook其他在.init_array時機執行的函數( 如like_strcpy )。

結果是雖然frida不會再直接Process terminated,但依會彈窗提示,代表仍然有其他的frida檢測邏輯。

hook_like_strcpy打印了很多東西,只列出一些我看到且認為比較重要的:

注:其實只要hook check1讓其固定返回0就能完全bypass,但由於我比較好奇另一個frida檢測的實現邏輯,因此才進行了接下來的操作。

同上面那樣hook like_strcpy,在遇到"ng1ok-64.so"時打印調用棧。

打印調用棧如下:

可以看到有一堆不同的地方,一開始還以為有那麼多不同的frida檢測邏輯,打算一個一個替換看看。

誰知道在替換第一個後,就只剩一個調用棧了。

而且APP顯示的檢測點也從ng1ok-64.so變成debuggable

看到上述調用棧libdyzzwwc.so offset: 0x240e0調用了libdl.so offset: 0x10a4,動調看看。

發現調用了dl_iterate_phdr,這個函數的作用大概是會遍歷所依賴的共享庫,對每個對象都調用一次回調。

要單步F7才能慢慢跟到callback_func裡面。

然後會跟到linker64dl__Z18do_dl_iterate_phdrPFiP12dl_phdr_infomPvES1,在這裡調用上述的callback_func

跟入callback_func,不知為何F5的結果與匯編的結果不一致( 大概是IDA對某些函數錯誤的分析所導致的連鎖效應 ),只能從匯編視圖繼續跟。

x0"/system/bin/linker64"x1libdl.socmp_func1類似strcmp,相等才會返回0

x0"/system/bin/linker64"x1/data/appcmp_func2同樣類似strcmp

x0"/system/bin/linker64"x1為packagename,x0包含x1時才為true,否則會走到check_smaps函數。

跟了一會可以總結出,x0就是dl_iterate_phdr遍歷時傳給callback_func的共享庫名字。

callback_func的大概邏輯就是將除了libdl.so、以/data/app/開頭、包含packagename的so都過濾後,然後調用check_smaps檢查。

check_smaps會調用check1

再來回顧下check1,裡面有一段這樣的檢測,而由於我魔改的frida-agent-<arch>.so放在了/data/local目錄下,所以才會被檢測到。

試下直接將check1固定返回0,看看可否bypass。

成功,不再顯示ng1ok-64.so,而是顯示debuggable

小結:

除了fopen("/proc/<pid>/maps") + fgets這套組合技外,還可以通過dl_iterate_phdr來實現類似遍歷/proc/<pid>/maps的效果,因此我一開始hook fgets時才無法bypass。

會觸發這個檢測是大概是因為我的手機環境是自定制的AOSP,我設置了所有APP默認有debuggable權限。

為了驗證是否如我所想,我將APP debuggable權限改成了可切換的模式。

可以看到,關閉debuggable的狀態下是可以正常進入遊戲的。

但關了debuggable權限後就無法動調了,這很不好。嘗試過找具體的檢測代碼,想針對性地bypass,但沒找到。

最終的解決方案是patch掉導致APP閃退的函數來bypass,後文會說明是哪個函數。

在動調.init_array函數的過程中,會對其中用到的一些函數下斷點。

某次調試完.init_array後按F9繼續運行,發現斷在了某個地方,向上回溯能來到另一個超大的檢測函數,我將其命名為after_initarray_check2

一開始沒有細究after_initarray_check2是誰調用的,後來想了想明顯是Java層調用的native函數。

將APP拉入jadx,查找dyzzwwc

其中只有一個native函數,顯然就是它。

同時會發現Java層做了一些混淆,但目前並不需要分析Java層,因此也無所謂了。

之後各種檢測的上層調用棧都是after_initarray_check2,因此這裡先小小分析一下它的來源。

在動調after_initarray_check2時,會發現IDA越來越卡,而且經常亂跳,經常crash,經常卡住不動。

一開始還以為是IDA的老問題( IDA動調有時候是真的卡… ),但漸漸感到不太對,直到在某處看到pthread_create才恍然大悟,猜測大概是after_initarray_check2啟動了一堆線程。

hook了pthread_create後發現果然如此,創建了N個線程,數了下總共有11個不同的線程回調函數。

後面會繼續分析這些線程到底在檢測什麼,現在先嘗試bypass,目的是讓frida可以正常hook APP( 並且解決debuggable檢測 )而不閃退。

APP啟動了那麼多的線程,相關的檢測邏輯大概都在其中,因此嘗試直接patch掉所有線程。

結果是APP雖然不會再彈出Alert Dialog,但會在進度條加載到某個時刻時閃退。

嘗試hook動調時發現的kill_func,看看會否觸發,順便打印調用棧。

的確會觸發,跳到對應位置繼續分析

發現是pthread_func9( 我命名的第9個檢測線程 )創建失敗所導致。

嘗試讓pthread_func9順利創建

之後雖然能順利進入遊戲,但過一陣子同樣閃退。

由此可知各個檢測線程存在一定程度上的耦合,牽一髮則動全身。

最終的通用bypass手段是從kill_func入手( 猜測所有閃退都會調用kill_func ),嘗試直接patch掉kill_func,讓它固定返回0,成功讓APP與frida都不再閃退。

至此我遇到的2個反調試都已成功繞過,但我同樣比較好奇其他檢測線程干了什麼,因此下文會繼續分析看看其他線程( 不會全部線程都分析 )。

通過frida + IDA來動調( 而不是adb shell am start -D -n XXX 那種方式 ),這樣做的目的是:

patch nanosleep,對本例來說,它會導致動調時卡住不動,大概也是由某個反調試邏輯觸發的,直接讓其固定返回0就可以。

最好是在對應的pthread_create和對應的線程回調函數裡都下斷點,只在線程的回調函數裡下斷點可能會失敗。

斷在對應的pthread_create後,最好先暫時其他線程,這樣會比較好調,防止其他線程的干擾。IDA Python腳本:一鍵暫停其他線程

function offset:0x45A68

pthread_func6一開始先調用check_fingerprint3函數進行第一部份的檢測,進入看看。

check_fingerprint3裡調用check_su檢查了一些常規的su路徑。

check_su會先構造各種可能的su路徑,如/system/xbin/su,然後傳入check_su_path_exist

check_su_path_exist( 其實叫做check_path_exist會好點,因為這個函數不只用來檢測su路徑 )會創建pthread_func7來檢測。

pthread_func7具體實現下一小節再看。

回到check_fingerprint3繼續向下看。

檢查ro.build.fingerprint是否包含userdebug

比較ro.product.modelCustom Phone是否相同。

檢查magisk特徵

獲取環境變量,遍歷其中的所有路徑,傳入trav_dir_and_check_su函數。

注:trav_dir_and_check_su函數實現如下,通過scandir來遍歷指定目錄,然後檢查其中是否包含su文件。

/sdcard/Download/boot.img都不放過?

又是一些magisk特徵:

/system/bin/magisk/system/bin/magiskinit/system/bin/magiskpolicy/system/bin/resetprop

最後又有一些su檢測

看完check_fingerprint3後,回到pthread_func6繼續向下看( 只發現一處特別可疑的地方 )。

循環遍歷solist ( 由g_solist賦值 ),調用soinfo_get_realpath ( 實際調用的是g_soinfo_get_realpath_func )、soinfo_get_soname ( 實際調用的是g_soinfo_get_soname )來獲取realpathsoname,然後判斷其中是否包含zygisk的特徵。

小結:

pthread_func6總的來說就是一個root檢測。

function offset:0xF2D0

pthread_func7中會通過各種手段嘗試打開/訪問傳入來的路徑,如果能順利執行就代表被檢測到。

用到的API包括:fopenopenatscandirlstatstataccessreadlink

function offset:0x39E18

一開始調用了sub_7869BC4880,動調時沒有看出它在干什麼。

但在靜態分析時手動解密了sub_7869BC4880中的一些字符串,大概是一些模擬器的特徵檢測。

回到pthread_func10繼續向下看。

調用了check_BlueStacks_emu,它專門檢測了BlueStacks模擬器。

具體檢測了以下特徵:( 將以下字符串作為參數傳入check_su_path_exist )

繼續向下看,又檢測了一些特徵,不認識。

檢測夜神模擬器

具體檢測了以下特徵:

檢測雷電模擬器

具體檢測了以下特徵:

檢測KoPlayer

檢測一些虛擬機特徵:

檢測Android指紋中是否包含:

調用check_android_prop檢測了一些android屬性


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-11-28 20:24 被ngiokweng编辑 ,原因: .
收藏
免费 14
支持
分享
最新回复 (18)
雪    币: 1288
活跃值: (6181)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
.KK
2
学废了~
2024-11-28 21:00
0
雪    币: 1509
活跃值: (1220)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
.KK 学废了~
2024-11-28 21:53
0
雪    币: 129
活跃值: (4590)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
LIAPP 难度在费时间,检测点多,但没用到svc,java 层函数不停调用native做检测,根据标识函数反推就容易得多了
2024-11-28 23:26
0
雪    币: 129
活跃值: (4590)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
这么好的贴没人点赞
2024-11-28 23:29
0
雪    币: 1509
活跃值: (1220)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
New对象处 LIAPP 难度在费时间,检测点多,但没用到svc,java 层函数不停调用native做检测,根据标识函数反推就容易得多了
確實, 順帶一提大佬你的文章寫的很好, 什麼時候多出幾篇
2024-11-28 23:33
0
雪    币: 180
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
真是个好帖子 现在的环境检测这么强 检测重定向 各种底层api获取加载的so信息   甚至直接在内存里映射so文件来调用避免被hook 太猛了 是我的话 直接用gg修改器dump global-metadata.dat
2024-11-29 00:00
0
雪    币: 1509
活跃值: (1220)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
8
mb_ihgczpkl 真是个好帖子 现在的环境检测这么强 检测重定向 各种底层api获取加载的so信息 甚至直接在内存里映射so文件来调用避免被hook 太猛了 是我的话 直接用gg修改器dump global-met ...
不太會用GG, 還是frida用得習慣點
2024-11-29 00:02
0
雪    币: 9110
活跃值: (6320)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
感谢大佬分享。
2024-11-29 02:39
0
雪    币: 4514
活跃值: (6766)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
赞一个先,学习了谢谢
6天前
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
支持楼主
6天前
0
雪    币: 1413
活跃值: (3041)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
感谢分享
6天前
0
雪    币: 2160
活跃值: (2862)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
感谢分享,很完整的分析
6天前
0
雪    币: 27
活跃值: (1753)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
这个游戏的anti frida做的真多
6天前
0
雪    币: 650
活跃值: (4247)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
感谢分享
3天前
0
雪    币: 3221
活跃值: (4972)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
16
好贴支持!感谢分享
3天前
0
雪    币: 562
活跃值: (4140)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17

1

最后于 3天前 被DirtyAngle编辑 ,原因:
3天前
0
雪    币: 1509
活跃值: (1220)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
18
乐子人 好贴支持!感谢分享
看你文章學的佬
3天前
0
雪    币: 342
活跃值: (981)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
19
写的真好 期待其他文章!
2天前
0
游客
登录 | 注册 方可回帖
返回
//