[Ptrace]Linux内存替换(六)动态链接库函数替换

本文在 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


一、被注入程序的编写

【动态链接库】

//fso.c
//gcc -c -Wall -Werror -fPIC fso.c
//gcc -shared -o libfso.so fso.o

#include 

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 

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 @plt>
   0x0011145e <+46>:    movl   $0x0,-0x10(%ebp)
   0x00111465 <+53>:    jmp    0x11146f 63>
   0x00111467 <+55>:    addl   $0x1,-0xc(%ebp)
   0x0011146b <+59>:    addl   $0x1,-0x10(%ebp)
   0x0011146f <+63>:    cmpl   $0x98967f,-0x10(%ebp)
   0x00111476 <+70>:    jle    0x111467 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 
#include 
#include 
#include 
#include 
#include 
#include 

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内存替换)