在這篇文章的第一部分,我們瞭解了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;
}