使用lief对ELF文件Patch

LIEF

LIEF可以很方便的对elf, pe,MachO 文件进行parse和patch,由于其提供了python库,故可以实现跨平台。

这里以Android平台为例进行测试。

直接修改目标elf的导入符号

注:直接修改符号一般适用于修改前后参数类型、个数完全相同的情况

二进制文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main()
{
printf("id");
}
/*
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lief_test
LOCAL_SRC_FILES := main.c
LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE
include $(BUILD_EXECUTABLE)
*/

编译完成后,push到手机中,运行如下图:

upload successful

使用lief,直接将导入表中的printf符号替换为system

1
2
3
4
5
import lief
binary = lief.parse("lief_test")
puts_sym = filter(lambda e: e.name == "printf", binary.dynamic_symbols)[0]
puts_sym.name = "system"
binary.write("lief_test_patch");

将lief_test_patch push到手机中,执行结果如下图:

upload successful

修改 libc 中的相关符号,然后使用 LD_LIBRARY_PATH 加载修改后的库

这里需要搞清楚elf加载的lib位置,例如我在一个64bit手机上做实验,那么加载的lib位于/system/lib64/libc.so, 32bit的libc.so位于/system/lib/libc.so
使用LD_LIBRARY_PATH=xxx 指定动态链接库的路径

先上实例代码:

1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
puts("id");
printf("finished\n");
return 0;
}

这里要实现libc中的puts和system互换,使用lief

1
2
3
4
5
6
7
8
9
import lief
libc = lief.parse("libc.so")
system_sym = filter(lambda e: e.name == "system", libc.dynamic_symbols)[0]
puts_sym = filter(lambda e: e.name == "puts", libc.dynamic_symbols)[0]

//注意不能出现两个一样的symbol
puts_sym.name = "system"
system_sym.name = "puts"
libc.write("libc_patch");

将patch后的libc_patch重命名为libc.so,并与要执行的elf文件放在同一目录内,更改其链接库的path并执行

1
2
3
4
adb push libc_patch /data/local/tmp
cd /data/local/tmp
mv libc_patch libc.so
LD_LIBRARY_PATH=. ./lief_test

执行结果如下图所示:

upload successful

直接添加代码

经测试printf,system等函数好像不行,会报segment fault错误,待研究

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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("exp(%d) = %f\n", a, exp(a));
return 0;
}

目标是hook exp 函数,直接增加一个 segments , 然后劫持函数指针到这里。首先编译一个 lib 用来提供用于 hook 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
//test.c
double hook(double x) {
return x + 100;
}
/*
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hook
LOCAL_SRC_FILES := test.c
#LOCAL_CFLAGS += -pic -fPIC
#LOCAL_LDFLAGS += -pic -fPIC
include $(BUILD_SHARED_LIBRARY)
*/

exp函数在libm库中,因此将手机中的/system/lib/libm.so导出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lief

libm = lief.parse("libm.so")
hook = lief.parse("libhook.so")

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

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

exp_symbol = libm.get_symbol("exp")
hook_symbol = hook.get_symbol("hook")

exp_symbol.value = segment_added.virtual_address + hook_symbol.value

libm.write("libm.so.6")

运行结果如下图所示:

upload successful

通过 got/plt 表 直接劫持程序

未测试成功
https://lief-project.github.io/doc/latest/tutorials/05_elf_infect_plt_got.html

修改一个可执行文件为链接库

对于一个PIE的elf,如果想直接调用其中的某个函数,可以通过lief将其修改为library,通过dlopen/dlsym来调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//__attribute__ ((visibility ("hidden"))) 避免编译器自动导出
//__attribute__ ((noinline)) 取消内嵌优化
#define LOCAL __attribute__ ((visibility ("hidden")))
#define NOINLINE __attribute__ ((noinline))

NOINLINE LOCAL int check(char* input) {
if (strcmp(input, "easy") == 0) {
return 1;
}
return 0;
}

int main(int argc, char** argv) {

if (argc != 2) {
printf("Usage: %s flag\n", argv[0]);
exit(-1);
}

if (check(argv[1])) {
printf("Well done!\n");
} else {
printf("Wrong!\n");
}
return 0;
}
//gcc crackme101.c -O0 -fPIE -pie -Wl,-strip-all -o crackme101.bin

用IDA打开crackme101.bin,找到check函数的偏移地址,将其修改为导出函数:

1
2
3
4
import lief
crackme101 = lief.parse("./crackme101.bin")
crackme101.add_exported_function(0x72A, "check_found")
crackme101.write("libcrackme101.so")

此时的libcrackme101.so既是一个library,又是一个可执行文件

实现对其调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

typedef int(*check_t)(char*);

int main (int argc, char** argv) {

void* handler = dlopen("./libcrackme101.so", RTLD_LAZY);
check_t check_found = (check_t)dlsym(handler, "check_found");

int output = check_found(argv[1]);

printf("Output of check_found('%s'): %d\n", argv[1], output);

return 0;
}

参考文档:

  1. 使用lief和libfuzzer进行fuzz https://blahcat.github.io/2018/03/11/fuzzing-arbitrary-functions-in-elf-binaries/
  2. https://github.com/lief-project/LIEF
  3. https://lief.quarkslab.com/doc/#tutorials