一、Android ptrace注入基礎
1.可執行文件建立
首先建立一個ELF可執行文件target,使用ndk-build進行編譯
需要先在任意地方建立一個jni,然後在jni目錄下建立Android.mk,Application.mk,target.c
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := target
LOCAL_SRC_FILES := target.c
include $(BUILD_EXECUTABLE)
Application.mk(ABI可以添加其他類型如x86)
APP_ABI := armeabi-v7a
target.c:
#include <stdio.h>
int count = 0;
void sevenWeapons(int number)
{
char* str = "Hello,lzh!";
printf("%s %d\n",str,number);
}
int main()
{
while(1)
{
sevenWeapons(count);
count++;
sleep(1);
}
return 0;
}
編寫完成後,使用命令行進入jni目錄,執行ndk-build
然後在jni的上一個目錄就能看到一個libs目錄,進入找到target,將其push到安卓中,賦予其權限,然後執行,就能看到如下情況
Hello,lzh! 0
Hello,lzh! 1
...
2. ptrace注入實現(一)
首先還是建立相關文件jni目錄
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hook1
LOCAL_SRC_FILES := hook1.c
include $(BUILD_EXECUTABLE)
Application.mk
APP_ABI := armeabi-v7a
hook1.c
long getSysCallNo(int pid, struct pt_regs *regs)
{
long scno = 0;
scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
if(scno == 0)
return 0;
if (scno == 0xef000000) {
scno = regs->ARM_r7;
} else {
if ((scno & 0x0ff00000) != 0x0f900000) {
return -1;
}
scno &= 0x000fffff;
}
return scno;
}
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("Before SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
}
}
void hookSysCallAfter(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("After SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write return: %ld\n",regs.ARM_r0);
}
printf("\n");
}
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n", argv[0]);
return 1;
}
pid_t pid;
int status;
pid = atoi(argv[1]);
if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
while(1)
{
wait(&status);
hookSysCallBefore(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
wait(&status);
hookSysCallAfter(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}
hook1.c代碼分析
getSysCallNo:
作用:獲取system call編號
//獲取system call編號
long getSysCallNo(int pid, struct pt_regs *regs)
{
long scno = 0;
//獲取系統調用的SWI指令,這裏一共有兩個指令EABI,OABI,分別對應兩個機器碼
scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
if(scno == 0)
return 0;
//爲EABI的時候,從r7中直接獲取調用號
if (scno == 0xef000000) {
scno = regs->ARM_r7;
} else {
//爲OABI的時候通過公式立即數(scno)=調用號 | 0x900000,先獲取立即數,在計算出調用號
if ((scno & 0x0ff00000) != 0x0f900000) {
return -1;
}
scno &= 0x000fffff;
}
return scno;
}
獲取到system call的編號之後,我們可以進而獲取到各個參數的值,我們可以看到第一個SysCallNo是162,也就是sleep函數。第二個SysCallNo是4,也就是write函數,因爲printf本質就是調用write這個系統調用來完成的。
//hook system call前
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("Before SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
}
}
//hook system call後
void hookSysCallAfter(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("After SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write return: %ld\n",regs.ARM_r0);
}
printf("\n");
}
編寫完成後,還是執行ndk-build,得到文件push進安卓手機
然後執行重新執行target文件,再使用下面命令獲取target的pid
adb shell "ps |grep "target""
獲取到pid後,在執行hook1
./hook1 19140
hook2.c代碼分析
還是hook之前的代碼,代碼如下,想了解原理就直接看註釋吧
Applicaton.mk
#APP_OPTIM := release
APP_PLATFORM := android-15
APP_ABI := armeabi-v7a
NDK_TOOLCHAIN_VERSION=4.9
APP_PIE := false
Android.mk:
LOCAL_PATH := $(call my-dir)
APP_CFLAGS := -std=c++11
include $(CLEAR_VARS)
LOCAL_MODULE := hook2
LOCAL_SRC_FILES := hook2.c
include $(BUILD_EXECUTABLE)
hook2.c
//獲取system call編號
long getSysCallNo(int pid, struct pt_regs *regs)
{
long scno = 0;
scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
if(scno == 0)
return 0;
if (scno == 0xef000000) {
scno = regs->ARM_r7;
} else {
if ((scno & 0x0ff00000) != 0x0f900000) {
return -1;
}
scno &= 0x000fffff;
}
return scno;
}
void getdata(pid_t child, long addr,
char *str, int len)
{ char *laddr;
int i, j;
union u {
long val;//字符地址
char chars[long_size];//字符
}data;
i = 0;
j = len / long_size;//在arm32下long類型長度爲4
laddr = str;
//先處理能除的部分
while(i < j) {
data.val = ptrace(PTRACE_PEEKDATA,
child, addr + i * 4,
NULL);//注入
memcpy(laddr, data.chars, long_size);//將data.chars複製到laddr處
++i;
laddr += long_size;//增加一個long的長度
}//類似於鏈表
j = len % long_size;
//在處理剩下的
if(j != 0) {
data.val = ptrace(PTRACE_PEEKDATA,
child, addr + i * 4,
NULL);
memcpy(laddr, data.chars, j);
}
str[len] = '\0';
}
//原理和get差不多,使用ptrace注入的方式打印
void putdata(pid_t child, long addr,
char *str, int len)
{ char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
}data;
i = 0;
j = len / long_size;
laddr = str;
while(i < j) {
memcpy(data.chars, laddr, long_size);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
}
}
void modifyString(pid_t pid, long addr, long strlen)
{ //注意這裏的strlen是地址的長度!
char* str;
str = (char *)calloc((strlen+1) * sizeof(char), 1);
getdata(pid, addr, str, strlen);
//reverse(str);
str[0]='l';//將字符串的第一個字符改成'l'
printf("Hook -------\n");
printf("%s\n",str);
putdata(pid, addr, str, strlen);
}
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
//printf("Before SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
modifyString(pid, regs.ARM_r1, regs.ARM_r2);
}
}
void hookSysCallAfter(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("After SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write return: %ld\n",regs.ARM_r0);
}
printf("\n");
}
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n", argv[0]);
return 1;
}
pid_t pid;
int status,errno;
pid = atoi(argv[1]);
if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
while(1)
{
wait(&status);
hookSysCallBefore(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
//wait(&status);
//hookSysCallAfter(pid);
//ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}
然後運行,效果如下: