linux hook

之前有分享 linux hook的方式,包括ld_preload,目前有個情況需要額外override 原本的某個function在linux 環境,這邊記錄一下可行方案

lief

https://github.com/lief-project/LIEF

code
pip install lief

如果採用直接修改elf 是否有可能直接hook 原來的function 直接看官方文檔 https://lief-project.github.io/doc/latest/tutorials/04_elf_hooking.html https://lief-project.github.io/doc/latest/tutorials/05_elf_infect_plt_got.html 我們直接對比較重要的這兩個範例紀錄一下 https://github.com/lief-project/tutorials 作者的github project連結

04_elf_hooking

do_math.c

code
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(int argc, char **argv) {
  if (argc != 2) {
    printf("Usage: %s <a> \n", argv[0]);
    exit(-1);
  }

  int a = atoi(argv[1]);
  printf("cos(%d) = %f\n", a, cos(a));
  return 0;
}

hook.c

code
double hook(double x) {
  return x + 1;
  //return (double)((int)x + 3);
}

insert_hook.py

code
#!/usr/bin/env python3

# Description
# -----------
# Hook the 'cos' function from the standard math library (libm)
import lief

libm = lief.parse("/usr/lib/libm.so.6")
hook = lief.parse("hook")

cos_symbol  = libm.get_symbol("cos")
hook_symbol = hook.get_symbol("hook")

code_segment = hook.segment_from_virtual_address(hook_symbol.value)
segment_added = libm.add(code_segment)

print("Hook inserted at VA: 0x{:06x}".format(segment_added.virtual_address))

# Offset of the function 'hook' within the CODE segment
hook_offset = hook_symbol.value - code_segment.virtual_address
new_addr    = segment_added.virtual_address + hook_offset
print(f"Change {cos_symbol.name}!{cos_symbol.value:x} -> {cos_symbol.name}!{new_addr:x}")
cos_symbol.value = new_addr
libm.write("libm.so.6")

到這邊do_math 所載入的 數學函式庫 cos 已經被替換成hook function

05_elf_infect_plt_got.html

這一個範例來說

crackme.c

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

// Damn_YoU_Got_The_Flag
char password[] = "\x18\x3d\x31\x32\x03\x05\x33\x09\x03\x1b\x33\x28\x03\x08\x34\x39\x03\x1a\x30\x3d\x3b";

inline int check(char* input);

int check(char* input) {
  for (int i = 0; i < sizeof(password) - 1; ++i) {
    password[i] ^= 0x5c;
  }
  return memcmp(password, input, sizeof(password) - 1);
}

int main(int argc, char **argv) {
  if (argc != 2) {
    printf("Usage: %s <password>\n", argv[0]);
    return EXIT_FAILURE;
  }

  if (strlen(argv[1]) == (sizeof(password) - 1) && check(argv[1]) == 0) {
    puts("You got it !!");
    return EXIT_SUCCESS;
  }

  puts("Wrong");
  return EXIT_FAILURE;

}

hook.c

code
#include "arch/x86_64/syscall.c"
#define stdout 1

int my_memcmp(const void* lhs, const void* rhs, int n) {
  const char msg[] = "Hook add\n";
  _write(stdout, msg, sizeof(msg));
  _write(stdout, (const char*)lhs, n);
  _write(stdout, "\n", 2);
  _write(stdout, (const char*)rhs, n);
  _write(stdout, "\n", 2);
  return 0;
}

hook_pltgot.py

code
# ./crackme.hooked XXXXXXXXXXXXXXXXXXXXX
# Hook add
# Damn_YoU_Got_The_Flag
# XXXXXXXXXXXXXXXXXXXXX
# You got it !!
import lief

crackme = lief.parse("crackme.bin")
hook    = lief.parse("hook")

segment_added  = crackme.add(hook.segments[0])

my_memcmp      = hook.get_symbol("my_memcmp")
my_memcmp_addr = segment_added.virtual_address + my_memcmp.value

crackme.patch_pltgot('memcmp', my_memcmp_addr)

# Remove bind now if present
if lief.ELF.DYNAMIC_TAGS.FLAGS in crackme:
    flags = crackme[lief.ELF.DYNAMIC_TAGS.FLAGS]
    flags.remove(lief.ELF.DYNAMIC_FLAGS.BIND_NOW)

if lief.ELF.DYNAMIC_TAGS.FLAGS_1 in crackme:
    flags = crackme[lief.ELF.DYNAMIC_TAGS.FLAGS_1]
    flags.remove(lief.ELF.DYNAMIC_FLAGS_1.NOW)

# Remove RELRO
if lief.ELF.SEGMENT_TYPES.GNU_RELRO in crackme:
    crackme[lief.ELF.SEGMENT_TYPES.GNU_RELRO].type = lief.ELF.SEGMENT_TYPES.NULL

crackme.write("crackme.hooked")

直接把memcmp 轉為 my_memcmp 間接orverride掉function ,試了幾次好像沒辦法替換掉libc 的 function

ld_preload

code
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <dlfcn.h>

static int (*orig_printf)(const char *format, ...) = NULL;

