參考文獻:
http://man7.org/linux/man-pages/man2/ptrace.2.html
https://www.linuxjournal.com/article/6100
https://www.linuxjournal.com/article/6210
http://blog.txipinet.com/2006/10/05/37-tecnicas-anti-debugging-sencillas-para-gnu-linux/
http://www.iosre.com/t/topic/9351
《匯編語言程序設計》
《程序員的自我修養》
目標:
1)對ptrace 有正確理解, 代碼實現
2)ptrace的用處
3)ptrace代碼注入
4)如何防止反調試
一. ptrace 介紹
Ptrace 提供了一種父進程可以控制子進程運行,並可以檢查和改變它的核心image。它主要用於實現斷點調試。一個被跟蹤的進程運行中,直到發生一個信號。則進程被中止,並且通知其父進程。在進程中止的狀態下,進程的內存空間可以被讀寫。父進程還可以使子進程繼續執行,並選擇是否是否忽略引起中止的信號
man手冊介紹:http://man7.org/linux/man-pages/man2/ptrace.2.html
二. ptrace 的函數詳解:
2.1 函數聲明
1 long ptrace(enum __ptrace_request request, 2 pid_t pid, 3 void *addr, 4 void *data);
.參數request:請求ptrace執行的操作
.參數pid:目標進程的ID
.參數addr:目標進程的地址值
.參數data:作用則根據request的不同而變化,如果需要向目標進程中寫入數據,data存放的是需要寫入的數據;如果從目標進程中讀數據,data將存放返回的數據
request參數決定了CODE的行為以及后續的參數是如何被使用的,參數request的常用的值如下:
在 i386 平台下(本文所有代碼都基於 i386), 系統調用的編號會被放在寄存器 %eax 中,而系統調用的參數會被依次放到 %ebx,%ecx,%edx,%exi 和 %edi中,比如說,對於下面的系統調用:
write(2, "Hello", 5)
匯編代碼:
1 movl $4, %eax 2 movl $2, %ebx 3 movl $hello,%ecx 4 movl $5, %edx 5 int $0x80
看完上面簡單的例子,現在我們來看看 ptrace 又是怎樣執行的:
三. 示例代碼
1 #include <sys/ptrace.h> 2 #include <sys/types.h> 3 #include <sys/wait.h> 4 #include <unistd.h> 5 #include <linux/user.h> /* For constants 6 ORIG_EAX etc */ 7 int main() 8 { pid_t child; 9 long orig_eax; 10 child = fork(); 11 if(child == 0) { 12 ptrace(PTRACE_TRACEME, 0, NULL, NULL); 13 execl("/bin/ls", "ls", NULL); 14 } 15 else { 16 wait(NULL); 17 orig_eax = ptrace(PTRACE_PEEKUSER, 18 child, 4 * ORIG_EAX, 19 NULL); 20 printf("The child made a " 21 "system call %ldn", orig_eax); 22 ptrace(PTRACE_CONT, child, NULL, NULL); 23 } 24 return 0; 25 }
gcc -o ptrace ptrace.c
報錯:
ptrace.c:6:24: error: linux/user.h: No such file or directory ptrace.c: In function ‘main’: ptrace.c:18: error: ‘ORIG_EAX’ undeclared (first use in this function) ptrace.c:18: error: (Each undeclared identifier is reported only once ptrace.c:18: error: for each function it appears in.) ptrace.c:20: warning: incompatible implicit declaration of built-in function ‘printf’
由於我的環境是64 位系統
這里有兩個地方有問題
- The ‘linux/user.h’ 不存在
- 64位寄存器 R*X, 所以EAX 改成 RAX
兩個修改方案:
1) linux/user.h 改成 sys/reg.h
long original_rax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
地址從“4*orig-eax”更改為“8*orig-rax”,因為它是要在用戶區域中讀取的地址,而user-regs-struct中的orig-rax成員是第15個成員(從0開始)。文件'sys/reg.h'中的orig_rax定義指定其位置:定義orig_rax 15
因為其他成員在64位機器上的大小是8,所以地址是:8*orig_rax
我們看一下頭文件結構體

1 struct user_regs_struct 2 { 3 unsigned long int r15; 4 unsigned long int r14; 5 unsigned long int r13; 6 unsigned long int r12; 7 unsigned long int rbp; 8 unsigned long int rbx; 9 unsigned long int r11; 10 unsigned long int r10; 11 unsigned long int r9; 12 unsigned long int r8; 13 unsigned long int rax; 14 unsigned long int rcx; 15 unsigned long int rdx; 16 unsigned long int rsi; 17 unsigned long int rdi; 18 unsigned long int orig_rax; 19 unsigned long int rip; 20 unsigned long int cs; 21 unsigned long int eflags; 22 unsigned long int rsp; 23 unsigned long int ss; 24 unsigned long int fs_base; 25 unsigned long int gs_base; 26 unsigned long int ds; 27 unsigned long int es; 28 unsigned long int fs; 29 unsigned long int gs; 30 }; 31 32 struct user 33 { 34 struct user_regs_struct regs; 35 int u_fpvalid; 36 struct user_fpregs_struct i387; 37 unsigned long int u_tsize; 38 unsigned long int u_dsize; 39 unsigned long int u_ssize; 40 unsigned long int start_code; 41 unsigned long int start_stack; 42 long int signal; 43 int reserved; 44 struct user_regs_struct* u_ar0; 45 struct user_fpregs_struct* u_fpstate; 46 unsigned long int magic; 47 char u_comm [32]; 48 unsigned long int u_debugreg [8]; 49 };
2)修改‘linux/user.h’ 為 ‘sys/user.h’
1 struct user_regs_struct regs; 2 ptrace(PTRACE_GETREGS, child, NULL, ®s); 3 printf("The child made a system call %ldn", regs.orig_rax);
第二個更簡單,因為它不需要計算位置,但它讀取的數據比第一個多
我認為,如果我們直接使用orig_x字段的地址,會更清楚、更容易理解:
1 struct user* user_space = (struct user*)0; 2 long original_rax = ptrace(PTRACE_PEEKUSER, child, &user_space->regs.orig_rax, NULL);
我們現在可以編譯和運行它了,但是我們得到了:“子系統調用59”,這與原來的“11”不同,有什么問題嗎?
在文件sys/syscall.h中,它包含文件'asm/unistd.h',注釋中指出該文件列出了系統調用:
1 /* This file should list the numbers of the system calls the system knows. 2 But instead of duplicating this we use the information available 3 from the kernel sources. */ 4 #include <asm/unistd.h>
是由於頭文件asm/unistd.h 包含了不同的文件,根據 __i386__ 和_ILP32__:
1 # ifdef __i386__ 2 # include <asm/unistd_32.h> 3 # elif defined(__ILP32__) 4 # include <asm/unistd_x32.h> 5 # else 6 # include <asm/unistd_64.h> 7 # endif
從頭文件里面asm/unistd_64.h 我們可以看到64位系統調用:
#define __NR_execve 59
第一個程序搞定, 讓我們繼續往下看吧, 蹩腳的英語確實讓人頭疼
第二個示例:讀取系統調用的參數
通過調用ptrace並傳入PTRACE_PEEKUSER作為第一個參數,我們可以檢查子進程中,保存了該進程的寄存器的內容(及其它一些內容)的用戶態內存區域(USER area)。內核把寄存器的內容保存到這塊區域,就是為了能夠讓父進程通過ptrace來讀取,下面舉一個例子來說明一下:
#include <sys/wait.h> #include <unistd.h> /* For fork() */ #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/reg.h> /* For constants ORIG_RAX etc */ #include <sys/user.h> #include <sys/syscall.h> /* SYS_write */ #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("[Enter SYS_write call] with regs.rdi [%ld], regs.rsi[%ld], regs.rdx[%ld], regs.rax[%ld], regs.orig_rax[%ld]\n", regs.rdi, regs.rsi, regs.rdx,regs.rax, regs.orig_rax); } else //離開此次系統調用 { printf("[Leave SYS_write call] return regs.rax [%ld], regs.orig_rax [%ld]\n", regs.rax, regs.orig_rax); iscalling = 0; } } ptrace(PTRACE_SYSCALL, child, NULL, NULL); } } return 0; }
輸出結果: [Enter SYS_write call] with regs.rdi [1], regs.rsi[140309977006080], regs.rdx[10], regs.rax[-38], regs.orig_rax[1] total 40K [Leave SYS_write call] return regs.rax [10], regs.orig_rax [1] [Enter SYS_write call] with regs.rdi [1], regs.rsi[140309977006080], regs.rdx[49], regs.rax[-38], regs.orig_rax[1] -rw-r–r--. 1 root root 7.5K Oct 7 16:56 main.o [Leave SYS_write call] return regs.rax [49], regs.orig_rax [1] [Enter SYS_write call] with regs.rdi [1], regs.rsi[140309977006080], regs.rdx[51], regs.rax[-38], regs.orig_rax[1] -rw-r–r--. 1 root root 17K Oct 6 16:58 Makefile [Leave SYS_write call] return regs.rax [51], regs.orig_rax [1] [Enter SYS_write call] with regs.rdi [1], regs.rsi[140309977006080], regs.rdx[49], regs.rax[-38], regs.orig_rax[1] -rwxr-xr-x. 1 root root 11K Oct 7 16:56 Ptarce [Leave SYS_write call] return regs.rax [49], regs.orig_rax [1]
至於上面的例子中出現的調用:wait(&status),這是個典型的用於判斷子進程是被 ptrace 停住還是已經運行結束了的用法,變量 status 用於標記子進程是否已經結束退出,關於這個 wait() 和 WIFEXITED 的更多細節,讀者可以自行查看一下manual(man 2).
還有更經典的示例, 可以參考:https://www.linuxjournal.com/article/6100?page=0,0
四. ptrace 反調試:
進程跟蹤器,類似於gdb watch的調試方法, Linux 系統gdb等調試器,都是通過ptrace系統調用實現
ptrace系統調用主要是父進程用來觀察和控制子進程的執行過程、檢查並替換子進程執行序列或者寄存器值的一種手段。主要用於實現斷點調試和跟蹤系統調用
但是如果你自己寫了一個軟件又不想被別人調試查看內部, 這時候就需要采取手段防止別人調試,反調試從邏輯上分大概分為, 一種是直接屏蔽調試器掛載, 另一種就是根據特征手動檢測調試器掛載. 當然也分為使用函數實現 和 直接使用內聯 asm 實現
這里也推薦一個個人感覺比較好的文章:http://bbs.iosre.com/t/topic/9351
這里介紹一下如何進行反調試:
ptrace有個參數 PT_DENY_ATTACH :它可以防止調試程序(gdb、dtrace等)在內核級別調試二進制文件
ptrace(PT_DENY_ATTACH, 0, 0, 0);
進行系統調用,防止符號斷點調試進行定位匯編代碼
通過asm鏈接匯編代碼,通過匯編代碼svc #0x80觸發中斷。通過syscall頭文件找到底層函數名對應的定義數值
asm( "mov x0,#31\n" "mov x1,#0\n" "mov x2,#0\n" "mov x3,#0\n" "mov w16,#26\n"//26就是prase,上面四個是傳入的參數 "svc #0x80"//觸發中斷 )
封裝ptrace
1 static __always_inline volatile long ptrace( 2 enum __ptrace_request request, 3 pid_t pid, 4 void *addr, 5 void *data) 6 { 7 __asm__ volatile( 8 "mov %0, %%rdi\n" 9 "mov %1, %%rsi\n" 10 "mov %2, %%rdx\n" 11 "mov %3, %%r10\n" 12 "mov $0x65, %%rax\n" 13 "syscall" 14 : 15 : "g"(request), "g"(pid), "g"(addr), "g"(data)); 16 asm("mov %%rax, %0" 17 : "=r"(ret)); 18 return (void *)ret; 19 }
是必須保證,可執行文件沒被修改的情況下有效的。如果可執行文件被靜態反匯編。找到ptrace系統調用的代碼將其替換成無效指令。那反調試策略將失效,所以,這樣的保護還是得基於,可執行代碼的加密
五. ptrace 代碼注入
ptrace是Unix系列系統的系統調用之一。其主要功能是實現對進程的追蹤。對目標進程,進行流程控制,用戶寄存器值讀取&寫入操作,內存進行讀取&修改。這樣的特性,就非常適合,用於編寫實現,遠程代碼注入。大部分的病毒會使用到這一點,實現,自用空間注入,rip位置直接注入,text段與data段之間的空隙注入
當使用要跟蹤的pid調用ptrace(PTRACE_ATTACH, ..)時,它大致相當於調用ptrace(PTRACE_TRACEME, ..)並成為跟蹤進程的子進程。跟蹤的進程被發送一個SIGSTOP,因此我們可以像往常一樣檢查和修改進程。修改或跟蹤完成后,可以通過調用ptrace(PTRACE_DETACH, ..)讓跟蹤的進程繼續執行
下面是一個小示例跟蹤程序的代碼:
1 int main() 2 { int i; 3 for(i = 0;i < 10; ++i) { 4 printf("My counter: %d\n", i); 5 sleep(2); 6 } 7 return 0; 8 }
保存文件 dummy2.c, 並且后台運行
1 gcc -o dummy2 dummy2.c 2 ./dummy2 &
現在我們attach到這個程序上去
1 #include <sys/ptrace.h> 2 #include <sys/types.h> 3 #include <sys/wait.h> 4 #include <unistd.h> 5 #include <sys/reg.h> /* For user_regs_struct 6 etc. */ 7 int main(int argc, char *argv[]) 8 { pid_t traced_process; 9 struct user_regs_struct regs; 10 long ins; 11 if(argc != 2) { 12 printf("Usage: %s <pid to be traced>\n", 13 argv[0], argv[1]); 14 exit(1); 15 } 16 traced_process = atoi(argv[1]); 17 ptrace(PTRACE_ATTACH, traced_process, 18 NULL, NULL); 19 wait(NULL); 20 ptrace(PTRACE_GETREGS, traced_process, 21 NULL, ®s); 22 ins = ptrace(PTRACE_PEEKTEXT, traced_process, 23 regs.eip, NULL); 24 printf("EIP: %lx Instruction executed: %lx\n", 25 regs.eip, ins); 26 ptrace(PTRACE_DETACH, traced_process, 27 NULL, NULL); 28 return 0; 29 }
上面的程序只是附到一個進程上,等待它停止,檢查它的eip(指令指針)並進行分離。
在跟蹤過程停止后,使用ptrace(PTRACE_POKETEXT, ..)和ptrace(PTRACE_POKEDATA, ..)注入代碼。
設置斷點:
調試器如何設置斷點?通常,它們用trap指令替換要執行的指令,以便當跟蹤的程序停止時,跟蹤程序(調試器)可以檢查它。一旦跟蹤程序繼續跟蹤過程,它將替換原來的指令。這里有一個例子:
1 #include <sys/ptrace.h> 2 #include <sys/types.h> 3 #include <sys/wait.h> 4 #include <unistd.h> 5 #include <sys/user.h> 6 #include <stdio.h> 7 8 const int long_size = sizeof(long); 9 void getdata(pid_t child, long addr, 10 char *str, int len) 11 { char *laddr; 12 int i, j; 13 union u { 14 long val; 15 char chars[long_size]; 16 }data; 17 i = 0; 18 j = len / long_size; 19 laddr = str; 20 while(i < j) { 21 data.val = ptrace(PTRACE_PEEKDATA, child, 22 addr + i * 4, NULL); 23 memcpy(laddr, data.chars, long_size); 24 ++i; 25 laddr += long_size; 26 } 27 j = len % long_size; 28 if(j != 0) { 29 data.val = ptrace(PTRACE_PEEKDATA, child, 30 addr + i * 4, NULL); 31 memcpy(laddr, data.chars, j); 32 } 33 str[len] = '\0'; 34 } 35 void putdata(pid_t child, long addr, 36 char *str, int len) 37 { char *laddr; 38 int i, j; 39 union u { 40 long val; 41 char chars[long_size]; 42 }data; 43 i = 0; 44 j = len / long_size; 45 laddr = str; 46 while(i < j) { 47 memcpy(data.chars, laddr, long_size); 48 ptrace(PTRACE_POKEDATA, child, 49 addr + i * 4, data.val); 50 ++i; 51 laddr += long_size; 52 } 53 j = len % long_size; 54 if(j != 0) { 55 memcpy(data.chars, laddr, j); 56 ptrace(PTRACE_POKEDATA, child, 57 addr + i * 4, data.val); 58 } 59 } 60 int main(int argc, char *argv[]) 61 { pid_t traced_process; 62 struct user_regs_struct regs, newregs; 63 long ins; 64 /* int 0x80, int3 */ 65 char code[] = {0xcd,0x80,0xcc,0}; 66 char backup[4]; 67 if(argc != 2) { 68 printf("Usage: %s <pid to be traced>\n", 69 argv[0], argv[1]); 70 exit(1); 71 } 72 traced_process = atoi(argv[1]); 73 ptrace(PTRACE_ATTACH, traced_process, 74 NULL, NULL); 75 wait(NULL); 76 ptrace(PTRACE_GETREGS, traced_process, 77 NULL, ®s); 78 /* Copy instructions into a backup variable */ 79 getdata(traced_process, regs.rip, backup, 3); 80 /* Put the breakpoint */ 81 putdata(traced_process, regs.rip, code, 3); 82 /* Let the process continue and execute 83 the int 3 instruction */ 84 ptrace(PTRACE_CONT, traced_process, NULL, NULL); 85 wait(NULL); 86 printf("The process stopped, putting back " 87 "the original instructions\n"); 88 printf("Press <enter> to continue\n"); 89 getchar(); 90 putdata(traced_process, regs.eip, backup, 3); 91 /* Setting the eip back to the original 92 instruction to let the process continue */ 93 ptrace(PTRACE_SETREGS, traced_process, 94 NULL, ®s); 95 ptrace(PTRACE_DETACH, traced_process, 96 NULL, NULL); 97 return 0; 98 }
這里簡單提一下為什么改rip
RIP --- x64體系
EIP --- x86體系
RIP/EIP注入原理:
1 掛起目標線程,需要用到 SuspendThread 函數
2 掛起之后獲取目標線程的上下文,需要用到 GetThreadContext函數
這個函數可以獲得一個CONTEXT結構體封裝的數據。這個結構體的定義在winnt.h頭文件中
結構體里面存儲了當前線程的上下文信息,比如當前線程的RIP/EIP在哪里,通用寄存器的值是
多少等等。
3 RIP/EIP注入關鍵就是修改Context中的RIP/EIP寄存器。使得要執行的代碼強制跳轉到我們
指定的代碼。最后將上下文設置回去,這用到 SetThreadContext 函數,最后執行ResumeThread
函數,將掛起線程恢復執行。
這里,我們用陷阱指令的代碼替換這三個字節,當進程停止時,我們替換原始指令並將eip重置為原始位置. 上圖闡明了執行上述程序時指令流的外觀(圖示針對32位系統, eip32位)
現在我們已經清楚了斷點是如何設置的,讓我們將一些代碼字節注入到正在運行的程序中。這些代碼字節將打印“hello world”
下面的程序是一個簡單的“hello world”程序,根據我們的需要進行了修改。用以下軟件編譯程序:
1 gcc -o hello hello.c 2 void main() 3 { 4 __asm__(" 5 jmp forward 6 backward: 7 popl %esi # Get the address of 8 # hello world string 9 movl $4, %eax # Do write system call 10 movl $2, %ebx 11 movl %esi, %ecx 12 movl $12, %edx 13 int $0x80 14 int3 # Breakpoint. Here the 15 # program will stop and 16 # give control back to 17 # the parent 18 forward: 19 call backward 20 .string \"Hello World\\n\"" 21 ); 22 }
這里需要前后跳轉才能找到“hello world”字符串的地址。
利用GDB反匯編
1 (gdb) disassemble main 2 Dump of assembler code for function main: 3 0x80483e0 <main>: push %ebp 4 0x80483e1 <main+1>: mov %esp,%ebp 5 0x80483e3 <main+3>: jmp 0x80483fa <forward> 6 End of assembler dump. 7 (gdb) disassemble forward 8 Dump of assembler code for function forward: 9 0x80483fa <forward>: call 0x80483e5 <backward> 10 0x80483ff <forward+5>: dec %eax 11 0x8048400 <forward+6>: gs 12 0x8048401 <forward+7>: insb (%dx),%es:(%edi) 13 0x8048402 <forward+8>: insb (%dx),%es:(%edi) 14 0x8048403 <forward+9>: outsl %ds:(%esi),(%dx) 15 0x8048404 <forward+10>: and %dl,0x6f(%edi) 16 0x8048407 <forward+13>: jb 0x8048475 17 0x8048409 <forward+15>: or %fs:(%eax),%al 18 0x804840c <forward+18>: mov %ebp,%esp 19 0x804840e <forward+20>: pop %ebp 20 0x804840f <forward+21>: ret 21 End of assembler dump. 22 (gdb) disassemble backward 23 Dump of assembler code for function backward: 24 0x80483e5 <backward>: pop %esi 25 0x80483e6 <backward+1>: mov $0x4,%eax 26 0x80483eb <backward+6>: mov $0x2,%ebx 27 0x80483f0 <backward+11>: mov %esi,%ecx 28 0x80483f2 <backward+13>: mov $0xc,%edx 29 0x80483f7 <backward+18>: int $0x80 30 0x80483f9 <backward+20>: int3 31 End of assembler dump.
我們需要將機器碼字節從main+3取到back +20,總共是41字節。機器代碼可以用GDB中的x命令查看:
1 (gdb) x/40bx main+3 2 <main+3>: eb 15 5e b8 04 00 00 00 3 <backward+6>: bb 02 00 00 00 89 f1 ba 4 <backward+14>: 0c 00 00 00 cd 80 cc 5 <forward+1>: e6 ff ff ff 48 65 6c 6c 6 <forward+9>: 6f 20 57 6f 72 6c 64 0a
現在我們有了要執行的指令字節。為什么等待?我們可以使用與前面示例相同的方法注入它們。下面是源代碼;這里只給出了主要功能:
1 int main(int argc, char *argv[]) 2 { pid_t traced_process; 3 struct user_regs_struct regs, newregs; 4 long ins; 5 int len = 41; 6 char insertcode[] = 7 "\xeb\x15\x5e\xb8\x04\x00" 8 "\x00\x00\xbb\x02\x00\x00\x00\x89\xf1\xba" 9 "\x0c\x00\x00\x00\xcd\x80\xcc\xe8\xe6\xff" 10 "\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f" 11 "\x72\x6c\x64\x0a\x00"; 12 char backup[len]; 13 if(argc != 2) { 14 printf("Usage: %s <pid to be traced>\n", 15 argv[0], argv[1]); 16 exit(1); 17 } 18 traced_process = atoi(argv[1]); 19 ptrace(PTRACE_ATTACH, traced_process, 20 NULL, NULL); 21 wait(NULL); 22 ptrace(PTRACE_GETREGS, traced_process, 23 NULL, ®s); 24 getdata(traced_process, regs.eip, backup, len); 25 putdata(traced_process, regs.eip, 26 insertcode, len); 27 ptrace(PTRACE_SETREGS, traced_process, 28 NULL, ®s); 29 ptrace(PTRACE_CONT, traced_process, 30 NULL, NULL); 31 wait(NULL); 32 printf("The process stopped, Putting back " 33 "the original instructions\n"); 34 putdata(traced_process, regs.eip, backup, len); 35 ptrace(PTRACE_SETREGS, traced_process, 36 NULL, ®s); 37 printf("Letting it continue with " 38 "original flow\n"); 39 ptrace(PTRACE_DETACH, traced_process, 40 NULL, NULL); 41 return 0; 42 }
將代碼注入空閑內存中
在前面的示例中,我們將代碼直接注入執行指令流。但是,調試器可能會與這種行為混淆,所以讓我們找到進程中的空閑內存並將代碼注入其中。
我們可以通過檢查跟蹤進程的/proc/pid/maps文件來找到空閑內存。下面的函數會找到這個的起始地址:
1 long freespaceaddr(pid_t pid) 2 { 3 FILE *fp; 4 char filename[30]; 5 char line[85]; 6 long addr; 7 char str[20]; 8 sprintf(filename, "/proc/%d/maps", pid); 9 fp = fopen(filename, "r"); 10 if(fp == NULL) 11 exit(1); 12 while(fgets(line, 85, fp) != NULL) { 13 sscanf(line, "%lx-%*lx %*s %*s %s", &addr, 14 str, str, str, str); 15 if(strcmp(str, "00:00") == 0) 16 break; 17 } 18 fclose(fp); 19 return addr; 20 }
關於代碼注入還有很多需要學習的