天天看点

一种hook libc库函数的简易方案

一、概述

有时候我们分析/逆向ELF文件时,可能想直接运行ELF看看效果,同时又想捕获ELF文件用了哪些字符串、回连地址&端口、操作了哪些文件等等特征信息。这时我们可以巧妙的借用LD_PRELOAD,来实现一种简易的hook libc库函数方案来打印我们想要的特征信息。

当然我们还可以用APK/ELF沙箱、HOOK等方式捕获更加详细的信息,我们这里不讨论,只就LD_PRELOAD来简单介绍。

二、LD_PRELOAD介绍

它允许你定义在程序运行前优先加载的动态链接库,用于有选择性的载入不同动态链接库中的相同函数。使用这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库(重写)

三、简易方案

1:初始化

在初始化阶段,主要工作:获取原始目标函数地址、获取配置文件信息等。设置_main()为构造函数,优于其它函数之前执行

// .init
__attribute__((constructor))
void _main()
{
    init();
}
           

接着,用dlsym获取目标libc函数原始地址。

typedef int (*PFN_connect)(int, const struct sockaddr *, socklen_t);

#define ADDFUNC( FUNCTYPE, SYMBOL ) (FUNCTYPE)getFuncAddr(SYMBOL)

void *LibcHelper::getFuncAddr( const char*  symbol ) 
{
    if ( symbol == NULL ) {
        return NULL;
    }
    void * handle = dlsym( libcHandle, symbol );
    if ( handle == NULL ) {
        LOGPRINT( "[-]dlsym fail: ", ".s", symbol );
    }
    return handle;
}

int LibcHelper::init()
{
    libcHandle = dlopen( "/system/lib/libc.so", RTLD_NOW|RTLD_GLOBAL );
    if ( !libcHandle ) {
        LOGPRINT( "Load libc fail.");
        return -;
    }

    pfnConnect = ADDFUNC(PFN_connect, "connect" );              
    return ;
}
           

2:实现hook函数

这是hook函数的主体部分,实现一个跟libc导出函数一致的函数。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
    LOGFUNC();
    // 获取原connect地址
    PFN_connect org_connect = LibcHelper::getInstance().getConnect();
    if ( org_connect != NULL ) {
        // 先调用原connect函数
        int ret = org_connect( sockfd, addr, addrlen );
        struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
        std::ostringstream s;
        if ( addr_in->sin_family ==  ) {
            struct sockaddr_un *sun = (struct sockaddr_un *)addr;
            s << "unix://" << sun->sun_path;
        }
        else {
            s << "ip://" << inet_ntoa(addr_in->sin_addr) << ":" << ntohs(addr_in->sin_port);
        }
        // 过滤无效目标,只对特定进程输出日志
        if ( CheckUtils::checkShowInfo() ) {
            // 记录到日志文件中
            LOGPRINT( "connect",
                "sockfd", sockfd,
                "addr", addr,
                "addrlen", addrlen,
                ret );
        }
        return ret;
    }
    LOGPRINT( "connect fail");
    return -;
}
           

3:测试

设置LD_PRELOAD环境变量的值为我们的so文件

adb push libcc.so /data/local/tmp
export LD_PRELOAD=/data/local/tmp/libcc.so
           

在这个shell中执行目标ELF文件,查看日志文件即可获取详细信息

一种hook libc库函数的简易方案

四、问题

1:这种方法暂时不能直接适用于静态编译的ELF文件

一个idea:参考IDA识别静态函数的方式 不知是否可行?
           

2:垃圾信息太多,需要过滤

只对特定的进程(process name)、路径等才输出到日志文件