ptrace理解


參考文獻:

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 位系統

這里有兩個地方有問題

  1. The ‘linux/user.h’ 不存在
  2. 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, &regs);
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, &regs);			//獲取寄存器參數
                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, &regs);
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, &regs);
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, &regs);
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, &regs);
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, &regs);
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, &regs);
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 }

 

關於代碼注入還有很多需要學習的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM