1. Introducción a ptrace en Android 10
1. Seguimiento de la ubicación del código fuente
En Android10, la ruta del archivo de definición de la función ptrace es:
bionic/libc/include/sys/ptrace.h
El prototipo de función se define de la siguiente manera:
long ptrace(int __request, ...);
La ruta del archivo de la función ptrace es:
bionic/libc/bionic/ptrace.cpp
El código de implementación de la función es el siguiente:
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;
}
Desde el código de implementación de ptrace, podemos ver que finalmente se llama a la función __ptrace. Después del seguimiento del código, la función __ptrace se implementa a través del ensamblaje de diferentes plataformas y finalmente llama a la llamada al sistema __NR_ptrace.
Ruta del archivo del código de implementación __ptrace de la plataforma Android arm:
bionic/libc/arch-arm/syscalls/__ptrace.S
El código de implementación es el siguiente:
#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)
Ruta del archivo del código de implementación __ptrace de la plataforma Android arm64:
bionic/libc/arch-arm64/syscalls/__ptrace.S
El código de implementación es el siguiente:
#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. Función
ptrace puede permitir que un proceso monitoree y controle la ejecución de otro proceso, y modifique la memoria y los registros del proceso monitoreado. Se utiliza principalmente para la depuración de puntos de interrupción del depurador, seguimiento de llamadas del sistema, etc. Por ejemplo, la herramienta strace proporcionada por el sistema Android puede rastrear todas las llamadas al sistema ejecutadas por la aplicación, como se muestra a continuación:
En la protección de aplicaciones de Android, ptrace se usa ampliamente para anti-depuración. Un proceso sólo puede ser ptrace una vez. Si se llama primero al método ptrace, puede evitar que otros depuren nuestro programa. Este es el legendario pozo preventivo. Por ejemplo, llame al siguiente código en JNI_onLoad para ocupar el hoyo primero:
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
LOGD("I am ptraced");
exit(0);
}else{
LOGD("ptrace success");
}
También puede utilizar el proceso principal ptrace multiproceso para detectar si está depurado, el código al que se hace referencia en línea es el siguiente:
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);
}
}
Dos, modificación del código fuente
Para los métodos anti-depuración anteriores, podemos cambiar la lógica ptrace en el código fuente a lo siguiente:
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;
}
Después de completar la modificación, ejecute el siguiente comando para compilar la prueba flash:
source build/envsetup.sh
breakfast oneplus3
brunch oneplus3
Tres, expansión
Dado que la función ptrace es fácil de secuestrar, muchas aplicaciones usan la función ptrace internamente y no llamarán directamente a ptrace. En su lugar, use syscall internamente o use ensamblado en línea para implementar la función de llamar a ptrace. Por lo tanto, el módulo ptrace hook del kernel se puede desarrollar para tener en cuenta varias llamadas ptrace. Acerca de la interceptación de llamadas del sistema kernel, puede consultar este artículo: https://www.anquanke.com/post/id/85375.
Siga la cuenta pública de WeChat para obtener más artículos relacionados: