參考文獻:
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 };
View Code
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 }
關于代碼注入還有很多需要學習的