Nook in the Lunar Mare

多种Hook技术记录

Feb 22, 2021


TODO: 待补充和更新


PRELOAD

Linux基础的hook方式,修改环境变量的LD_PRELOAD=xxx.so优先载入自己的so动态库。

  1. so对应的源文件里编写与hook目标同名且原型相同的函数,此后的控制流会优先转到该函数中。
  2. 可以利用dlopendlsym获取到原始函数的地址(比如要hook的是lib内的函数),这样可以实现在调用末尾返回到原始函数中。
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

typedef int(*strcmp_prototype)(const char *, const char *);
typedef strcmp_prototype strcmp_func;

// try to hook strcmp
int strcmp(const char *s1, const char *s2)
{
  void *handle = NULL;
  strcmp_func original_func = NULL;

  if (!handle)
  {
    handle = dlopen("libc.so.6", RTLD_LAZY);
    // oh use RTLD_NEXT to load it
    original_func = (strcmp_func)dlsym(handle, "strcmp");
  }

  printf("Nice Hack\n s1[%s]\n s2[%s]\n", s1, s2);

  return original_func(s1, s2);
}

目标

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  if (strcmp(argv[0], "password"))
    printf("Incorrect \n");
  else
    printf("Success\n");

  return 0;
}

编译+运行

gcc -shared -o libhook.so -ldl -rdynamic  -fPIC hook.c
gcc target.c
LD_PRELOAD=./libhook.so ./a.out                       
Nice Hack
 s1[./a.out]
 s2[password]
Incorrect 

常见的应用场景应该是网络类工具,可以做一个日志输出,或者类似proxychains对socket进行封装。

Ref

1 Intro

2.intro2

3 build shared object

4. LD_PRELOAD介绍

5. LD_PRELOAD的恶意利用预防

Dynamic Library

原理为上述中第二点,结合dlopen和dlsym获取函数名。这个技术的常见用例就是插件系统。大体思路是设计好API,使用Posix中的dlopen或则windows上的LoadLibrary进行实现。目前我尚未找到一个比较现代化的泛用的插件开发框架(或者辅助工具?),插件常与具体的框架整合在一起,例如Qt、COM这类。我看到一个比较可行的方法是嵌入某类脚本语言的解释器,以脚本语言作为插件开发语言,减轻插件开发者的压力。

基础的头文件,包含规定的API,程序主体与插件都需要包含该头文件。

/*
NAME : plugin.h
DESC : define Plugin APi
*/

#ifndef PROJ_PLUGIN_H
#define PROJ_PLUGIN_H

#include <stdio.h>
#include <string.h>

// pack possible paramter used in Plugin
// about context info or sth ...
// for example
/* struct ParamObjStruct
{
    // ... skip
    size_t data;
}

typedef ParamObjStruct *ParamObj;
*/

// It is optional, u can also unpack it as func's args
typedef struct 
{
  char *name;
  char *plugin_filename;
  size_t version;
} PluginInfoStruct;

typedef PluginInfoStruct *PluginInfo;

// define the prototype of plugin funtion
// can be different types
// I define 2 API here
typedef void(*pluginServiceA)(int);
typedef void *(*pluginServiceB)();

// for better readability
typedef pluginServiceA PLUGIN_API_A;
typedef pluginServiceB PLUGIN_API_B;

#endif

插件系统的头文件和伪实现如下

/**
* NAME: plugin_sys.h
* DESC: functions about plugin system
**/
#ifndef PROJ_PLUGIN_SYS_H
#define PROJ_PLUGIN_SYS_H
#include <dlfcn.h>
#include <stdlib.h>
#include "plugin.h"

#define MAX_PLUGIN_NUM 16

// a global var to store plugin file handle and plugin name
extern char **PLUGIN_NAME_LIST;
extern void **PLUGIN_HANDLER_LIST;
extern int PLUGIN_NO;

enum BOOL_ENUM
{FALSE,TRUE};
typedef enum BOOL_ENUM BOOL;

// you can also declare and implement a function that
// 1. scan a given plugin path (we can also specify an ext name for out plugins)
// and register them all
// such as "void scanPlugins(const char *path);"
// 2. or get plugin info from a config file

// register plugin into a place
BOOL registerPlugin(PluginInfo pluginInfo);

// load plugin and invoke service
void *loadPluginByIndex(const int id);

void invokePluginServiceA(void *handler, size_t data);
void *invokePluginServiceB(void *handler);

// plugin system init
void plugin_sys_init();
void plugin_sys_close();
#endif
/**
NAME : plugin_sys.c
DESC : plugin system implementation
**/
#include "plugin_sys.h"

char **PLUGIN_NAME_LIST;
void **PLUGIN_HANDLER_LIST;
int PLUGIN_NO;

// unpack pluginInfo and store id/name in PLUGIN_NAME_LIST and PLUGIN_HADNLER_LIST
BOOL registerPlugin(PluginInfo pluginInfo)
{
    // open the plugin filename
    void *handle = NULL;
    handle = dlopen(pluginInfo->plugin_filename, RTLD_NOW);

    if (handle == NULL)
    {
        printf("[*]Reg Plugin <%s>:<%s> Error: %s\n",
                pluginInfo->name,
                pluginInfo->plugin_filename,
                dlerror());

        return FALSE;
    }

    int name_len = strlen(pluginInfo->name);
    char *plugin_name = (char *)malloc(sizeof(char) * (name_len + 1));

    strncpy(plugin_name, pluginInfo->name, name_len);
    plugin_name[name_len] = '\0';
    PLUGIN_NAME_LIST[PLUGIN_NO] = plugin_name;
    

    PLUGIN_HANDLER_LIST[PLUGIN_NO] = handle;
    
    PLUGIN_NO += 1;
    printf("[*]Load plugin : %s, version : %d\n", pluginInfo->name, pluginInfo->version);
    
    return TRUE;
}

