Linux 系统调用 Ptrace 详解
?
From:https://blog.csdn.net/u012417380/article/details/60470075
Ptrace 詳解:https://www.cnblogs.com/tangr206/articles/3094358.html
ptrace運(yùn)行原理及使用詳解:https://blog.csdn.net/edonlii/article/details/8717029
?
ptrace 函數(shù)代碼分析
ptrace 函數(shù)深入分析:https://www.cnblogs.com/heixiang/p/10988992.html
Linux 源碼分析之 Ptrace:https://blog.csdn.net/u012417380/article/details/60468697
?
?
?
一、系統(tǒng)調(diào)用
?
操作系統(tǒng)提供一系列 系統(tǒng)調(diào)用?函數(shù) 來(lái)為應(yīng)用程序提供服務(wù)。關(guān)于系統(tǒng)調(diào)用的詳細(xì)相關(guān)知識(shí),可以查看 《程序員的自我修養(yǎng)》 第十二章。對(duì)于 x86 操作系統(tǒng)來(lái)說(shuō),用中斷命令 "int 0x80"?來(lái)進(jìn)行系統(tǒng)調(diào)用,系統(tǒng)調(diào)用前,需要將系統(tǒng)調(diào)用號(hào)放入到 %EAX 寄存器中,將系統(tǒng)的參數(shù)依次放入到寄存器 %ebx、%ecx、%edx 以及 %esi 和 %edi 中。
以 write 系統(tǒng)調(diào)用 為例:
write(2,"Hello",5);在 32位系統(tǒng)中會(huì)轉(zhuǎn)換成:
movl $1,%eax movl $2,%ebx movl $hello,%ecx movl $5,%edx int $0x80其中 1 為 write 的系統(tǒng)調(diào)用號(hào),所有的系統(tǒng)調(diào)用號(hào)定義在 unistd.h 文件中,$hello 表示字符串 "Hello"?的地址;
32位 Linux 系統(tǒng)通過(guò) 0x80 中斷來(lái)進(jìn)行系統(tǒng)調(diào)用。
64位系統(tǒng)用戶應(yīng)用層用整數(shù)寄存器 %rdi、%rsi、%rdx、%rcx、%r8 以及 %r9 來(lái)傳參。
而內(nèi)核接口用 %rdi 、%rsi、%rdx、%r10、&r8 以及 %r10 來(lái)傳參,并且用 syscall 指令而不是 80 中斷進(jìn)行系統(tǒng)調(diào)用。
x86 和 x64 都用寄存器 rax 來(lái)保存調(diào)用號(hào)和返回值。
?
?
二、ptrace 函數(shù)簡(jiǎn)介
?
【 ptrace系統(tǒng)調(diào)用 】
功能描述:
提供父進(jìn)程觀察和控制另一個(gè)進(jìn)程執(zhí)行的機(jī)制,同時(shí)提供查詢和修改另一進(jìn)程的核心影像與寄存器的能力。主要用于執(zhí)行斷點(diǎn)調(diào)試和系統(tǒng)調(diào)用跟蹤。父進(jìn)程可通過(guò)調(diào)用fork,接著指定所產(chǎn)生的子進(jìn)程的PTRACE_TRACEME行為,最后使用exec等操作來(lái)初始化一個(gè)進(jìn)程跟蹤??商娲淖龇ㄊ?#xff0c;父進(jìn)程通過(guò)PTRACE_ATTACH請(qǐng)求跟蹤一個(gè)現(xiàn)存進(jìn)程的執(zhí)行。
當(dāng)子進(jìn)程被跟蹤時(shí),每次接收到信號(hào)都會(huì)停止執(zhí)行,即使不對(duì)信號(hào)進(jìn)行處理(SIGKILL信號(hào)除外)。父進(jìn)程下次執(zhí)行wait調(diào)用時(shí),會(huì)接收到核心的通告,并可能檢查和修改已停止的子進(jìn)程。父進(jìn)程使子進(jìn)程繼續(xù)執(zhí)行,并有可能忽略接收到的信號(hào)。
?
ptrace() 系統(tǒng)調(diào)用函數(shù)提供了一個(gè)進(jìn)程(the “tracer”)監(jiān)察和控制另一個(gè)進(jìn)程(the “tracee”)的方法。并且可以檢查和改變“tracee”進(jìn)程的內(nèi)存和寄存器里的數(shù)據(jù)。它可以用來(lái)實(shí)現(xiàn)斷點(diǎn)調(diào)試和系統(tǒng)調(diào)用跟蹤。
tracee首先需要被附著到tracer。在多線程進(jìn)程中,每個(gè)線程都可以被附著到一個(gè)tracer。ptrace命令總是以ptrace(PTARCE_foo,pid,..)的形式發(fā)送到tracee進(jìn)程。pid是tracee線程ID。
當(dāng)一個(gè)進(jìn)程可以開(kāi)始跟蹤進(jìn)程通過(guò)調(diào)用fork函數(shù)創(chuàng)建子進(jìn)程并讓子進(jìn)程執(zhí)行PTRACE_TRACEME,然后子進(jìn)程再調(diào)用execve()(如果當(dāng)前進(jìn)程被ptrace,execve()成功執(zhí)行后 SIGTRAP信號(hào)量會(huì)被發(fā)送到該進(jìn)程)。一個(gè)進(jìn)程也可以使用”P(pán)TRACE_ATTACH”或者”P(pán)TRACE_SEIZE”來(lái)跟蹤另一個(gè)進(jìn)程。
當(dāng)進(jìn)程被跟蹤后,每當(dāng)信號(hào)量傳來(lái),甚至信號(hào)量會(huì)被忽略時(shí),tracee會(huì)暫停。tracer會(huì)在下次調(diào)用waitpid(wstatus)(或者其它wait系統(tǒng)調(diào)用)處被通知。該調(diào)用會(huì)返回一個(gè)包含tracee暫停原因信息的狀態(tài)碼。當(dāng)tracee暫停后,tracer可以使用一系列ptrace請(qǐng)求來(lái)查看和修改tracee中的信息。tracer接著可以讓tracee繼續(xù)執(zhí)行。tracee傳遞給tracer中的信號(hào)量通常被忽略。
當(dāng)PTRACE_O_TRACEEXEC項(xiàng)未起作用時(shí),所有成功執(zhí)行execve()的tracee進(jìn)程會(huì)被發(fā)送一個(gè) SIGTRAP信號(hào)量后暫停,在新程序執(zhí)行之前,父進(jìn)程將會(huì)取得該進(jìn)程的控制權(quán)。
當(dāng)tracer結(jié)束跟蹤后,可以通過(guò)調(diào)用 PTRACE_DETACH 繼續(xù)讓 tracee 執(zhí)行。
prace更多相關(guān)信息可以查看:http://man7.org/linux/man-pages/man2/ptrace.2.html 官方文檔。
簡(jiǎn)單的說(shuō):就是注入到另一個(gè)進(jìn)程里面,外掛用這個(gè)函數(shù)比較多。
?
用法:
#include <sys/ptrace.h>long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);?
參數(shù):
- request:請(qǐng)求執(zhí)行的行為,可能選擇有
PTRACE_TRACEME //指示父進(jìn)程跟蹤某個(gè)子進(jìn)程的執(zhí)行。任何傳給子進(jìn)程的信號(hào)將導(dǎo)致其停止執(zhí)行,同時(shí)父進(jìn)程調(diào)用wait()時(shí)會(huì)得到通告。之后,子進(jìn)程調(diào)用exec()時(shí),核心會(huì)給它傳送SIGTRAP信號(hào),在新程序開(kāi)始執(zhí)行前,給予父進(jìn)程控制的機(jī)會(huì)。pid, addr, 和 data參數(shù)被忽略。以上是唯一由子進(jìn)程使用的請(qǐng)求,剩下部分將由父進(jìn)程使用的請(qǐng)求。
PTRACE_PEEKTEXT, PTRACE_PEEKDATA //從子進(jìn)程內(nèi)存空間addr指向的位置讀取一個(gè)字,并作為調(diào)用的結(jié)果返回。Linux內(nèi)部對(duì)文本段和數(shù)據(jù)段不加區(qū)分,所以目前這兩個(gè)請(qǐng)求相等。data參數(shù)被忽略。
PTRACE_PEEKUSR //從子進(jìn)程的用戶區(qū)addr指向的位置讀取一個(gè)字,并作為調(diào)用的結(jié)果返回。
PTRACE_POKETEXT, PTRACE_POKEDATA //將data指向的字拷貝到子進(jìn)程內(nèi)存空間由addr指向的位置。
PTRACE_POKEUSR //將data指向的字拷貝到子進(jìn)程用戶區(qū)由addr指向的位置。
PTRACE_GETREGS, PTRACE_GETFPREGS //將子進(jìn)程通用和浮點(diǎn)寄存器的值拷貝到父進(jìn)程內(nèi)由data指向的位置。addr參數(shù)被忽略。
PTRACE_GETSIGINFO //獲取導(dǎo)致子進(jìn)程停止執(zhí)行的信號(hào)信息,并將其存放在父進(jìn)程內(nèi)由data指向的位置。addr參數(shù)被忽略。
PTRACE_SETREGS, PTRACE_SETFPREGS //從父進(jìn)程內(nèi)將data指向的數(shù)據(jù)拷貝到子進(jìn)程的通用和浮點(diǎn)寄存器。addr參數(shù)被忽略。
PTRACE_SETSIGINFO //將父進(jìn)程內(nèi)由data指向的數(shù)據(jù)作為siginfo_t結(jié)構(gòu)體拷貝到子進(jìn)程。addr參數(shù)被忽略。
PTRACE_SETOPTIONS //將父進(jìn)程內(nèi)由data指向的值設(shè)定為ptrace選項(xiàng),data作為位掩碼來(lái)解釋,由下面的標(biāo)志指定
PTRACE_O_TRACESYSGOOD //當(dāng)轉(zhuǎn)發(fā)syscall陷阱(traps)時(shí),在信號(hào)編碼中設(shè)置位7,即第一個(gè)字節(jié)的最高位。例如:SIGTRAP | 0x80。這有利于追蹤者識(shí)別一般的陷阱和那些由syscall引起的陷阱。
PTRACE_O_TRACEFORK //通過(guò) (SIGTRAP | PTRACE_EVENT_FORK << 8) 使子進(jìn)程下次調(diào)用fork()時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開(kāi)始執(zhí)行時(shí)就已設(shè)置SIGSTOP信號(hào)的新進(jìn)程。新進(jìn)程的PID可以通過(guò)PTRACE_GETEVENTMSG獲取。
PTRACE_O_TRACEVFORK //通過(guò) (SIGTRAP | PTRACE_EVENT_VFORK << 8) 使子進(jìn)程下次調(diào)用vfork()時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開(kāi)始執(zhí)行時(shí)就已設(shè)置SIGSTOP信號(hào)的新進(jìn)程。新進(jìn)程的PID可以通過(guò)PTRACE_GETEVENTMSG獲取。
PTRACE_O_TRACECLONE //通過(guò) (SIGTRAP | PTRACE_EVENT_CLONE << 8) 使子進(jìn)程下次調(diào)用clone()時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開(kāi)始執(zhí)行時(shí)就已設(shè)置SIGSTOP信號(hào)的新進(jìn)程。新進(jìn)程的PID可以通過(guò)PTRACE_GETEVENTMSG獲取。
PTRACE_O_TRACEEXEC //通過(guò) (IGTRAP | PTRACE_EVENT_EXEC << 8) 使子進(jìn)程下次調(diào)用exec()時(shí)停止其執(zhí)行。
PTRACE_O_TRACEVFORKDONE //通過(guò) (SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8) 使子進(jìn)程下次調(diào)用exec()并完成時(shí)停止其執(zhí)行。
PTRACE_O_TRACEEXIT //通過(guò) (SIGTRAP | PTRACE_EVENT_EXIT << 8) 使子進(jìn)程退出時(shí)停止其執(zhí)行。子進(jìn)程的退出狀態(tài)可通過(guò)PTRACE_GETEVENTMSG。
PTRACE_GETEVENTMSG //獲取剛發(fā)生的ptrace事件消息,并存放在父進(jìn)程內(nèi)由data指向的位置。addr參數(shù)被忽略。
PTRACE_CONT //重啟動(dòng)已停止的進(jìn)程。如果data指向的數(shù)據(jù)并非0,同時(shí)也不是SIGSTOP信號(hào),將會(huì)作為傳遞給子進(jìn)程的信號(hào)來(lái)解釋。那樣,父進(jìn)程可以控制是否將一個(gè)信號(hào)發(fā)送給子進(jìn)程。 addr參數(shù)被忽略。
PTRACE_SYSCALL, PTRACE_SINGLESTEP //如同PTRACE_CONT一樣重啟子進(jìn)程的執(zhí)行,但指定子進(jìn)程在下個(gè)入口或從系統(tǒng)調(diào)用退出時(shí),或者執(zhí)行單個(gè)指令后停止執(zhí)行,這可用于實(shí)現(xiàn)單步調(diào)試。addr參數(shù)被忽略。
PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP //用于用戶模式的程序仿真子進(jìn)程的所有系統(tǒng)調(diào)用。
PTRACE_KILL //給子進(jìn)程發(fā)送SIGKILL信號(hào),從而終止其執(zhí)行。data,addr參數(shù)被忽略。
PTRACE_ATTACH //銜接到pid指定的進(jìn)程,從而使其成為當(dāng)前進(jìn)程的追蹤目標(biāo)。
PTRACE_DETACH //PTRACE_ATTACH的反向操作。 -
pid:目標(biāo)進(jìn)程標(biāo)識(shí)。
-
addr:執(zhí)行 peek 和 poke 操作的目標(biāo)地址。
-
data:對(duì)于 poke 操作,存放數(shù)據(jù)的地方。對(duì)于 peek 操作,獲取數(shù)據(jù)的地方。
?
返回說(shuō)明:
成功執(zhí)行時(shí),PTRACE_PEEK*請(qǐng)求返回所請(qǐng)求的數(shù)據(jù),其它返回0。失敗返回-1,errno被設(shè)為以下的某個(gè)值。由于一個(gè)成功的PTRACE_PEEK*請(qǐng)求可能返回-1,決定錯(cuò)誤是否發(fā)生前,調(diào)用者應(yīng)檢查errno。
EBUSY:分配和釋放調(diào)試寄存器時(shí)出錯(cuò)
EFAULT:讀寫(xiě)不可訪問(wèn)的內(nèi)存空間
EINVAL:嘗試設(shè)置無(wú)效選項(xiàng)
EIO:請(qǐng)求無(wú)效,或者嘗試讀寫(xiě)父子進(jìn)程不可訪問(wèn)的空間
EPERM:沒(méi)有權(quán)限追蹤指定的進(jìn)程
ESRCH:指定的子進(jìn)程不存在,或者當(dāng)前正由調(diào)用者追蹤
?
?
三、示例
?
1. ptrace 追蹤子進(jìn)程執(zhí)行 exec()
#include <stdio.h> #include <unistd.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/reg.h> /* For constants ORIG_RAX etc */ int main(){pid_t child;long orig_rax;child=fork();if(child==0){ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls",NULL);}else{wait(NULL);orig_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);printf("The child made a system call %ld\n",orig_rax);ptrace(PTRACE_CONT,child,NULL,NULL);}}編譯后輸出:
The child made a system call 59 user1@user-virtual-machine:~/hookTest$ a.out attach.c~ ex1.c ex1.o ex2.c~ ex3.c ex3.o ex4.c~ victim.c~ attach.c attach.o ex1.c~ ex2.c ex2.o ex3.c~ ex4.c victim.c victim.oexecl()函數(shù)對(duì)應(yīng)的系統(tǒng)調(diào)用為_(kāi)_NR_execve,系統(tǒng)調(diào)用值為59。父進(jìn)程通過(guò)調(diào)用fork()來(lái)創(chuàng)建子進(jìn)程。在子進(jìn)程中,先運(yùn)行patrce().請(qǐng)求參數(shù)設(shè)為PTRACE_TRACE,來(lái)告訴內(nèi)核當(dāng)前進(jìn)程被父進(jìn)程trace,每當(dāng)有信號(hào)量傳遞到當(dāng)前進(jìn)程,該進(jìn)程會(huì)暫停,提醒父進(jìn)程在wait()調(diào)用處繼續(xù)執(zhí)行。然后再調(diào)用execl()。當(dāng)execl()函數(shù)成功執(zhí)行后,新程序運(yùn)行之前,SIGTRAP信號(hào)量會(huì)被發(fā)送到該進(jìn)程,讓子進(jìn)程停止,這時(shí)父進(jìn)程會(huì)在wait相關(guān)調(diào)用處被通知,獲取子進(jìn)程的控制權(quán),可以查看子進(jìn)程內(nèi)存和寄存器相關(guān)信息。
當(dāng)進(jìn)程進(jìn)行系統(tǒng)調(diào)用時(shí),int會(huì)在內(nèi)核棧中依次壓入用戶態(tài)的寄存器SS、ESP、EFLAGS、CS、EIP.中斷處理程序的SAVE_ALL宏會(huì)將 依次將EAX、EBP、EDI、ESI、EDX、ECX、EBX寄存器值壓入內(nèi)核棧。調(diào)用ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL) 獲取USER area信息時(shí)<sys/reg.h>文件定義了與內(nèi)核棧寄存器數(shù)組順序相同的下標(biāo):
#ifndef _SYS_REG_H #define _SYS_REG_H 1#ifdef __x86_64__ /* Index into an array of 8 byte longs returned from ptrace forlocation of the users' stored general purpose registers. */# define R15 0 # define R14 1 # define R13 2 # define R12 3 # define RBP 4 # define RBX 5 # define R11 6 # define R10 7 # define R9 8 # define R8 9 # define RAX 10 # define RCX 11 # define RDX 12 # define RSI 13 # define RDI 14 # define ORIG_RAX 15 # define RIP 16 # define CS 17 # define EFLAGS 18 # define RSP 19 # define SS 20 # define FS_BASE 21 # define GS_BASE 22 # define DS 23 # define ES 24 # define FS 25 # define GS 26 #else/* Index into an array of 4 byte integers returned from ptrace for* location of the users' stored general purpose registers. */# define EBX 0 # define ECX 1 # define EDX 2 # define ESI 3 # define EDI 4 # define EBP 5 # define EAX 6 # define DS 7 # define ES 8 # define FS 9 # define GS 10 # define ORIG_EAX 11 # define EIP 12 # define CS 13 # define EFL 14 # define UESP 15 # define SS 16 #endif這樣8*ORIG_RAX就找到USER area 中?ORIG_RAX?寄存器值的保存地址。ORIG_RAX保存了系統(tǒng)調(diào)用號(hào)。
當(dāng)檢查完系統(tǒng)調(diào)用之后,可以調(diào)用ptrace并設(shè)置參數(shù)PTRACE_CONT讓子進(jìn)程繼續(xù)進(jìn)行。
?
?
2.讀取子進(jìn)程系統(tǒng)調(diào)用參數(shù)
?
//64位下烏班圖程序#include <sys/ptrace.h> #include <sys/wait.h> #include <sys/reg.h> #include <sys/user.h> #include <sys/syscall.h> #include <stdio.h> int main(){pid_t child;long orig_rax;int status;int iscalling=0;struct user_regs_struct regs;child = fork();if(child==0){ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls","-l","-h",NULL);}else{while(1){wait(&status);if(WIFEXITED(status))break;orig_rax=ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);if(orig_rax == SYS_write){ptrace(PTRACE_GETREGS,child,NULL,®s);if(!iscalling){iscalling =1;printf("SYS_write call with %lld, %lld, %lld\n",regs.rdi,regs.rsi,regs.rdx);} else{printf("SYS_write call return %lld\n",regs.rax);iscalling = 0;} }ptrace(PTRACE_SYSCALL,child,NULL,NULL);}}return 0; }編譯后輸出:
SYS_write call with 1, 140179049189376, 14 總用量 28K SYS_write call return 14 SYS_write call with 1, 140179049189376, 51 -rw-rw-r-- 1 user1 user1 534 2月 26 18:02 ex1.c SYS_write call return 51 SYS_write call with 1, 140179049189376, 52 -rw-rw-r-- 1 user1 user1 534 2月 26 18:02 ex1.c~ SYS_write call return 52 SYS_write call with 1, 140179049189376, 53 -rw-rw-r-- 1 user1 user1 1.1K 3月 2 13:02 hook2.c SYS_write call return 53 SYS_write call with 1, 140179049189376, 54 -rw-rw-r-- 1 user1 user1 1.1K 3月 2 13:02 hook2.c~ SYS_write call return 54 SYS_write call with 1, 140179049189376, 53 -rwxrwxr-x 1 user1 user1 8.6K 3月 2 13:02 hook2.o SYS_write call return 53可以看到ls -l -h 執(zhí)行了六次SYS_write系統(tǒng)調(diào)用。
讀取寄存器中的參數(shù)時(shí),可以使用PTRACE_PEEKUSER一個(gè)字一個(gè)字讀取,也可以使用PTRACE_GETREGS參數(shù)直接將寄存器的值讀取到結(jié)構(gòu)體user_regs_struct 中,該結(jié)構(gòu)體定義在sys/user.h中
對(duì)于PTRACE_STSCALL參數(shù),該參數(shù)會(huì)像PTRACE_CONT一樣使暫停的子進(jìn)程繼續(xù)執(zhí)行,并在子進(jìn)程下次進(jìn)行系統(tǒng)調(diào)用前或系統(tǒng)調(diào)后,向子進(jìn)程發(fā)送SINTRAP信號(hào)量,讓子進(jìn)程暫停。
WIFEXITED函數(shù)(宏)函數(shù)用來(lái)檢查子進(jìn)程是暫停還準(zhǔn)備退出。
?
3.修改子進(jìn)程系統(tǒng)調(diào)用參數(shù)
?
val = ptrace(PTRACE_PEEKDATA,child,addr,NULL)PTRACE_PEEKDATA、PTRACE_PEEKTEXT參數(shù)是在tracee內(nèi)存的addr地址處讀取一個(gè)字(sizeof(long))的數(shù)據(jù),反回值是long 型的,可多次讀取addr
+i*sizeof(long)然后再合并得到最終字符串的內(nèi)容。
現(xiàn)在,我們對(duì)系統(tǒng)調(diào)用write 輸出的字符串參數(shù)進(jìn)行反轉(zhuǎn):
#include <sys/ptrace.h> #include <sys/wait.h> #include <sys/reg.h> #include <sys/syscall.h> #include <sys/user.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> #define long_size sizeof(long)void reverse(char * str) {int i,j;char temp;for(i=0,j=strlen(str)-2;i<=j;++i,--j){temp=str[i];str[i]=str[j];str[j]=temp;} }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;laddr=str;while(i<j){data.val=ptrace(PTRACE_PEEKDATA,child,addr+i*long_size,NULL);if(data.val == -1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(laddr,data.chars,long_size);++i;laddr +=long_size;};j=len % long_size;if(j!=0){data.val=ptrace(PTRACE_PEEKDATA,child,addr+i*long_size,NULL);memcpy(laddr,data.chars,j);}str[len]='\0'; }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*long_size,data.val);++i;laddr+=long_size;}j=len%long_size;if(j!=0){ //注意:由于寫(xiě)入時(shí)也是按字寫(xiě)入的,所以正確的做法是先將該字的高地址數(shù)據(jù)讀出保存在data的高地址上 ,然后將該字再寫(xiě)入memcpy(data.chars,laddr,j);ptrace(PTRACE_POKEDATA,child,addr +i*long_size,data.val);}}int main(){pid_t child;int status;struct user_regs_struct regs;child =fork();if(child ==0){ptrace(PTRACE_TRACEME,0,NULL,NULL);execl("/bin/ls","ls",NULL);}else{long orig_eax;char *str,*laddr;int toggle =0;while(1){wait(&status);if(WIFEXITED(status))break;orig_eax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);if(orig_eax == SYS_write){if(toggle == 0){toggle =1;ptrace(PTRACE_GETREGS,child,NULL,®s);str=(char * )calloc((regs.rdx+1),sizeof(char));getdata(child,regs.rsi,str,regs.rdx);reverse(str);putdata(child,regs.rsi,str,regs.rdx);}else{toggle =0;}}ptrace(PTRACE_SYSCALL,child,NULL,NULL);}}return 0; }輸出:
user1@user-virtual-machine:~/hookTest$ ./hook3.o o.3kooh ~c.3kooh c.3kooh o.2kooh ~c.2kooh c.2kooh ~c.1xe c.1xe?
?
4. 向其它程序注入指令
我們追蹤其它獨(dú)立運(yùn)行的進(jìn)程時(shí),需要使用下面的命令:
ptrace(PTRACE_ATTACH, pid, NULL, NULL)使pid進(jìn)程成為被追蹤的tracee進(jìn)程。tracee進(jìn)程會(huì)被發(fā)送一個(gè)SIGTOP信號(hào)量,tracee進(jìn)程不會(huì)立即停止,直到完成本次系統(tǒng)調(diào)用。如果要結(jié)束追蹤,則調(diào)用PTRACE_DETACH即可。
debug 設(shè)置斷點(diǎn)的功能可以通過(guò)ptrace實(shí)現(xiàn)。原理是ATTACH正在運(yùn)行的進(jìn)程使其停止。然后讀取該進(jìn)程的指令寄存器IR(32位x86為EIP,64w的是RIP)內(nèi)容所指向的指令,備份后替換成目標(biāo)指令,再使其繼續(xù)執(zhí)行,此時(shí)被追蹤進(jìn)程就會(huì)執(zhí)行我們替換的指令,運(yùn)行完注入的指令之后,我們?cè)倩謴?fù)原進(jìn)程的IR
,從而達(dá)到改變?cè)绦蜻\(yùn)行邏輯的目的。
tracee進(jìn)程代碼:
stdio.h>int main(){int i=0;while(1){printf("Hello,ptrace! [pid:%d]! num is %d\n",getpid(),i++);sleep(2);}return 0; }tracer進(jìn)程代碼
#include<sys/ptrace.h> #include<sys/reg.h> #include<sys/wait.h> #include<sys/user.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<stdio.h>#define long_size sizeof(long)void getdata(pid_t child, long addr ,char * str,int len){char * laddr =str;int i,j;union u{long val;char chars [long_size] ;} data;i=0;j=len/long_size;while(i<j){data.val=ptrace(PTRACE_PEEKDATA,child,addr + long_size*i,NULL);if(data.val==-1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(laddr,data.chars,long_size);++i; laddr=laddr+long_size;}j= len %long_size;if(j!=0){data.val=ptrace(PTRACE_PEEKDATA,child,addr + long_size*i,NULL);if(data.val==-1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(laddr,data.chars,j);}str[len]='\0'; }void putdata(pid_t child , long addr,char * str,int len){char * laddr =str;int i,j;j=len/long_size;i=0;union u{long val;char chars [long_size] ;} data;while(i<j){memcpy(data.chars,laddr,long_size);ptrace(PTRACE_POKEDATA,child,addr + long_size*i,data.val);++i;laddr=laddr+long_size;}j=len%long_size;if(j!=0){data.val= ptrace(PTRACE_PEEKDATA,child,addr + long_size*i,NULL);if(data.val==-1){if(errno){printf("READ error: %s\n",strerror(errno));}}memcpy(data.chars,laddr,j);ptrace(PTRACE_POKEDATA,child,addr + long_size*i,data.val); } }int main(int argc,char * argv[]){if(argc!=2){printf("Usage: %s pid\n",argv[0]);}pid_t tracee = atoi(argv[1]);struct user_regs_struct regs;/*int 80(系統(tǒng)調(diào)用) int 3(斷點(diǎn))*/unsigned char code[]={0xcd,0x80,0xcc,0x00,0,0,0,0}; //八個(gè)字節(jié),等于long 型的長(zhǎng)度char backup[8]; //備份讀取的指令ptrace(PTRACE_ATTACH,tracee,NULL,NULL);long inst; //用于保存指令寄存器所指向的下一條將要執(zhí)行的指令的內(nèi)存地址 wait(NULL);ptrace(PTRACE_GETREGS,tracee,NULL,®s);inst =ptrace(PTRACE_PEEKTEXT,tracee,regs.rip,NULL);printf("tracee:RIP:0x%llx INST: 0x%lx\n",regs.rip,inst);//讀取子進(jìn)程將要執(zhí)行的 7 bytes指令并備份getdata(tracee,regs.rip,backup,7);//設(shè)置斷點(diǎn)putdata(tracee,regs.rip,code,7);//讓子進(jìn)程繼續(xù)執(zhí)行并執(zhí)行“int 3”斷點(diǎn)指令停止ptrace(PTRACE_CONT,tracee,NULL,NULL);wait(NULL);long rip=ptrace(PTRACE_PEEKUSER,tracee,8*RIP,NULL);//獲取子進(jìn)程停止時(shí),rip的值long inst2=ptrace(PTRACE_PEEKTEXT,tracee,rip,NULL);printf("tracee:RIP:0x%lx INST: 0x%lx\n",rip,inst2);printf("Press Enter to continue tracee process\n");getchar();putdata(tracee,regs.rip,backup,7); //重新將備份的指令寫(xiě)回寄存器ptrace(PTRACE_SETREGS,tracee,NULL,®s);//設(shè)置會(huì)原來(lái)的寄存器值ptrace(PTRACE_CONT,tracee,NULL,NULL);ptrace(PTRACE_DETACH,tracee,NULL,NULL);return 0;}先運(yùn)行tracee.o 文件
$ ./tracee.o此時(shí)tracee.o輸出:
Hello,ptrace! [pid:14384]! num is 0 Hello,ptrace! [pid:14384]! num is 1 Hello,ptrace! [pid:14384]! num is 2 Hello,ptrace! [pid:14384]! num is 3 ......再另打開(kāi)一個(gè)shell運(yùn)行attach.o文件
$ ./.attach.o 14384 //pid此時(shí)tracee.o執(zhí)行到int 3斷點(diǎn)指令停止,attach1,o輸出:
tracee:RIP:0x7f48b0394f20 INST: 0x3173fffff0013d48 tracee:RIP:0x7f48b0394f23 INST: 0x8348c33100000000 Press Enter to continue tracee process按任意鍵tracee.o恢復(fù)執(zhí)行
參考:http://www.cnblogs.com/pannengzhi/p/5203467.html
?
?
?
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Linux 系统调用 Ptrace 详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 浅谈C语言指针
- 下一篇: 安卓逆向_9 --- log 插桩、To