1. Android10でのptraceの概要
1.ソースコードの場所の追跡
Android10では、ptrace関数定義ファイルのパスは次のとおりです。
bionic/libc/include/sys/ptrace.h
関数プロトタイプは次のように定義されています。
long ptrace(int __request, ...);
ptrace関数のファイルパスは次のとおりです。
bionic/libc/bionic/ptrace.cpp
関数実装コードは次のとおりです。
long ptrace(int req、...){
bool is_peek = (req == PTRACE_PEEKUSR || req == PTRACE_PEEKTEXT || req == PTRACE_PEEKDATA);
long peek_result;
va_list args;
va_start(args, req);
pid_t pid = va_arg(args, pid_t);
void* addr = va_arg(args, void*);
void* data;
if (is_peek) {
data = &peek_result;
} else {
data = va_arg(args, void*);
}
va_end(args);
//最终是调用__ptrace函数
long result = __ptrace(req, pid, addr, data);
if (is_peek && result == 0) {
return peek_result;
}
return result;
}
ptrace実装コードから、__ ptrace関数が最終的に呼び出されたことがわかります。コードトレースの後、__ ptrace関数はさまざまなプラットフォームのアセンブリを通じて実装され、最後に__NR_ptraceシステムコールを呼び出します。
Android ARMプラットフォーム__ptrace実装コードファイルパス:
bionic/libc/arch-arm/syscalls/__ptrace.S
実装コードは次のとおりです。
#include <private/bionic_asm.h>
ENTRY(__ptrace)
mov ip, r7
.cfi_register r7, ip
ldr r7, =__NR_ptrace
swi #0
mov r7, ip
.cfi_restore r7
cmn r0, #(MAX_ERRNO + 1)
bxls lr
neg r0, r0
b __set_errno_internal
END(__ptrace)
Android arm64プラットフォーム__ptrace実装コードファイルパス:
bionic/libc/arch-arm64/syscalls/__ptrace.S
実装コードは次のとおりです。
#include <private/bionic_asm.h>
ENTRY(__ptrace)
mov x8, __NR_ptrace
svc #0
cmn x0, #(MAX_ERRNO + 1)
cneg x0, x0, hi
b.hi __set_errno_internal
ret
END(__ptrace)
.hidden __ptrace
2.機能
ptraceを使用すると、プロセスは別のプロセスの実行を監視および制御し、監視対象プロセスのメモリとレジスタを変更できます。これは主に、デバッガブレークポイントのデバッグ、システム呼び出しの追跡などに使用されます。たとえば、Androidシステムが提供するstraceツールは、以下に示すように、アプリによって実行されたすべてのシステムコールを追跡できます。
Androidアプリの保護では、ptraceはアンチデバッグに広く使用されています。プロセスをptraceにできるのは1回だけです。最初にptraceメソッドを呼び出すと、他の人がプログラムをデバッグできなくなる可能性があります。これは伝説的な先制ピットです。たとえば、JNI_onLoadで次のコードを呼び出して、最初に穴を占有します。
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
LOGD("I am ptraced");
exit(0);
}else{
LOGD("ptrace success");
}
マルチプロセスのptrace親プロセスを使用して、デバッグされているかどうかを検出することもできます。オンラインで参照されるコードは次のとおりです。
static void anti_ptrace()
{
pid_t child;
child = fork();
if (child)
{
//等待子进程退出
wait(NULL);
}
else
{
// 获取父进程的pid
pid_t parent = getppid();
LOGD("ptrace attach start");
// ptrace附加父进程
if (ptrace(PTRACE_ATTACH, parent, 0, 0) < 0)
{
LOGD("ptrace attach failure");
//attach父进程失败,说明被调试了,进入死循环,也可以考虑用杀进程的方式杀掉父进程
//kill(parent,SIGKILL);
while(1);
}
LOGD("ptrace attach success");
//说明没有被调试,附加成功
// 释放附加的进程
ptrace(PTRACE_DETACH, parent, 0, 0);
// 结束当前子进程
exit(0);
}
}
2、ソースコードの変更
上記のアンチデバッグメソッドの場合、ソースコードのptraceロジックを次のように変更できます。
long ptrace(int req、...){
bool is_peek = (req == PTRACE_PEEKUSR || req == PTRACE_PEEKTEXT || req == PTRACE_PEEKDATA);
long peek_result;
va_list args;
va_start(args, req);
pid_t pid = va_arg(args, pid_t);
void* addr = va_arg(args, void*);
void* data;
if (is_peek) {
data = &peek_result;
} else {
data = va_arg(args, void*);
}
va_end(args);
///ADD START
int caller_uid=getuid();
//int caller_pid=getpid();
//caller_uid>10000说明是普通App调用
if(caller_uid>10000)
{
if(req ==PTRACE_TRACEME)
{
//自己ptrace自己,直接返回成功
return 0;
}
if(req==PTRACE_ATTACH)
{
int caller_ppid=getppid();
if(caller_ppid==pid)
{
//如果是子进程ptrace父进程,直接返回成功
return 0;
}
//TODO 也可以通过读取/proc/$pid/status获取进程的uid,如果uid和当前调用ptrace的uid一致,也直接返回成功
}
}
///ADD END
long result = __ptrace(req, pid, addr, data);
if (is_peek && result == 0) {
return peek_result;
}
return result;
}
変更が完了したら、次のコマンドを実行してフラッシュテストをコンパイルします:
source build/envsetup.sh
breakfast oneplus3
brunch oneplus3
三、拡張
ptrace関数はハイジャックされやすいため、多くのアプリは内部でptrace関数を使用し、ptraceを直接呼び出すことはありません。代わりに、内部でsyscallを使用するか、インラインアセンブリを使用してptraceを呼び出す関数を実装します。したがって、カーネルフックptraceモジュールは、さまざまなptrace呼び出しを考慮に入れるように開発できます。カーネルシステムコールの傍受については、https://www.anquanke.com/post/id/85375の記事を参照してください。
その他の関連記事を入手するには、WeChatパブリックアカウントをフォローしてください。