1. ptrace 

 ptrace는 여러 유닉스와 유닉스 계열 운영 체제에서 발견되는 시스템 호출이다. ptrace(이 이름은 "process trace"의 축약형이다)를 통해 컨트롤러가 대상의 내부 상태를 조사하고 조작하게 함으로써, 한 프로세스가 다른 프로세스를 제어할 수 있다. ptrace는 디버거와 다른 코드 분석, 특히 소프트웨어 개발을 도와주는 도구들에서 사용된다.

[출처] 위키백과 https://ko.wikipedia.org/wiki/Ptrace


2. 목적

 본래의 목적은 안드로이드 LD_PRELOAD 환경 변수에 라이브러리명을 인젝션하여 특정 함수를 후킹하는 것

2-1) LD_PRELOAD 

 프로세스 실행 전 라이브러리 로딩 과정에서 해당 환경변수에 추가되어있는 라이브러리를 우선적으로 로딩하게 만드는 환경변수

2-2) 메인아이디어 

 zygote를 kill하게되면 init프로세스에서 zygote프로세스를 재생성하며, 이때 LD_PRELOAD환경 변수가 재로드되고, 이때 값을 조작하여 원하는 라이브러리를 인젝션한다.


2-3) 목적 달성 과정

 - zygote process kill (성공)

 - new zygote process의 pid를 알아냄 (성공)

 - new zygote process에서 실행하는 execve 실행 시점에서 환경변수 값 조작 (실패)

 - 후킹 여부 확인 (실패)


3. aarch32 vs aarch64

3-1) register갯수가 다름

 - register갯수가 대폭 추가되면서 ptrace를 이용하여 system call을 이해하기 위해 사용되는 register번호 또한 달라졌으며, 그에 따라 코드에서 사용하는 구조체 및 상수 또한 달라졌다.

 

aarch32 

aarch64 

system call number표시 reg 

 8

 IP reg

ARM_ip 

 12

 return 값 reg

reg표시 structure

pt_regs

 user_regs_struct

 syscall number 참조

 리눅스 syscall number와 동일

ndk-bundle\include\asm-generic\unistd.h


 - aarch64 레지스터 설명

r30 (LR): The Link Register, is used as the subroutine link register (LR) and stores the return address when Branch with Link operations are performed.

r29 (FP): The Frame Pointer

r19…r28: Callee-saved registers

r18: The Platform Register, if needed; otherwise a temporary register.

r17 (IP1): The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register.

r16 (IP0): The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register.

r9…r15: Temporary registers

r8: Indirect result location register

r0…r7: Parameter/result registers


3-2) zygote 프로세스

 - aarch32에서는 zygote process밖에 존재 하지 않지만, aarch64에서는 zygote / zygote64 process가 존재함 

 - aarch64에서 zygote 프로세스는 두 개지만 zygote process를 kill하면 zygote64 또한 재 실행되는 것으로 보임


3-3) zygote 프로세스 kill 후

 - 새 프로세스 생성 전 aarch32에서는 stat64 system call을 사용하여 실행 바이너리(app_process)의 정보를 얻어 오지만, aarch64에서는 fstat64 system call을 사용하여 실행 바이너리(app_process32 / app_process64)의 정보를 얻어오며, aarch64의 app_process 바이너리는 app_process64의 심볼릭 링크이다.

 - aarch32에서는 fork system call로 zygote process를 생성하지만 aarch64에서는 clone system call을 이용

 - 개인적으로 fork / clone system call의 가장 큰 차이점은 프로세스 복사(생성) 이 후 실행 할 바이너리를 경로로 넘겨주느냐(fork), file descriptor(clone)의 차이가 가장 큰 외적 차이로 보이며, 조사를 통한 내적 차이는 parent process로부터 메모리 스택을 공유(clone) / 비공유(fork)라고 생각하고, 전반적으로 clone의 system call이 fork에 비해 비교적 비용이 낮은 것으로 설명이 되어있었다.


3-4) execve system call 탐지 가능 여부

이 부분은 aarch64에서도 아마도(?) 탐지가 가능할 것이나 내 능력/자료가 부족한 것으로 생각된다.

 - ptrace를 사용하여 레지스터 값들을 조사할 때 실행되는 system call number에 execve system call에 해당하는 220번이 탐지 되지 않음

 - strace를 이용하여 값을 조사했을 때는 execve system call이 실행되는 것이 확인됨


4. aarch64에서 ptrace사용

4-1) 레지스터 값을 가져오기 위한 구조체 선언

1
2
3
4
5
6
7
8
9
10
//#include <linux/PTRACE.h>
//#include <sys/PTRACE.h>
 
//aarch64에서 달라진 구조체
struct user_regs_struct gregs;
 
//aarch64에서 추가로 필요한 부분
struct iovec iovecs;
iovecs.iov_base = &gregs;
iovecs.iov_len  = sizeof(gregs);


4-2) ptrace attatch

1
2
3
4
5
6
7
8
// step 1. init프로세스에 PTRACE attatch 
ret = PTRACE(PTRACE_ATTACH, INIT_PID, (void *)10);
if(ERROR == ret){
    printf("[ERROR] PTRACE_ATTACH failed return = %d\n", ret);        
}
else{
    printf("[INFO] PTRACE_ATTACH SUCCESS\n");    
}



4-3) 레지스터 값 얻어오기

1
2
3
4
5
6
7
8
9
10
11
// cpu 레지스터 획득
ret = PTRACE(PTRACE_GETREGSET, INIT_PID, (void *)NT_PRSTATUS, &iovecs);
        
if ( ret < 0 ) {
    printf("[ERROR] PTRACE_GETREGSET ERROR : %d\n", ret);
            
}       
else{
    printf("[INFO] PTRACE_GETREGSET SUCCESS : %d\n", ret);                
}








4-4) system call 탐지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define SYSCALL_REGISTER(r)     r.regs[8]
#define RESULT_REGISTER(r)      r.regs[0]
#define IP_REGISTER(r)          r.regs[12]
 
//gregs.regs[28]이 0이 무엇을 뜻하는 지는 알수 없으나,
//여러 번 반복해보는 과정을 통해 0인 경우일 때가 new pid가 리턴되는 것을 확인함
//그 중에서도 리턴 값이 정상인 것과 비정상인 값이 있었는데,
//정상 값을 찾기 위해 maximum pid값을 지정함(cat /proc/sys/kernel/pid_max -> 32768)
if ( 220 == SYSCALL_REGISTER(gregs) && 
      40 == IP_REGISTER(gregs)        && 
       0 == gregs.regs[28]             &&
   32768 >= RESULT_REGISTER(gregs) ) {
            printf("[*] Found a new zygote64 process: %ld\n", RESULT_REGISTER(gregs));
            
            new_zygote_pid = RESULT_REGISTER(gregs);
}



4-5) ptrace detach

1
2
3
4
5
6
7
//ptarce detach
if(PTRACE(PTRACE_DETACH, INIT_PID, (void*)10< 0) {
        printf("[ERROR] PTRACE_DETACH error\n");
}
else{
        printf("[INFO] PTRACE_DETACH SUCCESS\n");
}



+ Recent posts