ptrace injection game introduction

The Android system uses the Linux kernel. Many technologies on the Linux system can be applied to the Android system. The technology of ptrace injection into remote processes on the Android system is one of them. This chapter will introduce the complete process of ptrace injection.

1. Introduction to ptrace function

The core of the ptrace injection technology is the ptrace function. During the ptrace injection process, the ptrace function will be called multiple times. As mentioned in the Linux man document (hyperlink to: http://man7.org/linux/man-pages/man2/ptrace.2.html), the ptrace function provides a method for a process to monitor and control other processes, After injecting into a process, the parent process can also read and modify the memory space and register values ​​of the child process.

The prototype of the ptrace function is as follows, where the request parameter is a union, which determines the behavior of the ptrace function, the pid parameter is the ID of the remote process, the addr parameter and the data parameter have different meanings under different request parameter values .
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

The request parameter has many values. Due to space limitations, only a part of the request parameters that need to be used in the process of ptrace injection process are introduced here.

PTRACE_ATTACH means attaching to the specified remote process;

PTRACE_DETACH, detaches from the specified remote process

PTRACE_GETREGS, means to read the current register environment of the remote process

PTRACE_SETREGS, which means setting the register environment of the remote process

PTRACE_CONT, which means to keep the remote process running

PTRACE_PEEKTEXT, read a word-sized data from the specified memory address of the remote process

PTRACE_POKETEXT, write a word-sized data to the specified memory address of the remote process

Two, ptrace injection process flow

2.1 Overview of ptrace injection
The purpose of ptrace injection is to inject external modules into the game process, and then execute the code of the injected module, which can modify the code and data of the game process. Currently, there are two ways to implement ptrace injection module into the remote process, one of which is to use ptrace to inject shellcode into the memory space of the remote process, and then load the remote process module by executing the shellcode; the other method is to directly call dlopen\ Functions such as dlsym load the injected module and execute the specified code. The implementation methods of the two methods are slightly different, but generally speaking, the code is executed in the remote process space through the ptrace function. This chapter only introduces the second method in detail. The attachment contains the codes implemented by the two injection methods. If you are interested readers can understand.

As shown in Figure 1-1 below, the overall process of injecting ptrace into a remote process is not complicated, but there are some details that need attention, and a little carelessness will cause the remote process to crash. The following subsections describe each step in the process in detail.
insert image description here
2.2 Attach to the remote process
The first step of ptrace injection is to attach to the remote process first, as shown below, attaching to the remote process is by calling the ptrace function whose request parameter is PTRACE_ATTACH, and pid is the ID of the remote process that needs to be attached. The addr parameter and the data parameter are NULL.

ptrace(PTRACE_ATTACH, pid, NULL, NULL);

After attaching to the remote process, the execution of the remote process will be interrupted. At this time, the parent process can determine whether the child process enters the pause state by calling the waitpid function. The function prototype of waitpid is as follows. When the options parameter is WUNTRACED, it means that if the remote process corresponding to pid enters the suspended state, it will return immediately, which can be used to wait for the remote process to enter the suspended state.

pid_t waitpid(pid_t pid,int * status,int options);

2.3 Reading and writing register values
​​Before changing the execution flow of the remote process through ptrace, it is necessary to read all the register values ​​​​of the remote process and save them. When detach, write the saved original register values ​​to the remote process to restore the original value of the remote process. There are execution processes.

As shown below, for the ptrace call for reading and writing register values, the request parameters are PTRACE_GETREGS and PTRACE_SETREGS respectively, and pid is the ID of the corresponding process.

ptrace(PTRACE_GETREGS, pid, NULL, regs);

ptrace(PTRACE_SETREGS, pid, NULL, regs);

Under the ARM processor, the regs of the data parameter is a pointer to the pt_regs structure, and the register value obtained from the remote process will be stored in the structure. The definition of the pt_regs structure is as follows, where the ARM_r0 member is used to store the value of the R0 register. The function The return value after the call will be stored in the R0 register, the ARM_pc member stores the current execution address, the ARM_sp member stores the current stack top address, the ARM_lr member stores the return address, and the ARM_cpsr member stores the value of the status register.

struct pt_regs {

long uregs[18];

};

#define ARM_cpsr uregs[16]

#define ARM_pc uregs[15]

#define ARM_lr uregs[14]

#define ARM_sp uregs[13]

#define ARM_ip uregs[12]

#define ARM_fp uregs[11]

#define ARM_r10 uregs[10]

#define ARM_r9 uregs[9]

#define ARM_r8 uregs[8]

#define ARM_r7 uregs[7]

#define ARM_r6 uregs[6]

#define ARM_r5 uregs[5]

#define ARM_r4 uregs[4]

#define ARM_r3 uregs[3]

#define ARM_r2 uregs[2]

#define ARM_r1 uregs[1]

#define ARM_r0 uregs[0]

#define ARM_ORIG_r0 uregs[17]

2.4 Reading and writing data in remote process memory
Calling the ptrace function whose request parameter is PTRACE_PEEKTEXT can read data from the memory space of the remote process, one word size of data at a time. As shown below, the addr parameter is the memory address of the remote process to read data, and the return value is the read data.

ptrace(PTRACE_PEEKTEXT, pid, pCurSrcBuf, 0);

ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) ;