// load plugin and invoke service
void *loadPluginByIndex(const int id)
{
    printf("[*]Load Handler\n");
    return PLUGIN_HANDLER_LIST[id];
}


// transfer name to index, we won't implement it
// void *loadPluginByName(const char *name);

// wrapper function to invoke services inside plugin
void invokePluginServiceA(void *handler, size_t data)
{
    PLUGIN_API_A func = (PLUGIN_API_A)dlsym(handler, "serviceA");
    func(data);
}
void *invokePluginServiceB(void *handler)
{
    PLUGIN_API_B func = (PLUGIN_API_B)dlsym(handler, "serviceB");
    func();
}

// plugin system init
void plugin_sys_init()
{
    PLUGIN_NAME_LIST = (char **)malloc(sizeof(void *) * MAX_PLUGIN_NUM);
    PLUGIN_HANDLER_LIST = (void **)malloc(sizeof(void *) * MAX_PLUGIN_NUM);
    
    // record current plugin no.
    PLUGIN_NO = 0;
}
void plugin_sys_close()
{
    printf("[-]Start To close\n");
    if (PLUGIN_NAME_LIST != NULL)
    {
        for(int i = 0; i < PLUGIN_NO; ++i)
             free(PLUGIN_NAME_LIST[i]);
        free(PLUGIN_NAME_LIST);
        PLUGIN_NAME_LIST = NULL;
    }
    
    if (PLUGIN_HANDLER_LIST != NULL)
    {
        for (int i = 0; i < PLUGIN_NO; ++i)
            dlclose(PLUGIN_HANDLER_LIST[i]);
        free(PLUGIN_HANDLER_LIST);
        PLUGIN_HANDLER_LIST = NULL;
    }
    
    PLUGIN_NO = 0;
    printf("[-]Close\n");
}

接下来我们实现一个插件

/*
NAME : example_plugin.c
DESC : a demo to show plugin arch
*/

#include "plugin.h"
#include <stdio.h>

void serviceA(size_t data)
{
    printf("[Example Plugin]--Service A printf data : %d\n", data);
}
void *serviceB()
{
    printf("[Example Plugin]--Service B printf nothing and return NULL\n");
    return NULL;
}

测试文件如下

#include "plugin_sys.h"
#include "plugin.h"

int main(int argc, char *argv[])
{
    // init a plugin info for test
    PluginInfo info = (PluginInfo)malloc(sizeof(PluginInfoStruct));
    info->name = (char*)"Example Plugin";
    info->plugin_filename = (char *)"example.plg";
    info->version = 1;
    
    // init plugin system
    plugin_sys_init();
    
    // register our example plugin
    
    if (TRUE == registerPlugin(info))
    {
        void *handler = loadPluginByIndex(0);
        invokePluginServiceA(handler, 5);
        invokePluginServiceB(handler);
    }
    
    // close and clean
    plugin_sys_close();
    free(info);
}

makefile

plugin_sys: main.o plugin_sys.o
        gcc -o plugin_sys main.o plugin_sys.o -ldl

main.o: main.c plugin.h plugin_sys.h
        gcc -c -g main.c -ldl

plugin_sys.o: plugin_sys.c plugin_sys.h plugin.h
        gcc -c -g plugin_sys.c -ldl

clean:
        rm *.o

演示

# compile out test case
>> make
>> make clean

# compile our plugin
>> gcc -shared -o example.plg -ldl example_plugin.c  

# run and set temp LD LIBRARY
>> LD_LIBRARY_PATH=. ./plugin_sys 
[*]Load plugin : Example Plugin, version : 1
[*]Load Handler
[Example Plugin]--Service A printf data : 5
[Example Plugin]--Service B printf nothing and return NULL
[-]Start To close
[-]Close

Ref

  1. StackOverflow 对于C/C++用于插件设计的讨论
  2. StackOverflow上插件设计的最佳实践讨论,主要集中于概念上的问题
  3. 经典文章-CPlusPlus,例子小容易快速理解,探讨C/C++插件ABI中Compiler的影响–Making a plugin system
  4. [Dr.Dobbs的文章,太长加网站太慢看不下去,匆匆掠过part1]
  5. An example Plugin Framework - Pluga
  6. Linux Dynamic的较为全面的介绍–By 郑瀚

Trace Hook

  • ltrace
  • strace
  • fstrace
  • ptrace

Ref

1. tutorial

2. rootkit

3. ftrace

4. notes

5. 十年一剑

6. 文风畅快的一个博主

Code Injection / Redirection Trick

本章节首要目的为帮助我理解PLT和GOT的概念

其次,Code Injection的原理就是利用mmap将lib.so映射到进程空间中,根据mmap加载的基址加上段偏移计算加载后的地址,用该地址替换PLT表中跳转地址即可。可以用GDB进行实验(需要较高级权限)

Ref

1. Code Injection into Linux Application – 最初的例子考虑的是daemon热更新场景

2. PLT Redirection 的讲解

3. Redirectin Function Trick 三篇中最推荐的一篇