原文地址:http://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application
介绍
假设你的程序在linux上运行,未来的很长一段时间也不想要终止运行,例如unix守护进程。然而你想要通过一个简单的方式来升级这个程序,但是却不想要终止程序的执行。这是你想到的就是在你的程序中更新一些已知的函数,这样这个程序就可以在不干扰函数的正常行为的基础上做一些额外的工作并且不需要终止你的程序。你会考虑到插入一些新的代码到你的程序的代码中,这样当其他的程序中已有的函数被调用的时候这些新插入的代码就会被处罚。这样虽然看起来不太现实,但是它展示了人们的想法,就是为什么有时候需要向正在运行的程序插入一些代码。当然这个技术也和向正在运行的代码中插入病毒的技术相关。
在这篇文章中,我会解释怎样插入一个c函数到一个正在运行的linux程序中而不需要终止程序的执行。我们会讨论的话题有:linxu对象文件ELF文件,对象文件的section,symbol以及relocation
工作实例概述
我会用下面的简单的例子一步一步的解释代码插入技术。这个例子包括三个部分:
1. 动态共享库libdynlib,so,由dynlib.hpp和dynlib.cpp两个C++文件构建。
2. 应用app,由app.cpp源文件构建,并且与libdynlib.so库相链接。
3. 在injection.cpp中的插入函数
让我们先看一下各个部件的代码
// dynlib.hpp
extern "C" void print();
dynlib.hpp头文件定义了print()函数
// dynlib.cpp
#include <stdlib.h>
#include <iostream>
#include "dynlib.hpp"
using namespace std;
extern "C" void print()
{
static unsigned int counter = 0;
++counter;
cout << counter << ": PID " << getpid() << ": In print() " << endl;
}
dynlib.cpp实现了print()函数,这个函数就是输出一个计数器,(这个计数器每次函数调用都会增加),并且输出了程序进程id。
// app.cpp
#include <dlfcn.h />
#include <iostream />
#include "dynlib.hpp"
using namespace std;
int main()
{
while (1)
{
print();
cout << "Going to sleep ..." << endl;
sleep(3);
cout << "Waked up ..." << endl;
}
return 0;
}
应用app.cpp调用print()函数【通过libdynlib.so这个动态库,然后sleep几秒,然后继续执行无限循环】
// injection.cpp
#include <stdlib.h />
extern "C" void print();
extern "C" void injection()
{
print(); // do the original job, call the function print()
system("date"); // do some additional job
}
injection()函数调用会替换应用的main函数中的print()函数。injection()函数会首先调用原来的print()函数,然后做一些额外的工作,例如他可以运行一些外部的可执行文件通过system()函数调用,或者打印当前的日期,正如我们在上面程序中所作的一样。
编译运行应用
下面让我们首先用g++ 这个C++编译器和gcc这个C编译器。
g++ -ggdb -Wall dynlib.cpp -fPIC -shared -o libdynlib.so
g++ -ggdb app.cpp -ldynlib -ldynlib -L./ -o app
gcc -Wall injection.cpp -c -o injection.o
-rwxr-xr-x 1 gregory ftp 52248 Feb 12 02:05 app
-rw-r--r-- 1 gregory ftp 1088 Feb 12 02:05 injection.o
-rwxr-xr-x 1 gregory ftp 52505 Feb 12 02:05 libdynlib.so
要注意的是动态链接库libdynlib.so用-fPIC标志进行编译和链接的,这回产生位置无关的代码,并且injection对象使用c编译器来编译。我们现在可以运行应用app这个可执行文件了。
[lnx63:code_injection] ==> ./app
1: PID 4184: In print()
Going to sleep ...
Waked up ...
2: PID 4184: In print()
Going to sleep ...
Waked up ...
3: PID 4184: In print()
Going to sleep ...
进入调试器
app这个应用已经开始执行了,我们现在要向它插入我们的新代码而不停止它的执行。在这个插入的过程中我们会使用Linux gdb调试器。首先我们要把gdb attach到应用的进程4184.
[lnx63:code_injection] ==> gdb app 4184
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
Using host libthread_db library "/lib/tls/libthread_db.so.1".
Attaching to program: /store/fileril104/project/gregory/code_injection/app, process 4184
Reading symbols from
/store/fileril104/project/gregory/code_injection/libdynlib.so...done.
Loaded symbols for /store/fileril104/project/gregory/code_injection/libdynlib.so
Reading symbols from /usr/lib/libstdc++.so.6...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/tls/libm.so.6...done.
Loaded symbols for /lib/tls/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0x006e17a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
(gdb)
把injection代码load到可执行文件进程内存中
正如我上面提到的injection.o对象文件最初并不包含在app执行文件进程映像中,我们首先需要把injection.o加载到进程的内存地址空间中,这可以通过mmap()系统调用完成,
(gdb) call open("injection.o", 2)
$1 = 3
(gdb) call mmap(0, 1088, 1 | 2 | 4, 1, 3, 0)
$2 = 1073754112
(gdb)
我们首先用O_RDWE来打开injection.o文件,我们需要写,因为一会儿我们要对加载的injection代码做修改,open调用返回的分配的文件描述符是3,然后我们把这个文件用mmap()调用带到进程的地址空间中,这个mmap()调用接受文件大小(1088字节)。文件映射权限PROT_READ|PROT_WRITE|PROT_EXEC用来读写执行。返回映射文件在进程地址空间的起始地址1073754112.。我们可以去人injectino.o确实映射到了进程的地址空间通过查看/proc/[pid]/maps,这里pid是可执行进程id,在我们的例子中时4184,。
[lnx63:code_injection] ==> cat /proc/4184/maps
006e1000-006f6000 r-xp 00000000 fd:00 394811 /lib/ld-2.3.4.so
006f6000-006f7000 r-xp 00015000 fd:00 394811 /lib/ld-2.3.4.so
006f7000-006f8000 rwxp 00016000 fd:00 394811 /lib/ld-2.3.4.so
006ff000-00824000 r-xp 00000000 fd:00 394812 /lib/tls/libc-2.3.4.so
00824000-00825000 r-xp 00124000 fd:00 394812 /lib/tls/libc-2.3.4.so
00825000-00828000 rwxp 00125000 fd:00 394812 /lib/tls/libc-2.3.4.so
00828000-0082a000 rwxp 00828000 00:00 0
00832000-00853000 r-xp 00000000 fd:00 394813 /lib/tls/libm-2.3.4.so
00853000-00855000 rwxp 00020000 fd:00 394813 /lib/tls/libm-2.3.4.so
0096e000-00975000 r-xp 00000000 fd:00 394816 /lib/libgcc_s-3.4.6-20060404.so.1
00975000-00976000 rwxp 00007000 fd:00 394816 /lib/libgcc_s-3.4.6-20060404.so.1
00978000-00a38000 r-xp 00000000 fd:00 45535 /usr/lib/libstdc++.so.6.0.3
00a38000-00a3d000 rwxp 000bf000 fd:00 45535 /usr/lib/libstdc++.so.6.0.3
00a3d000-00a43000 rwxp 00a3d000 00:00 0
08048000-08049000 r-xp 00000000 00:34 30468731 /store/fileril104/project/gregory/
code_injection/app
08049000-0804a000 rwxp 00000000 00:34 30468731 /store/fileril104/project/gregory/
code_injection/app
0804a000-0806b000 rwxp 0804a000 00:00 0
40000000-40001000 r-xp 00000000 00:34 30468725 /store/fileril104/project/gregory/
code_injection/libdynlib.so
40001000-40002000 rwxp 00000000 00:34 30468725 /store/fileril104/project/gregory/
code_injection/libdynlib.so
40002000-40003000 rwxp 40002000 00:00 0
40003000-40004000 rwxs 00000000 00:34 30468724 /store/fileril104/project/gregory/
code_injection/injection.o
4000f000-40011000 rwxp 4000f000 00:00 0
bfffe000-c0000000 rwxp bfffe000 00:00 0
ffffe000-fffff000 ---p 00000000 00:00 0
我们可以看到 /store/fileril104/project/gregory/code_injection/injection.o开始于地址
0x40003000
(decimal
1073754112
) ,终止于地址
0x40004000
。
Relocations
现在可以从内部检查应用的ELF格式的二进制可执行文件。我们会使用readelf命令来展示来自ELF格式对象文件的不同数据。