[Ptrace]Linux内存替换(六)动态链接库函数替换
2015-08-19 21:30
531 查看
本文在 Linux内存替换(四)代码注入 的基础上,介绍了代码注入替换运行程序加载的动态链接库中指定函数的方法。 网上的一种思路是利用在被注入程序中申请空间存放待注入代码,然后通过修改GOT表函数地址指针实现函数替换;本文介绍的方法利用预先构造好的函数Shellcode直接覆盖动态链接库中指定函数代码实现函数替换。两种方法相比,前者具有更好的适用性,而且自动化程度更高,后者思路和实现都更加简单。
实现环境:
CentOS 6.6 (Final)
Linux version 2.6.32-504.el6.i686
gcc version 4.4.7 20120313
nasm version 2.07
以上代码中的for循环,仅仅是为了确保Shellcode在注入时具有足够的存储空间。
【被注入程序】
关于被注入程序的编写,详细信息或出现错误请参见 加载动态链接库so程序简单实例
以上空指令nop,是为堆栈恢复和函数返回指令预留的存储空间,预留空指令空间必须比被注入程序动态库堆栈恢复和函数返回指令占用的空间大。
【提取Shellcode】
执行以上命令得到如下Shellcode,共计49个字节,但是该Shellcode还不能直接使用,需要根据目标程序动态链接库实际情况进行修改。
【修改Shellcode】
利用GDB调试被注入程序,得到动态库中函数堆栈恢复和函数返回的代码。
分析动态库hello函数的汇编代码,可以较容易的判断出恢复堆栈和函数返回的地址从0x00111478到0x0011147d共6个字节,且该函数实际可被shellcode覆盖填充的部分从0x00111437(函数开始的堆栈平衡处理部分不能覆盖,易导致异常)到0x0011147d共70个字节小于shellcode的49个字节,满足注入空间要求。因此最后得到的Shellcode如下:
查看被注入程序的内存分配情况,得到动态链接库加载基址(0x00111000),结合上面GDB调试得到的函数注入起始地址(0x00111437),可以确定注入地址的固定偏移量为0x437,因此只要注入程序确定动态链接库基址,即可利用固定偏移量得到注入起始地址。
以上注入部分代码可参考本系列前几篇文章。
执行注入程序:
以上被注入程序输出由Hello Myboy! 变成 Hello world! 且继续正常运行,说明注入已成功。
http://blog.csdn.net/myarrow/article/details/9630377
《linux下的动态链接库(DLL) 》
http://blog.csdn.net/tju355/article/details/6884696
实现环境:
CentOS 6.6 (Final)
Linux version 2.6.32-504.el6.i686
gcc version 4.4.7 20120313
nasm version 2.07
一、被注入程序的编写
【动态链接库】//fso.c //gcc -c -Wall -Werror -fPIC fso.c //gcc -shared -o libfso.so fso.o #include <stdio.h> void hello() { int i = 0; int j = 0; printf("Hello Myboy!\n"); for(i=0; i<10000000; i++) { j++; } }
以上代码中的for循环,仅仅是为了确保Shellcode在注入时具有足够的存储空间。
【被注入程序】
//main.c //gcc -L /home/mycos/so -Wall -o main main.c -lfso //export LD_LIBRARY_PATH=/home/mycos/so :$LD_LIBRARY_PATH #include <stdio.h> extern void hello(void); int main() { int i = 0; printf("This is Main!\n"); while(1) { if(i%10 == 0) printf("\n"); sleep(1); hello(); i++; } return 0; }
关于被注入程序的编写,详细信息或出现错误请参见 加载动态链接库so程序简单实例
二、Shellcode的编写
【hello.asm】; 32-bit "Hello World!" in CentOS 6 i686 ; nasm -felf32 hello.asm -o hello.o ; ld -s -o hello hello.o global _start _start: jmp string code: pop ecx mov eax, 0x4 mov ebx, 0x1 mov edx, 0xD int 0x80 nop nop nop nop nop nop nop nop string: call code db 'Hello world!',0x0a
以上空指令nop,是为堆栈恢复和函数返回指令预留的存储空间,预留空指令空间必须比被注入程序动态库堆栈恢复和函数返回指令占用的空间大。
【提取Shellcode】
for i in $(objdump -d hello |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo
执行以上命令得到如下Shellcode,共计49个字节,但是该Shellcode还不能直接使用,需要根据目标程序动态链接库实际情况进行修改。
\xe9\x1a\x00\x00\x00\x59\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xba\x0d\x00\x00\x00\xcd\x80\x90\x90\x90\x90\x90\x90\x90\x90\xe8\xdf\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x0a
【修改Shellcode】
利用GDB调试被注入程序,得到动态库中函数堆栈恢复和函数返回的代码。
[mycos@localhost so]$ gdb main (gdb) b hello Breakpoint 1 at 0x804842c (gdb) r Starting program: /home/mycos/so/main This is Main! Breakpoint 1, 0x00111434 in hello () from /home/mycos/so/libfso.so (gdb) disas hello Dump of assembler code for function hello: 0x00111430 <+0>: push %ebp 0x00111431 <+1>: mov %esp,%ebp 0x00111433 <+3>: push %ebx => 0x00111434 <+4>: sub $0x24,%esp 0x00111437 <+7>: call 0x111429 <__i686.get_pc_thunk.bx> 0x0011143c <+12>: add $0x1190,%ebx 0x00111442 <+18>: movl $0x0,-0x10(%ebp) 0x00111449 <+25>: movl $0x0,-0xc(%ebp) 0x00111450 <+32>: lea -0x10f8(%ebx),%eax 0x00111456 <+38>: mov %eax,(%esp) 0x00111459 <+41>: call 0x111334 <puts@plt> 0x0011145e <+46>: movl $0x0,-0x10(%ebp) 0x00111465 <+53>: jmp 0x11146f <hello+63> 0x00111467 <+55>: addl $0x1,-0xc(%ebp) 0x0011146b <+59>: addl $0x1,-0x10(%ebp) 0x0011146f <+63>: cmpl $0x98967f,-0x10(%ebp) 0x00111476 <+70>: jle 0x111467 <hello+55> 0x00111478 <+72>: add $0x24,%esp 0x0011147b <+75>: pop %ebx 0x0011147c <+76>: pop %ebp 0x0011147d <+77>: ret End of assembler dump.
(gdb) x /8xb 0x111478 0x111478 <hello+72>: 0x83 0xc4 0x24 0x5b 0x5d 0xc3 0x90 0x90
分析动态库hello函数的汇编代码,可以较容易的判断出恢复堆栈和函数返回的地址从0x00111478到0x0011147d共6个字节,且该函数实际可被shellcode覆盖填充的部分从0x00111437(函数开始的堆栈平衡处理部分不能覆盖,易导致异常)到0x0011147d共70个字节小于shellcode的49个字节,满足注入空间要求。因此最后得到的Shellcode如下:
\xe9\x1a\x00\x00\x00\x59\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xba\x0d\x00\x00\x00\xcd\x80\x83\xc4\x24\x5b\x5d\xc3\x90\x90\xe8\xdf\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x0a
查看被注入程序的内存分配情况,得到动态链接库加载基址(0x00111000),结合上面GDB调试得到的函数注入起始地址(0x00111437),可以确定注入地址的固定偏移量为0x437,因此只要注入程序确定动态链接库基址,即可利用固定偏移量得到注入起始地址。
[mycos@localhost asm]$ ps aux | grep main mycos 13626 0.0 1.3 22292 14376 pts/2 S+ 00:17 0:00 gdb main mycos 13628 0.0 0.0 1872 344 pts/2 T 00:17 0:00 /home/mycos/so/main mycos 13793 0.0 0.0 4352 724 pts/3 S+ 01:08 0:00 grep main [mycos@localhost asm]$ cat /proc/13628/maps 00110000-00111000 r-xp 00000000 00:00 0 [vdso] 00111000-00112000 r-xp 00000000 08:02 1054157 /home/mycos/so/libfso.so 00112000-00113000 rw-p 00000000 08:02 1054157 /home/mycos/so/libfso.so 008ed000-0090b000 r-xp 00000000 08:02 1053999 /lib/ld-2.12.so 0090b000-0090c000 r--p 0001d000 08:02 1053999 /lib/ld-2.12.so 0090c000-0090d000 rw-p 0001e000 08:02 1053999 /lib/ld-2.12.so 00913000-00aa3000 r-xp 00000000 08:02 1054095 /lib/libc-2.12.so 00aa3000-00aa5000 r--p 00190000 08:02 1054095 /lib/libc-2.12.so 00aa5000-00aa6000 rw-p 00192000 08:02 1054095 /lib/libc-2.12.so 00aa6000-00aa9000 rw-p 00000000 00:00 0 08048000-08049000 r-xp 00000000 08:02 1054152 /home/mycos/so/main 08049000-0804a000 rw-p 00000000 08:02 1054152 /home/mycos/so/main b7ff1000-b7ff2000 rw-p 00000000 00:00 0 b7ffe000-b8000000 rw-p 00000000 00:00 0 bffeb000-c0000000 rw-p 00000000 00:00 0 [stack]
三、注入程序的编写
【被注入程序】//injectso.c //gcc injectso.c -o injectso #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/user.h> #include <sys/file.h> #include <stdio.h> #include <string.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); if (data.val < 0) { printf("getdata1 Failed! \n"); return; } 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); if (data.val < 0) { printf("getdata2 Failed! \n"); return; } memcpy(laddr, data.chars, j); } str[len] = ' '; } 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; long rst; i = 0; j = len / long_size; laddr = str; while(i < j){ memcpy(data.chars, laddr, long_size); rst = ptrace(PTRACE_POKEDATA, child, addr + i*4, data.val); if (rst < 0) { printf("Putdata1 Failed! \n"); return; } ++i; laddr += long_size; } j = len % long_size; if(j != 0){ memcpy(data.chars, laddr, j); rst = ptrace(PTRACE_POKEDATA, child, addr + i*4, data.val); if (rst < 0) { printf("Putdata2 Failed! \n"); return; } } } long getsobaseaddr(pid_t pid, char* soname) { FILE *fp; char filename[30]; char line[100]; long addr; char str[100]; sprintf(filename, "/proc/%d/maps", pid); fp = fopen(filename, "r"); if(fp == NULL) return 1; while(fgets(line, 100, fp) != NULL) { sscanf(line, "%x-%*s %*s %*s %*s %*s %s", &addr, str, str, str, str, str, str); if(strstr(str, soname) != NULL) break; } fclose(fp); return addr + 0x437; //offset } int main(int argc, char *argv[]) { pid_t traced_process; struct user_regs_struct regs; int len = 49; /* hello world */ char code[] = "\xe9\x1a\x00\x00\x00\x59\xb8\x04" "\x00\x00\x00\xbb\x01\x00\x00\x00" "\xba\x0d\x00\x00\x00\xcd\x80\x83" "\xc4\x24\x5b\x5d\xc3\x90\x90\xe8" "\xe1\xff\xff\xff\x48\x65\x6c\x6c" "\x6f\x20\x77\x6f\x72\x6c\x64\x21" "\x0a"; if(argc != 2) { printf("PID?\n"); return 1; } traced_process = atoi(argv[1]); ptrace(PTRACE_ATTACH, traced_process, NULL, NULL); int pid = wait(NULL); printf("Attach Pid: %d\n",pid); ptrace(PTRACE_GETREGS, traced_process, NULL, ®s); long helloaddr = getsobaseaddr(traced_process, "libfso.so"); printf("Inject So Addr: %p\n", helloaddr); putdata(traced_process, helloaddr, code, len); ptrace(PTRACE_SETREGS, traced_process, NULL, ®s); ptrace(PTRACE_DETACH, traced_process, NULL, NULL); return 0; }
以上注入部分代码可参考本系列前几篇文章。
四、执行结果
执行被注入程序:[mycos@localhost so]$ ./main This is Main! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello Myboy! Hello world! Hello world! Hello world! Hello world!
执行注入程序:
[mycos@localhost so]$ ps aux | grep main mycos 13834 12.2 0.0 1872 384 pts/0 T 21:16 0:03 ./main mycos 13293 0.0 0.0 4352 720 pts/1 S+ 21:16 0:00 grep main [mycos@localhost so]$ ./injectso 13834
以上被注入程序输出由Hello Myboy! 变成 Hello world! 且继续正常运行,说明注入已成功。
五、参考内容
《使用ptrace向已运行进程中注入.so并执行相关函数 》http://blog.csdn.net/myarrow/article/details/9630377
《linux下的动态链接库(DLL) 》
http://blog.csdn.net/tju355/article/details/6884696
相关文章推荐
- Linux 网卡驱动学习(九)(层二转发)
- Linux写时复制技术
- BugFree3.0.4Linux环境安装指南
- Linux 虚拟地址与物理地址的映射关系分析
- Linux Malloc分析-从用户空间到内核空间
- Linux的C开发环境
- linux 命令 - 命令搜索命令 whereis which
- 华为成为 Linux 基金会白金会员
- 在Linux DeviceTree添加dtsi文件并在驱动中读取节点信息写入sys文件系统
- [ASM]Linux x86平台汇编实例
- [Linux 运维]/proc/modules 以及内核模块工具
- Linux下端口被占用解决
- 解决CentOS 6.2下安装ipvsadm-1.26报错
- Linux 命令 - 文件搜索命令 locate
- linux 常见命令 ,实用才是硬道理.
- CentOS中安装subversion,并使用svn+ssh访问
- Linux 开发闲杂知识点速查
- 解决虚拟机linux端mysql数据库无法远程访问
- linux下类似Bus Hound的工具
- linux 硬链接与软链接