Linux平台⼀种进程代码注⼊⽅法⽤于在⽬标程序的 main 函数执⾏前完成⼀些操作 特定情况下⽤来调试还是不错的。
源代码
/* fakemain.c
* Heiher <admin@heiher.info>
*/
#include <stdio.h>
#define __USE_GNU
#include <dlfcn.h>
static void do_something(void)
{
printf("Hello!\n");
}
int __libc_start_main(int (*main)(int,char**,char**),
int argc,char**ubp_av,void (*init)(void),
void (*fini)(void),void (*rtld_fini)(void),
void (*stack_end))
{
int (*__libc_start_main_real)(int (*main) (int,char**,char**),
int argc,char**ubp_av,void (*init)(void),
void (*fini)(void),void (*rtld_fini)(void),
void (*stack_end));
do_something();
__libc_start_main_real = dlsym(RTLD_NEXT, "__libc_start_main");
return __libc_start_main_real(main, argc, ubp_av, init, fini,
rtld_fini, stack_end);
}
编译
gcc -o libfakemain.so -fPIC -shared fakemain.c -ldl
测试
LD_PRELOAD=./libfakemain.so ls
Hello!
fakemain.c hotkey hotkey.vala libfakemain.so
Over!
1、简介
假设Linux上正在运⾏某程序,像Unix守护程序等,我们不想终⽌该程序,但是同时⼜需要更新程序的功能。⾸先映⼊脑海的可能是更新程序中⼀些已知函数,添加额外的功能,这样就不会影响到程序已有的功能,且不⽤终⽌程序。考虑向正在运⾏的程序中注⼊⼀些新的代码,当程序中已存在的另⼀个函数被调⽤时触发这些新代码。也许这种想法有些异想天开,但并不是不能实现的,有时我们确实需要向正在运⾏的程序中注⼊⼀些代码,当然其与病毒的代码注⼊技术与存在⼀定关联。
在本⽂中,我会向读者解释如何向正在Linux系统上运⾏的程序中注⼊⼀段C函数代码,⽽不必终⽌该程序。⽂中我们会讨论Linux⽬标⽂件格式Executable and Linkable Format(ELF),讨论⽬标⽂件sections(段)、symbols(符号)以及relocations(重定位)。
2、⽰例概述
笔者会利⽤以下简单的⽰例程序向读者⼀步步解释代码注⼊技术。⽰例由以下三部分组成:
(1)由源码dynlib.h与dynlib.c编译的动态(共享)库libdynlib.so
(2)由源码app.c编译的app程序,会链接libdynlib.so库
(3)injection.c⽂件中的注⼊函数
下⾯看⼀下这些代码:
//dynlib.h
extern void print();
dynlib.h⽂件中声明了printf()函数。
//dynlib.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include "dynlib.h"
extern void print()
{
static unsigned int counter = 0;
++counter;
printf("%d : PID %d : In print()\n", counter, getpid());
}
dynlib.c⽂件实现了print()函数,该函数只是打印⼀个计数(每次函数被调⽤时都会使该值增加)以及当前进程的pid。
//app.c
#include <stdio.h>
#include <unistd.h>
#include "dynlib.h"
int main()
{
while(1)
{
print();
printf("Going \n");
sleep(3);
printf("\n");
}
return 0;
}
app.c⽂件中的函数调⽤print()函数(来⾃libdynlib.so动态库),之后睡眠⼏秒钟,然后继续执⾏该⽆限循环。
//injection.c
#include <stdlib.h>
extern void print();
extern void injection()
{
print(); //原本的⼯作,调⽤print()函数
system("date"); //添加的额外⼯作
}
injection()函数调⽤会替换app.c⽂件中main()函数调⽤的print()函数调⽤。injection()函数⾸先会调⽤原print()函数,之后进⾏额外的⼯作。例如,它可以利⽤system()函数运⾏⼀些外部可执⾏程序,或者像本例中⼀样打印当前的⽇期。
3、编译并运⾏程序
⾸先利⽤gcc编译器编译这些源⽂件:
$ gcc -g -Wall dynlib.c -fPIC -shared -o libdynlib.so
$ gcc –g app.c –ldynlib –L ./ -o app
$ gcc -Wall injection.c -c -o injection.o
编译后的程序为:
-rwxrwxr-x 1 0×80 0×80 6224 Oct 15 14:04 app
-rw-rw-r– 1 0×80 0×80 888 Oct 16 17:53 injection.o
-rwxrwxr-x 1 0×80 0×80 5753 Oct 16 17:52 libdynlib.so
需要注意的是动态库libdynlib.so在编译时指定了-fPIC选项,⽤来⽣成地址⽆关的程序。下⾯运⾏app可执⾏程序:
[0x80@localhost dynlib]$ ./app
./app: error while loading shared libraries: libdynlib.so: cannot open shared object file: No such file or directory
如果产⽣以上错误,我们需要将⽣成的libdynlib.so⽂件拷贝到/usr/lib/⽬录下,再执⾏该程序,得到如下结果:
[0x80@localhost dynlib]$ ./app
1 : PID 25658 : In print()
Going to sleep…
Waked up…
2 : PID 25658 : In print()
Going to sleep…
Waked up…
3 : PID 25658 : In print()
Going to sleep…
4、调试应⽤程序
程序app只是⼀个简单的循环程序,这⾥我们假设其已经运⾏了⼏周,在不终⽌该程序的情况下,将我们的新代码注⼊到该程序中。在注⼊过程中利⽤Linux⾃带的功能强⼤的调试器gdb。⾸先我们需要利⽤pid(见程序的输出)将程序附着到gdb:
GNU gdb Red Hat Linux (6.3.0.0-1.122rh)
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 “i386-redhat-linux-gnu”…Using host libthread_db library “/lib/libthread_db.so.1″.
Attaching to program: /home/0×80/dynlib/app, process 25658
Reading symbols from shared object read from target memory…done.
Loaded system supplied DSO at 0×464000
`shared object read from target memory’ has disappeared; keeping its symbols.
Reading symbols from /usr/lib/libdynlib.so…done.
Loaded symbols for /usr/lib/libdynlib.so
Reading symbols from /lib/libc.so.6…done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2…done.
Loaded symbols for /lib/ld-linux.so.2
0×00464410 in __kernel_vsyscall ()
(gdb)
5、将注⼊代码加载到可执⾏程序的内存中
如前所述,⽬标⽂件injection.o初始并不包含在app可执⾏进程镜像中,我们⾸先需要将injection.o加载到进程的内存地址空间。可以通过mmap()系统调⽤,该系统调⽤可以将injection.o⽂件映射到app进程地址空间中。在gdb调试器中:
(gdb) call open(“injection.o”, 2)
$1 = 3
(gdb) call mmap(0, 888, 1|2|4, 1, 3, 0)
$2 = 1118208
(gdb)
⾸先利⽤O_RDWR(值为2)的读/写权限打开injection.o⽂件。⼀会之后我们在加载注⼊代码时做写修改,因此需要写权限。返回值为系统分配的⽂件描述符,可以看到值为3。之后调⽤mmap()系统调⽤将该⽂件载⼊进程的地址空间。mmap()函数原型如下:
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
函数包含6个参数:
可以看到/home/0×80/dynlib/injection.o 起始于进程地址空间的0×00111000地址处(转换成⼗进制即为1118208),终⽌于地址空间的0×00112000地址处。以上输出同时包含了其它动态库的映射信息。现在我们已经将所有需要的组件加载到可执⾏进程的内存空间中了。
aplinux下的sleep函数
如读者所见,print 符号重定位位于app 程序的绝对(虚拟)地址0×08049688偏移处,重定位的类型
为R_386_JUMP_SLOT 。在程序被加载到内存且在运⾏之前,重定位地址是⼀个绝对虚拟地址。注意该重定位驻留在程序⼆进制镜像的.rel.plt 段内。PLT 即Procedure Linkage Table 的缩写,是为函数间接调⽤提供的表,即在调⽤⼀个函数是,不是直接跳转到函数的位置,⽽是⾸先跳转到Procedure Linkage Table 的⼊⼝处,之后再从PLT 跳转到函数的实际代码处。如果要调⽤的函数位于⼀个动态库中(如本例中的libdynlib.so ),那么这种做法是必要的,因为我们不可能提前知道动态库会被加载到进程空间的什么位置,以及动态库中的第⼀个函数是什么(本位中为print()函数)。所有这些知识只在程序被加载到内存之后且运⾏之前有效,这时系统的动态链接器(Linux 系统中为ld-linux.so )会解决重定位的问题,使请求的函数能够被正确调⽤。在本⽂的例⼦中,动态链接器会将libdynlib.so 加载到可执⾏进程的地址空间,到print()函数在库中的地址,并将该地址设置为重定位地址0×08049688。
我们的⽬标是⽤injection.o ⽬标⽂件中injection()函数的地址替换print()函数的地址,该函数在程序刚开始运⾏之初并不包含在它的进程地址空间中。
更多关于ELF 格式、重定位以及动态链接器的的信息,读者可以参考Executable and Linkable Format(ELF)⽂档。
我们可以检查地址0×08049688正是函数print()函数的地址:
(gdb) p & print
$3 = (void (*)()) 0x50051c (gdb) p/x * 0×08049688
$4 = 0x50051c
(gdb)
injection()函数的地址可以通过对injection.o ⽂件运⾏readelf –s(显⽰⽬标⽂件的符号表)得到:00111000-00112000 rwxs 00000000 03:02 57933979 /home/0x80/dynlib/injection.o
00464000-00465000 r-xp 00464000 00:00 0 [vdso]
00500000-00501000 r-xp 00000000 03:01 5464089 /usr/lib/libdynlib.so
00501000-00502000 rw-p 00000000 03:01 5464089 /usr/lib/libdynlib.so
007bb000-007d4000 r-xp 00000000 03:01 1311704 /lib/ld-2.4.so
007d4000-007d5000 r--p 00018000 03:01 1311704 /lib/ld-2.4.so
007d5000-007d6000 rw-p 00019000 03:01 1311704 /lib/ld-2.4.so
007d8000-00904000 r-xp 00000000 03:01 1311705 /lib/libc-2.4.so
00904000-00907000 r--p 0012b000 03:01 1311705 /lib/libc-2.4.so
00907000-00908000 rw-p 0012e000 03:01 1311705 /lib/libc-2.4.so
00908000-0090b000 rw-p 00908000 00:00 0
08048000-08049000 r-xp 00000000 03:02 57933977 /home/ 0x80 /dynlib/app
08049000-0804a000 rw-p 00000000 03:02 57933977 /home/ 0x80 /dynlib/app
09ca5000-09cc6000 rw-p 09ca5000 00:00 0 [heap]
b7f94000-b7f95000 rw-p b7f94000 00:00 0
b7fa4000-b7fa6000 rw-p b7fa4000 00:00 0
bfb91000-bfba6000 rw-p bfb91000 00:00 0 [stack][0x80@localhost ~]$
[0x80@localhost dynlib]$ readelf -r app
Relocation section ‘.rel.dyn’ at offset 0×338 contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049678 00000c06 R_386_GLOB_DAT 00000000 __gmon_start__
Relocation section ‘.rel.plt’ at offset 0×340 contains 5 entries:
Offset Info Type Sym.Value Sym. Name
08049688 00000107 R_386_JUMP_SLOT 00000000 print
0804968c 00000207 R_386_JUMP_SLOT 00000000 puts
08049690 00000407 R_386_JUMP_SLOT 00000000 sleep
08049694 00000607 R_386_JUMP_SLOT 00000000 __libc_start_main
08049698 00000c07 R_386_JUMP_SLOT 00000000 __gmon_start__
[0x80@localhost dynlib]$
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论