int printf(const char *format, ...)
{
 if (orig_printf == NULL)
 {
  orig_printf = (int (*)(const char *format, ...))dlsym(RTLD_NEXT, "printf");
 }
orig_printf("within my own printf\n");
 // TODO: print desired message from caller. 
 return orig_printf("within my own printf\n");
}
code
gcc -Wall -fPIC -shared -o PrintfHank.so PrintfHank.c -ldl 
export LD_PRELOAD=./PrintfHank.so

大概整理了這些方式,主要要針對沒辦法修改的程式碼進行額外處理 算是重新封裝printf ,讓輸出log可以濾掉 必須注意 https://stackoverflow.com/questions/1576689/problems-on-injecting-into-printf-using-ld-preload-method 有時候假設printf沒有帶參數可能會被優化成puts printf

code
printf("rko%d\n",123);

puts

code
printf("rko\n",123);

example

code
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdarg.h>
static int (*orig_printf)(const char *format, ...) = NULL;
static int (*orig_puts)(const char *str) = NULL;
int puts(const char *str)
{
    if (orig_puts == NULL)
    {
        orig_puts = (int (*)(const char *str))dlsym(RTLD_NEXT, "puts");
    }    
    return orig_puts("within my own puts");
}
int printf(const char *format, ...)
{


    if (orig_printf == NULL)
    {
        orig_printf = (int (*)(const char *format, ...))dlsym(RTLD_NEXT, "printf");
    }

    va_list arg;
   int done;

   va_start (arg, format);
   done = vfprintf (stdout, format, arg);
   char *ch;
   ch =arg;
   orig_printf("%s\n",ch);
   va_end (arg);

   return orig_printf("within my own printf\n");
}

ptrace

當然使用 ptrace 也可以

override printf

code
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
void hook_printf() {
    printf("printf has been intercepted!\n");
}

int main() {
    pid_t child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execl("./a.out", "24", 0);
    }
    else {
        wait(NULL);
        long orig_eax;
        while (1) {
        struct user_regs_struct regs;
            ptrace(PTRACE_SYSCALL, child, 0, 0);
            wait(NULL);
            ptrace(PTRACE_GETREGS, child, NULL, &regs);
            orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
            if (orig_eax == __NR_write) {
                // change the return address to hook_printf
                hook_printf();
                // read the string from memory
                char *str = (char*)regs.rdi;
                char buffer[1024];
                for (int i = 0; i < regs.rdx; i++) {
                buffer[i] = ptrace(PTRACE_PEEKDATA, child, regs.rsi + i, NULL);
                }
                buffer[regs.rdx] = '\0';
                printf("printf2: %s\n", buffer);
            }
        }
    }
    return 0;
}

no execute printf

code
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
void hook_printf() {
    printf("printf has been intercepted!\n");
}

int main() {
    pid_t child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execl("./a.out", "24", 0);
    }
    else {
        wait(NULL);
        long orig_eax;
        while (1) {
        struct user_regs_struct regs;
            ptrace(PTRACE_SYSCALL, child, 0, 0);
            wait(NULL);
            ptrace(PTRACE_GETREGS, child, NULL, &regs);
            orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
            if (orig_eax == __NR_write) {
                // change the return address to hook_printf
                hook_printf();
                regs.rax = -1;
                ptrace(PTRACE_SETREGS, child, NULL, &regs);
                printf("printf disabled\n");
                // read the string from memory
                // char *str = (char*)regs.rdi;
                // char buffer[1024];
                // for (int i = 0; i < regs.rdx; i++) {
                // buffer[i] = ptrace(PTRACE_PEEKDATA, child, regs.rsi + i, NULL);
                // }
                // buffer[regs.rdx] = '\0';
                // printf("printf: %s\n", buffer);

            }
        }
    }
    return 0;
}

run exit

code
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
void hook_printf() {
    printf("printf has been intercepted!\n");
}

int main() {
    pid_t child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execl("./a.out", "24", 0);
    }
    else {
          int status;
        wait(NULL);
        long orig_eax;
        while (1) {
        struct user_regs_struct regs;
            ptrace(PTRACE_SYSCALL, child, 0, 0);
            // wait(NULL);
            if(wait(&status) == -1)
                break;
            if (WIFEXITED(status)) {
                ptrace(PTRACE_DETACH, child, 0, 0);
                break;
            }
            ptrace(PTRACE_GETREGS, child, NULL, &regs);
            orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
            if (orig_eax == __NR_write) {
                // change the return address to hook_printf
                hook_printf();
                // regs.rax = -1;
                // ptrace(PTRACE_SETREGS, child, NULL, &regs);
                // printf("printf disabled\n");
                // read the string from memory
                char *str = (char*)regs.rdi;
                char buffer[1024];
                for (int i = 0; i < regs.rdx; i++) {
                buffer[i] = ptrace(PTRACE_PEEKDATA, child, regs.rsi + i, NULL);
                }
                buffer[regs.rdx] = '\0';
                printf("printf: %s\n", buffer);

            }
        }
    }
    return 0;
}

read arg

code
        execl("./a.out", "a.out","XXXXXXXXXX", 0);

這樣印出來的後綴都有加上想要的字串了,其實glibc 跟之前介紹一樣有malloc hook 和 free hook,ebpf 也可以達成hook不 過需要重編linux kernel 開啟ebpf的模組,使用ld_preload 還是蠻方便的不過權限可能需要實際情況才能確定,

參考

https://axcheron.github.io/playing-with-ld_preload/