在這篇文章的第一部分,我們了解了ptrace是怎麼用來追蹤系統調用并且修改系統調用參數的,在這篇文章中,我們研究更進階的技術如加入斷點以及在正在運作的程式中插入代碼。debuggers利用這些方法來設定斷點以及運作調試handlers.與第一部分一樣,我們這裡讨論的都是針對i386體系結構的。
Attaching to a Running Process
在第一部分,我們在調用ptrace(PTRACE_TRECEME,..)之後,把這個程序作為子程序執行。如果你隻想要看到程序是如何做系統調用并且跟蹤程式的,第一部分的認識已經足夠了,如果一想要跟蹤或者條是一個已經在執行的程式,那麼ptrace(PTRACE_ATTACH,。。)應該是你要考慮的。
當我們做ptrace(PTRACE_ATTACH,。。)并且傳遞要跟蹤的線程的pid,那就相當于這個程序自己調用ptrace(PTRACE_TRACEME,..)并且成為這個tracing 程序的子程序。這個被跟蹤的程序被發送了一個SIGSTOP信号,這樣我們就可以像往常一樣檢查并且修改程序了。在我們完成修改或者跟蹤之後,我們可以使得被跟蹤的程序繼續執行通過調用ptrace(PTRACE_DETACH,..)
下面tracing程式的代碼
int main()
{ int i;
for(i = 0;i < 10; ++i) {
printf("My counter: %d\n", i);
sleep(2);
}
return 0;
}
把這個檔案儲存為dummy2.c 編譯并且運作它:
gcc -o dummy2 dummy2.c
./dummy2 &
這時,我們可以通過下面的程式attach到dummy2,
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h> /* For user_regs_struct
etc. */
int main(int argc, char *argv[])
{ pid_t traced_process;
struct user_regs_struct regs;
long ins;
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n",
argv[0], argv[1]);
exit(1);
}
traced_process = atoi(argv[1]);
ptrace(PTRACE_ATTACH, traced_process,
NULL, NULL);
wait(NULL);
ptrace(PTRACE_GETREGS, traced_process,
NULL, ®s);
ins = ptrace(PTRACE_PEEKTEXT, traced_process,
regs.eip, NULL);
printf("EIP: %lx Instruction executed: %lx\n",
regs.eip, ins);
ptrace(PTRACE_DETACH, traced_process,
NULL, NULL);
return 0;
}
上面的程式簡單的attach到一個程序上,等待它完成,并且檢查它的eip(指令指針),然後detach 如果要插入代碼,可以再被跟蹤程序停下來之後使用ptrace(PTRACE_POKETEXT,..)與ptrace(PTRACE_POKEDATA,..)
設定斷點
調試器是怎麼設定斷點的?一般來說,他們把要執行的指令換成一個trap指令,這樣被跟蹤的程式就會停止,而跟蹤程式,也就是debugger, 可以控制被跟蹤的程式,一旦調試器繼續被跟蹤程序的執行,它原有的指令就會被替換。
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
const int long_size = sizeof(long);
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 * 4, NULL);
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 * 4, 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 * 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);
}
}
int main(int argc, char *argv[])
{ pid_t traced_process;
struct user_regs_struct regs, newregs;
long ins;
/* int 0x80, int3 */
char code[] = {0xcd,0x80,0xcc,0};
char backup[4];
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n",
argv[0], argv[1]);
exit(1);
}
traced_process = atoi(argv[1]);
ptrace(PTRACE_ATTACH, traced_process,
NULL, NULL);
wait(NULL);
ptrace(PTRACE_GETREGS, traced_process,
NULL, ®s);
/* Copy instructions into a backup variable */
getdata(traced_process, regs.eip, backup, 3);
/* Put the breakpoint */
putdata(traced_process, regs.eip, code, 3);
/* Let the process continue and execute
the int 3 instruction */
ptrace(PTRACE_CONT, traced_process, NULL, NULL);
wait(NULL);
printf("The process stopped, putting back "
"the original instructions\n");
printf("Press <enter> to continue\n");
getchar();
putdata(traced_process, regs.eip, backup, 3);
/* Setting the eip back to the original
instruction to let the process continue */
ptrace(PTRACE_SETREGS, traced_process,
NULL, ®s);
ptrace(PTRACE_DETACH, traced_process,
NULL, NULL);
return 0;
}
這裡我們把要跟蹤程式的代碼的三個位元組替換成了要完成trap操作的代碼,而當程式停止時,我們把剛才替換下來的三個位元組重新設定回去,并且重新設定指令指針eip到三個指令開始的位置。

第一步,被跟蹤程序停止之後,第二步,把被跟蹤程序代碼的三個位元組替換成完成trap指令的代碼
第三步,trap完成,控制交給跟蹤程式,第四步,把原來替換下來的三個位元組的代碼傳回回去,重新設定eip數值到剛開始的位置。
現在我們已經斷點是怎麼實作的了。 下面讓我們在正在運作的程式中插入一些代碼位元組,這些代碼會輸出hello world。 下面的程式是一個簡單的hello world程式,通過修改符合我們的需要, 編譯指令為 gcc -o hello hello.c
void main()
{
__asm__("
jmp forward
backward:
popl %esi # Get the address of
# hello world string
movl $4, %eax # Do write system call
movl $2, %ebx
movl %esi, %ecx
movl $12, %edx
int $0x80
int3 # Breakpoint. Here the
# program will stop and
# give control back to
# the parent
forward:
call backward
.string \"Hello World\\n\""
);
}
這裡的向後跳和向前跳是用來找到helloworld字元串的位址 我們可以通過gdb得到上面彙編代碼對應的機器碼,打開gdb然後反彙編這個程式:
(gdb) disassemble main
Dump of assembler code for function main:
0x80483e0 <main>: push %ebp
0x80483e1 <main+1>: mov %esp,%ebp
0x80483e3 <main+3>: jmp 0x80483fa <forward>
End of assembler dump.
(gdb) disassemble forward
Dump of assembler code for function forward:
0x80483fa <forward>: call 0x80483e5 <backward>
0x80483ff <forward+5>: dec %eax
0x8048400 <forward+6>: gs
0x8048401 <forward+7>: insb (%dx),%es:(%edi)
0x8048402 <forward+8>: insb (%dx),%es:(%edi)
0x8048403 <forward+9>: outsl %ds:(%esi),(%dx)
0x8048404 <forward+10>: and %dl,0x6f(%edi)
0x8048407 <forward+13>: jb 0x8048475
0x8048409 <forward+15>: or %fs:(%eax),%al
0x804840c <forward+18>: mov %ebp,%esp
0x804840e <forward+20>: pop %ebp
0x804840f <forward+21>: ret
End of assembler dump.
(gdb) disassemble backward
Dump of assembler code for function backward:
0x80483e5 <backward>: pop %esi
0x80483e6 <backward+1>: mov $0x4,%eax
0x80483eb <backward+6>: mov $0x2,%ebx
0x80483f0 <backward+11>: mov %esi,%ecx
0x80483f2 <backward+13>: mov $0xc,%edx
0x80483f7 <backward+18>: int $0x80
0x80483f9 <backward+20>: int3
End of assembler dump.
我們需要去main+3到backward+20的機器碼,總共41位元組。機器碼可以通過gdb的x指令看到
(gdb) x/40bx main+3
<main+3>: eb 15 5e b8 04 00 00 00
<backward+6>: bb 02 00 00 00 89 f1 ba
<backward+14>: 0c 00 00 00 cd 80 cc
<forward+1>: e6 ff ff ff 48 65 6c 6c
<forward+9>: 6f 20 57 6f 72 6c 64 0a
現在我們有了要執行的指令流,我們現在就用他們來插入到之前的例子中,下面是源代碼,這裡隻給出main 函數。
int main(int argc, char *argv[])
{ pid_t traced_process;
struct user_regs_struct regs, newregs;
long ins;
int len = 41;
char insertcode[] =
"\xeb\x15\x5e\xb8\x04\x00"
"\x00\x00\xbb\x02\x00\x00\x00\x89\xf1\xba"
"\x0c\x00\x00\x00\xcd\x80\xcc\xe8\xe6\xff"
"\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f"
"\x72\x6c\x64\x0a\x00";
char backup[len];
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n",
argv[0], argv[1]);
exit(1);
}
traced_process = atoi(argv[1]);
ptrace(PTRACE_ATTACH, traced_process,
NULL, NULL);
wait(NULL);
ptrace(PTRACE_GETREGS, traced_process,
NULL, ®s);
getdata(traced_process, regs.eip, backup, len);
putdata(traced_process, regs.eip,
insertcode, len);
ptrace(PTRACE_SETREGS, traced_process,
NULL, ®s);
ptrace(PTRACE_CONT, traced_process,
NULL, NULL);
wait(NULL);
printf("The process stopped, Putting back "
"the original instructions\n");
putdata(traced_process, regs.eip, backup, len);
ptrace(PTRACE_SETREGS, traced_process,
NULL, ®s);
printf("Letting it continue with "
"original flow\n");
ptrace(PTRACE_DETACH, traced_process,
NULL, NULL);
return 0;
}