Playing with ptrace, Part II
Playing with ptrace, Part II
Issue
From Issue #
December
Dec , By Pradeep Padala
inSysAdmin
In Part II of his series on ptrace, Pradeep tackles the more advanced topics of setting breakpoints and injecting code into running processes.
In Part I of this article [LJ, November ], we saw how ptrace can be used to trace system calls and change system call arguments. In this article, we investigate advanced techniques like setting breakpoints and injecting code into running programs. Debuggers use these methods to set up breakpoints and execute debugging handlers. As with Part I, all code in this article is i386 architecture-specific. Attaching to a Running Process
In Part I, we ran the process to be traced as a child after calling ptrace(PTRACE_TRACEME, ..). If you simply wanted to see how the process is making system calls and trace the program, this would be sufficient. If you want to trace or debug a process already running, then ptrace(PTRACE_ATTACH, ..) should be used. When a ptrace(PTRACE_ATTACH, ..) is called with the pid to be traced, it is roughly equivalent to the process calling ptrace(PTRACE_TRACEME, ..) and becoming a child of the tracing process. The traced process is sent a SIGSTOP, so we can examine and modify the process as usual. After we are done with modifications or tracing, we can let the traced process continue on its own by calling ptrace(PTRACE_DETACH, ..). The following is the code for a small example tracing program: int main()
{ int i;
for(i = ;i < ; ++i) {
printf("My counter: %d\n", i);
sleep();
}
return ;
}
Save the program as dummy2.c. Compile and run it: gcc -o dummy2 dummy2.c
./dummy2 &
Now, we can attach to dummy2 by using the code below:
#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 != ) {
printf("Usage: %s <pid to be traced>\n",
argv[], argv[]);
exit();
}
traced_process = atoi(argv[]);
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 ;
}
The above program simply attaches to a process, waits for it to stop, examines its eip (instruction pointer) and detaches.
To inject code use ptrace(PTRACE_POKETEXT, ..) and ptrace(PTRACE_POKEDATA, ..) after the traced process has stopped. Setting Breakpoints
How do debuggers set breakpoints? Generally, they replace the instruction to be executed with a trap instruction, so that when the traced program stops, the tracing program, the debugger, can examine it. It will replace the original instruction once the tracing program continues the traced process. Here's an example: #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 = ;
j = len / long_size;
laddr = str;
while(i < j) {
data.val = ptrace(PTRACE_PEEKDATA, child,
addr + i * , NULL);
memcpy(laddr, data.chars, long_size);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != ) {
data.val = ptrace(PTRACE_PEEKDATA, child,
addr + i * , 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 = ;
j = len / long_size;
laddr = str;
while(i < j) {
memcpy(data.chars, laddr, long_size);
ptrace(PTRACE_POKEDATA, child,
addr + i * , data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != ) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i * , 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,};
char backup[];
if(argc != ) {
printf("Usage: %s <pid to be traced>\n",
argv[], argv[]);
exit();
}
traced_process = atoi(argv[]);
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, );
/* Put the breakpoint */
putdata(traced_process, regs.eip, code, );
/* 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, );
/* 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 ;
}
Here we replace the three bytes with the code for a trap instruction, and when the process stops, we replace the original instructions and reset the eip to original location. Figures - clarify how the instruction stream looks when above program is executed. Figure . After the Process Is Stopped Figure . After the Trap Instruction Bytes Are Set Figure . Trap Is Hit and Control Is Given to the Tracing Program Figure . After the Original Instructions Are Replaced and eip Is Reset to the Original Location Now that we have a clear idea of how breakpoints are set, let's inject some code bytes into a running program. These code bytes will print “hello world”. The following program is a simple “hello world” program with modifications to fit our needs. Compile the following program with: gcc -o hello hello.c
void main()
{
__asm__("
jmp forward
backward:
popl %esi # Get the address of
# hello world string
movl $, %eax # Do write system call
movl $, %ebx
movl %esi, %ecx
movl $, %edx
int $0x80
int3 # Breakpoint. Here the
# program will stop and
# give control back to
# the parent
forward:
call backward
.string \"Hello World\\n\""
);
}
The jumping backward and forward here is required to find the address of the “hello world” string. We can get the machine code for the above assembly from GDB. Fire up GDB and disassemble the program: (gdb) disassemble main
Dump of assembler code for function main:
0x80483e0 <main>: push %ebp
0x80483e1 <main+>: mov %esp,%ebp
0x80483e3 <main+>: jmp 0x80483fa <forward>
End of assembler dump.
(gdb) disassemble forward
Dump of assembler code for function forward:
0x80483fa <forward>: call 0x80483e5 <backward>
0x80483ff <forward+>: dec %eax
0x8048400 <forward+>: gs
0x8048401 <forward+>: insb (%dx),%es:(%edi)
0x8048402 <forward+>: insb (%dx),%es:(%edi)
0x8048403 <forward+>: outsl %ds:(%esi),(%dx)
0x8048404 <forward+>: and %dl,0x6f(%edi)
0x8048407 <forward+>: jb 0x8048475
0x8048409 <forward+>: or %fs:(%eax),%al
0x804840c <forward+>: mov %ebp,%esp
0x804840e <forward+>: pop %ebp
0x804840f <forward+>: ret
End of assembler dump.
(gdb) disassemble backward
Dump of assembler code for function backward:
0x80483e5 <backward>: pop %esi
0x80483e6 <backward+>: mov $0x4,%eax
0x80483eb <backward+>: mov $0x2,%ebx
0x80483f0 <backward+>: mov %esi,%ecx
0x80483f2 <backward+>: mov $0xc,%edx
0x80483f7 <backward+>: int $0x80
0x80483f9 <backward+>: int3
End of assembler dump.
We need to take the machine code bytes from main+ to backward+, which is a total of bytes. The machine code can be seen with the x command in GDB: (gdb) x/40bx main+
<main+>: eb 5e b8
<backward+>: bb f1 ba
<backward+>: 0c cd cc
<forward+>: e6 ff ff ff 6c 6c
<forward+>: 6f 6f 6c 0a
Now we have the instruction bytes to be executed. Why wait? We can inject them using the same method as in the previous example. The following is the source code; only the main function is given here:
int main(int argc, char *argv[])
{ pid_t traced_process;
struct user_regs_struct regs, newregs;
long ins;
int len = ;
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 != ) {
printf("Usage: %s <pid to be traced>\n",
argv[], argv[]);
exit();
}
traced_process = atoi(argv[]);
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 ;
}
Playing with ptrace, Part II Issue
From Issue #
December
Dec , By Pradeep Padala
inSysAdmin
In Part II of his series on ptrace, Pradeep tackles the more advanced topics of setting breakpoints and injecting code into running processes.
Injecting the Code into Free Space
In the previous example we injected the code directly into the executing instruction stream. However, debuggers can get confused with this kind of behaviour, so let's find the free space in the process and inject the code there. We can find free space by examining the /proc/pid/maps file of the traced process. The following function will find the starting address of this map: long freespaceaddr(pid_t pid)
{
FILE *fp;
char filename[];
char line[];
long addr;
char str[];
sprintf(filename, "/proc/%d/maps", pid);
fp = fopen(filename, "r");
if(fp == NULL)
exit();
while(fgets(line, , fp) != NULL) {
sscanf(line, "%lx-%*lx %*s %*s %s", &addr,
str, str, str, str);
if(strcmp(str, "00:00") == )
break;
}
fclose(fp);
return addr;
}
Each line in /proc/pid/maps represents a mapped region of the process. An entry in /proc/pid/maps looks like this: map start-mapend protection offset device
inode process file
-0804d000 r-xp :
/opt/kde2/bin/kdeinit
The following program injects code into free space. It's similar to the previous injection program except the free space address is used for keeping our new code. Here is the source code for the main function:
int main(int argc, char *argv[])
{ pid_t traced_process;
struct user_regs_struct oldregs, regs;
long ins;
int len = ;
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];
long addr;
if(argc != ) {
printf("Usage: %s <pid to be traced>\n",
argv[], argv[]);
exit();
}
traced_process = atoi(argv[]);
ptrace(PTRACE_ATTACH, traced_process,
NULL, NULL);
wait(NULL);
ptrace(PTRACE_GETREGS, traced_process,
NULL, ®s);
addr = freespaceaddr(traced_process);
getdata(traced_process, addr, backup, len);
putdata(traced_process, addr, insertcode, len);
memcpy(&oldregs, ®s, sizeof(regs));
regs.eip = addr;
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, addr, backup, len);
ptrace(PTRACE_SETREGS, traced_process,
NULL, &oldregs);
printf("Letting it continue with "
"original flow\n");
ptrace(PTRACE_DETACH, traced_process,
NULL, NULL);
return ;
}
Behind the Scenes
So what happens within the kernel now? How is ptrace implemented? This section could be an article on its own; however, here's a brief description of what happens. When a process calls ptrace with PTRACE_TRACEME, the kernel sets up the process flags to reflect that it is being traced: Source: arch/i386/kernel/ptrace.c
if (request == PTRACE_TRACEME) {
/* are we already being traced? */
if (current->ptrace & PT_PTRACED)
goto out;
/* set the ptrace bit in the process flags. */
current->ptrace |= PT_PTRACED;
ret = ;
goto out;
}
When a system call entry is done, the kernel checks this flag and calls the trace system call if the process is being traced. The gory assembly details can be found in arch/i386/kernel/entry.S. Now, we are in the sys_trace() function as defined in arch/i386/kernel/ptrace.c. It stops the child and sends a signal to the parent notifying that the child is stopped. This wakes up the waiting parent, and it does the ptrace magic. Once the parent is done, and it calls ptrace(PTRACE_CONT, ..) or ptrace(PTRACE_SYSCALL, ..), it wakes up the child by calling the scheduler function wake_up_process(). Some other architectures can implement this by sending a SIGCHLD to child. Conclusion
ptrace may appear to be magic to some people, because it can examine and modify a running program. It is generally used by debuggers and system call tracing programs, such as ptrace. It opens up interesting possibilities for doing user-mode extensions as well. There have been a lot of attempts to extend the operating system on the user level. See Resources to read about UFO, a user-level extension to filesystems. ptrace also is used to employ security mechanisms. All example code from this article and from Part I is available as a tar archive on the Linux Journal FTP site [ftp.linuxjournal.com/pub/lj/listings/issue104/.tgz].
Playing with ptrace, Part II的更多相关文章
- Playing with ptrace, Part I
X86_64 的 Redhat / Centos / Scientific 下面,若要编译.运行32位程序,需要安装以下包: yum install libgcc.i686 yum install g ...
- linux ptrace II
第一篇 linux ptrace I 在之前的文章中我们用ptrace函数实现了查看系统调用参数的功能.在这篇文章中,我们会用ptrace函数实现设置断点,跟代码注入功能. 参考资料 Playing ...
- linux ptrace I
这几天通过<游戏安全--手游安全技术入门这本书>了解到linux系统中ptrace()这个函数可以实现外挂功能,于是在ubuntu 16.04 x86_64系统上对这个函数进行了学习. 参 ...
- linux ptrace I【转】
转自:https://www.cnblogs.com/mmmmar/p/6040325.html 这几天通过<游戏安全——手游安全技术入门这本书>了解到linux系统中ptrace()这个 ...
- ltrace命令详解
原文链接:https://ipcmen.com/ltrace 用来跟踪进程调用库函数的情况 补充说明 NAME ltrace - A library call tracer ltrace命 ...
- Linux Hook 笔记
相信很多人对"Hook"都不会陌生,其中文翻译为"钩子".在编程中, 钩子表示一个可以允许编程者插入自定义程序的地方,通常是打包好的程序中提供的接口. 比如,我 ...
- linux 进程学习笔记-进程跟踪
进程跟踪 long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); Linux用ptrace来进行进 ...
- 安卓动态调试七种武器之离别钩 – Hooking(上)
安卓动态调试七种武器之离别钩 – Hooking(上) 作者:蒸米@阿里聚安全 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的 ...
- 每天学点GDB 13
ptrace是gdb实现的基石,本文简要介绍一下ptrace. ptrace linux提供的系统调用ptrace,使得一个进程可以attach到另一个进程并进而完整的控制被attach上的进程. 被 ...
随机推荐
- 如何在 Windows Azure 的虚拟机 ubuntu 上面安装和配置 openVPN(三)
第三步:安装openVPN 请打开 ubuntu 官方文档.然后,从上到下,按照步骤,输入执行命令.大家只需要执行到 First trouble shooting 之前即可. 不要怕麻烦,仔细读,一步 ...
- stl+模拟 CCF2016 4 路径解析
// stl+模拟 CCF2016 4 路径解析 // 一开始题意理解错了.... #include <iostream> #include <string> #include ...
- ACM2050
问题描述: 平面上有n条折线,问这些折线最多能将平面分割成多少块? 样例输入 1 2 样例输出 2 7 答案是: 2n ( 2n + 1 ) / 2 + 1 - 2n = 2 n^2 – n + ...
- CentOS 7 最小化安装之后安装Mysql
启动网卡 验证网络管理器和网卡接口状态 # systemctl status NetworkManager.service # nmcli dev status 修改网卡配置文件 通过上一步可以看到有 ...
- algorithm@ Shortest Path in Directed Acyclic Graph (O(|V|+|E|) time)
Given a Weighted Directed Acyclic Graph and a source vertex in the graph, find the shortest paths fr ...
- 15个顶级Java多线程面试题及答案
1)现在有T1.T2.T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉.这个多线程问题比 ...
- MySQL分支Percona, cmake编译安装
Percona是在MySQL基础上改进的,在功能和性能上有着很显著的提升.该版本提升了在高负载情况下的InnoDB的性能,为DBA提供一些非常有用的性能诊断工具:另外有更多的参数和命令来控制服务器行为 ...
- POJ 1287 Networking (最小生成树)
Networking 题目链接: http://acm.hust.edu.cn/vjudge/contest/124434#problem/B Description You are assigned ...
- Java IO (1) - InputStream
Java IO (1) - InputStream 前言 JavaIO一共包括两种,一种是stream,一种是reader/writer,每种又包括in/out,所以一共是四种包.Java 流在处理上 ...
- Lucene:信息检索与全文检索
目录 信息检索的概念 信息检索技术的分类 全文检索与数据库查询对比 全文检索工具一般由三部分构成 全文检索中建立索引和进行检索的流程 索引里面究竟存什么 如何创建索引 如何对索引进行检索 Lucene ...