Calling the ptrace function whose request parameter is PTRACE_POKETEXT can write data into the memory space of the remote process, and also write data of a word size at a time. The addr parameter of the ptrace function is the remote process memory address to write data into, and the data parameter is The data to write.

When writing data, you need to pay attention. If the length of the written data is not a multiple of the word size, when writing the last data that is less than the word size, you must first save the high-order data at the original address.

As shown in the following code, first read the data of a word size in the original memory through the ptrace function whose request parameter is PTRACE_PEEKTEXT, then copy the data to be written to the lower bits of the read data, and then call the ptrace function to convert the modified Data is written to the memory address of the remote process.

lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurDestBuf, NULL);

memcpy((void *)(&lTmpBuf), pCurSrcBuf, nRemainCount);

if (ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0)

{

LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);

return -1;

}

2.5 Remote call function
In the ARM processor, the first four parameters of the function call are passed through the R0-R3 registers, and the remaining parameters are pushed into the stack in order from right to left. As shown in the following code, before calling the function remotely, it is necessary to judge the number of parameters of the function call first. If it is less than 4, write the parameters into the R0-R3 registers in order. If it is more than 4, first adjust the SP register Allocate space in the stack, and then write the remaining parameters to the stack by calling the ptrace function.

for (i = 0; i < num_params && i < 4; i ++) {

 regs->uregs[i] = parameters[i];    

}

if (i < num_params) {

regs->ARM_sp -= (num_params - i) * sizeof(long) ;

if (ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)¶meters[i], (num_params - i) * sizeof(long)) == -1)

return -1;

}

After writing the parameters of the function, modify the PC register of the process to the address of the function to be executed. There is one thing to note here. There are two instructions, ARM and Thumb, under the ARM architecture. Therefore, it is necessary to determine which instruction the function is parsed into before calling the function. The code shown below judges the call by whether the lowest bit of the address is 1. The instruction at the address is ARM or Thumb. If it is a Thumb instruction, you need to reset the lowest bit to 0, and set the T flag of the CPSR register. If it is an ARM instruction, reset the T flag of the CPSR register.

if (regs->ARM_pc & 1) { /* thumb */

regs->ARM_pc &= (~1u);

regs->ARM_cpsr |= CPSR_T_MASK;

} else { /* arm */

regs->ARM_cpsr &= ~CPSR_T_MASK;

}

Before resuming the remote process, it is also necessary to set the LR register value of the remote process to 0, and call the waitpid function whose options parameter is WUNTRACED in the local process to wait for the remote process to re-enter the suspended state. After the function call of the remote process ends, it will jump to the address stored in the LR register, but because the LR register is set to 0, it will cause an error in the execution of the remote process. At this time, the process will enter a pause state, and the local process will wait for the end. By reading The return result of the remote function call can be obtained by fetching the R0 register of the remote process. The above is a complete process of calling the remote function.

In the ptrace injection process, the function needs to be called multiple times. In addition to calling the function of the injected module, it is also necessary to call the mmap function to allocate memory in the remote process address space, call the dlopen function to remotely load the injected module, and call the dlsym function to obtain the injected module. Inject the address of the function corresponding to the module, and call the dlclose function to close the loaded module. The prototypes of these functions are as follows,

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

void * dlopen( const char * pathname, int mode);

voiddlsym(voidhandle,constchar*symbol);

int dlclose (void *handle);

Before calling these functions, you need to obtain the addresses of these system functions in the remote process first. The mmap function is in the "/system/lib/libc.so" module, and the dlopen, dlsym, and dlclose functions are all in "/system/ bin/linker" module.

Reading "/proc/pid/maps" can get the loading base address of the system module in the local process and the remote process. To get the virtual address of functions such as mmap in the memory space of the remote process, you can calculate the relative function of mmap and other functions in the local process. Based on the address offset of the module, and then use this address offset plus the base address of the corresponding module of the remote process, this address is the virtual address of the corresponding function in the memory space of the remote process.

2.6 Restoring the register value
Before detach from the remote process, it is necessary to restore the original register environment of the remote process to ensure that the original execution flow of the remote process is not damaged. If the register value is not restored, the remote process will crash during detach.

2.7 Detach process
Detaching from the remote process is the last step of ptrace injection, and the injected process will continue to run after detach. As shown below, detach from the remote process is to call the ptrace function whose request parameter is PTRACE_DETACH.

ptrace(PTRACE_DETACH, pid, NULL, 0);

Guess you like

Origin blog.csdn.net/douluo998/article/details/129915714