Playing with ptrace, Part I
X86_64 的 Redhat / Centos / Scientific 下面,若要编译、运行32位程序,需要安装以下包:
yum install libgcc.i686
yum install glibc-static.i686
yum install glibc-devel.i686
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <stdio.h>
int main()
{ pid_t child;
long orig_eax;
child = fork();
if(child == ) {
ptrace(PTRACE_TRACEME, , NULL, NULL);
execl("/bin/ls", "ls", NULL);
}
else {
wait(NULL);
orig_eax = ptrace(PTRACE_PEEKUSER,
child, * ORIG_EAX,
NULL);
printf("The child made a "
"system call %ld\n", orig_eax);
ptrace(PTRACE_CONT, child, NULL, NULL);
}
return ;
}
"2.c" 27L, 619C written
[root@monitor ~]# gcc .c -m32 -o
[root@monitor ~]# ./
The child made a system call
Playing with ptrace, Part I
Issue
From Issue #
November
Nov , By Pradeep Padala
inSysAdmin
Using ptrace allows you to set up system call interception and modification at the user level.
Have you ever wondered how system calls can be intercepted? Have you ever tried fooling the kernel by changing system call arguments? Have you ever wondered how debuggers stop a running process and let you take control of the process? If you are thinking of using complex kernel programming to accomplish tasks, think again. Linux provides an elegant mechanism to achieve all of these things: the ptrace (Process Trace) system call. ptrace provides a mechanism by which a parent process may observe and control the execution of another process. It can examine and change its core image and registers and is used primarily to implement breakpoint debugging and system call tracing. In this article, we learn how to intercept a system call and change its arguments. In Part II of the article we will study advanced techniques—setting breakpoints and injecting code into a running program. We will peek into the child process' registers and data segment and modify the contents. We will also describe a way to inject code so the process can be stopped and execute arbitrary instructions. Basics
Operating systems offer services through a standard mechanism called system calls. They provide a standard API for accessing the underlying hardware and low-level services, such as the filesystems. When a process wants to invoke a system call, it puts the arguments to system calls in registers and calls soft interrupt 0x80. This soft interrupt is like a gate to the kernel mode, and the kernel will execute the system call after examining the arguments. On the i386 architecture (all the code in this article is i386-specific), the system call number is put in the register %eax. The arguments to this system call are put into registers %ebx, %ecx, %edx, %esi and %edi, in that order. For example, the call: write(, "Hello", )
roughly would translate into movl $, %eax
movl $, %ebx
movl $hello,%ecx
movl $, %edx
int $0x80
where $hello points to a literal string “Hello”.
So where does ptrace come into picture? Before executing the system call, the kernel checks whether the process is being traced. If it is, the kernel stops the process and gives control to the tracking process so it can examine and modify the traced process' registers. Let's clarify this explanation with an example of how the process works: #include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/user.h> /* For constants x86_64=> <sys/reg.h>
ORIG_EAX etc */ int main()
{ pid_t child;
long orig_eax;
child = fork();
if(child == ) {
ptrace(PTRACE_TRACEME, , NULL, NULL);
execl("/bin/ls", "ls", NULL);
}
else {
wait(NULL);
orig_eax = ptrace(PTRACE_PEEKUSER,
child, * ORIG_EAX,
NULL);
printf("The child made a "
"system call %ld\n", orig_eax);
ptrace(PTRACE_CONT, child, NULL, NULL);
}
return ;
}
When run, this program prints: The child made a system call
along with the output of ls. System call number is execve, and it's the first system call executed by the child. For reference, system call numbers can be found in /usr/include/asm/unistd.h.
As you can see in the example, a process forks a child and the child executes the process we want to trace. Before running exec, the child calls ptrace with the first argument, equal to PTRACE_TRACEME. This tells the kernel that the process is being traced, and when the child executes the execve system call, it hands over control to its parent. The parent waits for notification from the kernel with a wait() call. Then the parent can check the arguments of the system call or do other things, such as looking into the registers. When the system call occurs, the kernel saves the original contents of the eax register, which contains the system call number. We can read this value from child's USER segment by calling ptrace with the first argument PTRACE_PEEKUSER, shown as above. After we are done examining the system call, the child can continue with a call to ptrace with the first argument PTRACE_CONT, which lets the system call continue. ptrace Parameters
ptrace is called with four arguments: long ptrace(enum __ptrace_request request,
pid_t pid,
void *addr,
void *data);
The first argument determines the behaviour of ptrace and how other arguments are used. The value of request should be one of PTRACE_TRACEME, PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSER, PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER, PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_SETREGS, PTRACE_SETFPREGS, PTRACE_CONT, PTRACE_SYSCALL, PTRACE_SINGLESTEP, PTRACE_DETACH. The significance of each o
Playing with ptrace, Part I Issue
From Issue #
November
Nov , By Pradeep Padala
inSysAdmin
Using ptrace allows you to set up system call interception and modification at the user level.
Reading System Call Parameters
By calling ptrace with PTRACE_PEEKUSER as the first argument, we can examine the contents of the USER area where register contents and other information is stored. The kernel stores the contents of registers in this area for the parent process to examine through ptrace. Let's show this with an example: #include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <sys/syscall.h> /* For SYS_write etc */
int main()
{ pid_t child;
long orig_eax, eax;
long params[];
int status;
int insyscall = ;
child = fork();
if(child == ) {
ptrace(PTRACE_TRACEME, , NULL, NULL);
execl("/bin/ls", "ls", NULL);
}
else {
while() {
wait(&status);
if(WIFEXITED(status))
break;
orig_eax = ptrace(PTRACE_PEEKUSER,
child, * ORIG_EAX, NULL);
if(orig_eax == SYS_write) {
if(insyscall == ) {
/* Syscall entry */
insyscall = ;
params[] = ptrace(PTRACE_PEEKUSER,
child, * EBX,
NULL);
params[] = ptrace(PTRACE_PEEKUSER,
child, * ECX,
NULL);
params[] = ptrace(PTRACE_PEEKUSER,
child, * EDX,
NULL);
printf("Write called with "
"%ld, %ld, %ld\n",
params[], params[],
params[]);
}
else { /* Syscall exit */
eax = ptrace(PTRACE_PEEKUSER,
child, * EAX, NULL);
printf("Write returned "
"with %ld\n", eax);
insyscall = ;
}
}
ptrace(PTRACE_SYSCALL,
child, NULL, NULL);
}
}
return ;
}
This program should print an output similar to the following: ppadala@linux:~/ptrace > ls
a.out dummy.s ptrace.txt
libgpm.html registers.c syscallparams.c
dummy ptrace.html simple.c
ppadala@linux:~/ptrace > ./a.out
Write called with , ,
a.out dummy.s ptrace.txt
Write returned with
Write called with , ,
libgpm.html registers.c syscallparams.c
Write returned with
Write called with , ,
dummy ptrace.html simple.c
Write returned with
Here we are tracing the write system calls, and ls makes three write system calls. The call to ptrace, with a first argument of PTRACE_SYSCALL, makes the kernel stop the child process whenever a system call entry or exit is made. It's equivalent to doing a PTRACE_CONT and stopping at the next system call entry/exit.
In the previous example, we used PTRACE_PEEKUSER to look into the arguments of the write system call. When a system call returns, the return value is placed in %eax, and it can be read as shown in that example. The status variable in the wait call is used to check whether the child has exited. This is the typical way to check whether the child has been stopped by ptrace or was able to exit. For more details on macros like WIFEXITED, see the wait() man page. Reading Register Values
If you want to read register values at the time of a syscall entry or exit, the procedure shown above can be cumbersome. Calling ptrace with a first argument of PTRACE_GETREGS will place all the registers in a single call. The code to fetch register values looks like this: #include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <sys/syscall.h>
int main()
{ pid_t child;
long orig_eax, eax;
long params[];
int status;
int insyscall = ;
struct user_regs_struct regs;
child = fork();
if(child == ) {
ptrace(PTRACE_TRACEME, , NULL, NULL);
execl("/bin/ls", "ls", NULL);
}
else {
while() {
wait(&status);
if(WIFEXITED(status))
break;
orig_eax = ptrace(PTRACE_PEEKUSER,
child, * ORIG_EAX,
NULL);
if(orig_eax == SYS_write) {
if(insyscall == ) {
/* Syscall entry */
insyscall = ;
ptrace(PTRACE_GETREGS, child,
NULL, ®s);
printf("Write called with "
"%ld, %ld, %ld\n",
regs.ebx, regs.ecx,
regs.edx);
}
else { /* Syscall exit */
eax = ptrace(PTRACE_PEEKUSER,
child, * EAX,
NULL);
printf("Write returned "
"with %ld\n", eax);
insyscall = ;
}
}
ptrace(PTRACE_SYSCALL, child,
NULL, NULL);
}
}
return ;
}
This code is similar to the previous example except for the call to ptrace with PTRACE_GETREGS. Here we have made use of the user_regs_struct defined in <linux/user.h> to read the register values.
Playing with ptrace, Part I Issue
From Issue #
November
Nov , By Pradeep Padala
inSysAdmin
Using ptrace allows you to set up system call interception and modification at the user level.
Doing Funny Things
Now it's time for some fun. In the following example, we will reverse the string passed to the write system call: #include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <sys/syscall.h>
const int long_size = sizeof(long);
void reverse(char *str)
{ int i, j;
char temp;
for(i = , j = strlen(str) - ;
i <= j; ++i, --j) {
temp = str[i];
str[i] = str[j];
str[j] = temp;
}
}
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()
{
pid_t child;
child = fork();
if(child == ) {
ptrace(PTRACE_TRACEME, , NULL, NULL);
execl("/bin/ls", "ls", NULL);
}
else {
long orig_eax;
long params[];
int status;
char *str, *laddr;
int toggle = ;
while() {
wait(&status);
if(WIFEXITED(status))
break;
orig_eax = ptrace(PTRACE_PEEKUSER,
child, * ORIG_EAX,
NULL);
if(orig_eax == SYS_write) {
if(toggle == ) {
toggle = ;
params[] = ptrace(PTRACE_PEEKUSER,
child, * EBX,
NULL);
params[] = ptrace(PTRACE_PEEKUSER,
child, * ECX,
NULL);
params[] = ptrace(PTRACE_PEEKUSER,
child, * EDX,
NULL);
str = (char *)calloc((params[]+)
* sizeof(char));
getdata(child, params[], str,
params[]);
reverse(str);
putdata(child, params[], str,
params[]);
}
else {
toggle = ;
}
}
ptrace(PTRACE_SYSCALL, child, NULL, NULL);
}
}
return ;
}
The output looks like this: ppadala@linux:~/ptrace > ls
a.out dummy.s ptrace.txt
libgpm.html registers.c syscallparams.c
dummy ptrace.html simple.c
ppadala@linux:~/ptrace > ./a.out
txt.ecartp s.ymmud tuo.a
c.sretsiger lmth.mpgbil c.llacys_egnahc
c.elpmis lmth.ecartp ymmud
This example makes use of all the concepts previously discussed, plus a few more. In it, we use calls to ptrace with PTRACE_POKEDATA to change the data values. It works exactly the same way as PTRACE_PEEKDATA, except it both reads and writes the data thatt the child passes in arguments to the system call whereas PEEKDATA only reads the data.
Single-Stepping
ptrace provides features to single-step through the child's code. The call to ptrace(PTRACE_SINGLESTEP,..) tells the kernel to stop the child at each instruction and let the parent take control. The following example shows a way of reading the instruction being executed when a system call is executed. I have created a small dummy executable for you to understand what is happening instead of bothering with the calls made by libc. Here's the listing for dummy1.s. It's written in assembly language and compiled as gcc -o dummy1 dummy1.s: .data
hello:
.string "hello world\n"
.globl main
main:
movl $, %eax
movl $, %ebx
movl $hello, %ecx
movl $, %edx
int $0x80
movl $, %eax
xorl %ebx, %ebx
int $0x80
ret
The example program that single-steps through the above code is: #include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <sys/syscall.h>
int main()
{ pid_t child;
const int long_size = sizeof(long);
child = fork();
if(child == ) {
ptrace(PTRACE_TRACEME, , NULL, NULL);
execl("./dummy1", "dummy1", NULL);
}
else {
int status;
union u {
long val;
char chars[long_size];
}data;
struct user_regs_struct regs;
int start = ;
long ins;
while() {
wait(&status);
if(WIFEXITED(status))
break;
ptrace(PTRACE_GETREGS,
child, NULL, ®s);
if(start == ) {
ins = ptrace(PTRACE_PEEKTEXT,
child, regs.eip,
NULL);
printf("EIP: %lx Instruction "
"executed: %lx\n",
regs.eip, ins);
}
if(regs.orig_eax == SYS_write) {
start = ;
ptrace(PTRACE_SINGLESTEP, child,
NULL, NULL);
}
else
ptrace(PTRACE_SYSCALL, child,
NULL, NULL);
}
}
return ;
}
This program prints:
hello world
EIP: Instruction executed: 80cddb31
EIP: 804947c Instruction executed: c3
You might have to look at Intel's manuals to make sense out of those instruction bytes. Using single stepping for more complex processes, such as setting breakpoints, requires careful design and more complex code.
In Part II, we will see how breakpoints can be inserted and code can be injected into a running program. All of the example code from this article and from Part II (which will be printed in next month's issue) is available as a tar archive on the Linux Journal FTP site [ftp.linuxjournal.com/pub/lj/listings/issue103/6011.tgz]. email: ppadala@cise.ufl.edu
Pradeep Padala is currently working on his Master's degree at the University of Florida. His research interests include Grid and distributed systems. He can be reached via e-mail at p_padala@yahoo.com or through his web site (www.cise.ufl.edu/~ppadala).
Playing with ptrace, Part I的更多相关文章
- Playing with ptrace, Part II
Playing with ptrace, Part II Issue From Issue # December Dec , By Pradeep Padala inSysAdmin In Part ...
- 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()这个 ...
- 安卓动态调试七种武器之离别钩 – Hooking(上)
安卓动态调试七种武器之离别钩 – Hooking(上) 作者:蒸米@阿里聚安全 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的 ...
- 每天学点GDB 13
ptrace是gdb实现的基石,本文简要介绍一下ptrace. ptrace linux提供的系统调用ptrace,使得一个进程可以attach到另一个进程并进而完整的控制被attach上的进程. 被 ...
- 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来进行进 ...
随机推荐
- 如何从ST网站找到对应的固件库
ST官方网站改版后,基本上很难搜索到固件库的地址,找了半天才找到固件库的下载地址,通过此方法可以找到其他需要的资源,故记下来方便大家. 下载的网站地址为: Home>Tools and Soft ...
- php 解决微信昵称emoji表情插入MySQL报错
在PHP接受到微信用户昵称入库的时候报错 原因:utf-8 最大3个字节,而emoji占4个字节 解决办法: 1.修改mysql 数据库的字符集,改为utf8mb4,但是前提是MySQL的版本需要5. ...
- 微软控制台带来的PHP控制台输出问题
/** * 测试文件包含方式对跨平台的影响 * 控制台下测试. * 默认的文件编码为 UTF-8 */ function testChinese() { $file = __DIR__ . '/con ...
- Probabilistic SVM 与 Kernel Logistic Regression(KLR)
本篇讲的是SVM与logistic regression的关系. (一) SVM算法概论 首先我们从头梳理一下SVM(一般情况下,SVM指的是soft-margin SVM)这个算法. 这个算法要实现 ...
- mybatis系列-11-一对多查询
11.1 需求 查询订单及订单明细的信息. 11.2 sql语句 确定主查询表:订单表 确定关联查询表:订单明细表 在一对一查询基础上添加订单明细表关联即可. SELECT order ...
- Spring Framework 中启动 Redis 事务操作
背景: 项目中遇到有一系列对Redis的操作,并需要保持事务处理. 环境: Spring version 4.1.8.RELEASE Redis Server 2.6.12 (64位) spring- ...
- mysql 错误解决
1. Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE ...
- homework03
代码实现真的是大问题……在第二次作业还没有真正实现的情况下只能写这么一篇博客来整理一下从各位大神那里看到的东西. 两个弱菜加起来同样是弱菜,所以我和我的小伙伴的配合就是悲剧的聚合. 首先,大家都说C# ...
- Hibernate之Session缓存以及操作Session缓存的相关方法
1.Session概述 A.Session 接口是 Hibernate 向应用程序提供的操纵数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载 Java 对象的方法. B. Sessio ...
- [iOS基础控件 - 6.9] 聊天界面Demo
A.需求 做出一个类似于QQ.微信的聊天界面 1.每个cell包含发送时间.发送人(头像).发送信息 2.使用对方头像放在左边,我方头像在右边 3.对方信息使用白色背景对话框,我方信息使用蓝色背景对话 ...