├── README.md ├── .gitignore ├── Dockerfile ├── include ├── align.h ├── env.h ├── elf_utils.h └── log.h ├── Makefile ├── src ├── env.c ├── main.c ├── log.c └── elf_utils.c └── dummy └── dummy.c /README.md: -------------------------------------------------------------------------------- 1 | # tiny-link 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | build 3 | .vscode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt update -y 4 | RUN apt install gcc-8 -y 5 | RUN apt install make -y 6 | -------------------------------------------------------------------------------- /include/align.h: -------------------------------------------------------------------------------- 1 | #ifndef ALIGN_H 2 | #define ALIGN_H 3 | 4 | #define ALIGN_UP(x,size) (((x) + ((size)-1)) & (-(size))) 5 | #define ALIGN_DOWN(x,size) ((x) & (-(size))) 6 | 7 | #endif -------------------------------------------------------------------------------- /include/env.h: -------------------------------------------------------------------------------- 1 | #ifndef ENV_H 2 | #define ENV_H 3 | 4 | #include "log.h" 5 | 6 | struct { 7 | log_level_t log_level; 8 | } env_g; 9 | 10 | int init_env(); 11 | 12 | #endif -------------------------------------------------------------------------------- /include/elf_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef ELF_UTILS_H 2 | #define ELF_UTILS_H 3 | 4 | #include 5 | 6 | struct elf_context { 7 | int fd; 8 | Elf64_Ehdr header; 9 | Elf64_Phdr *program_header; 10 | Elf64_Ehdr *elf_base; 11 | }; 12 | 13 | int init_elf(const char *filename, struct elf_context *ctx); 14 | int parse_elf(struct elf_context *ctx); 15 | int mmap_elf_segments(struct elf_context *ctx); 16 | void fix_auxv(struct elf_context *ctx, const char *envp[]); 17 | void fix_argv(const char *argv[]); 18 | void run_elf_entry(struct elf_context *ctx, const char *argv[]); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR := ./src 2 | OBJ_DIR := ./build 3 | BIN_DIR := ./bin 4 | INCLUDE_DIR := ./include 5 | 6 | SRC_FILES := $(wildcard $(SRC_DIR)/*.c) 7 | OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRC_FILES)) 8 | 9 | CC := gcc-8 10 | CFLAGS := -Wall -Werror -Wextra 11 | LDFLAGS := -static-pie 12 | 13 | all: $(BIN_DIR)/ld.so $(BIN_DIR)/dummy.elf 14 | 15 | $(BIN_DIR)/dummy.elf: dummy/dummy.c 16 | $(CC) $(CFLAGS) -s -nostdlib -static -o $@ $^ 17 | 18 | $(BIN_DIR)/ld.so: $(OBJ_FILES) 19 | $(CC) $(LDFLAGS) -o $@ $^ 20 | 21 | $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c 22 | $(CC) $(CFLAGS) -I$(INCLUDE_DIR) -c -o $@ $< 23 | 24 | clean: 25 | rm $(BIN_DIR)/* $(OBJ_DIR)/* 26 | -------------------------------------------------------------------------------- /src/env.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "env.h" 4 | #include "log.h" 5 | 6 | static const char* get_env_or_default(const char* name, const char *default_value); 7 | 8 | int init_env() 9 | { 10 | const char* log_level_str = get_env_or_default("LOG_LEVEL", "INFO"); 11 | if (0 > str_to_log_level(log_level_str, &env_g.log_level)) { 12 | log_fatal("Unrecognized log level: %s", log_level_str); 13 | goto error; 14 | } 15 | 16 | return 0; 17 | 18 | error: 19 | return -1; 20 | } 21 | 22 | static const char* get_env_or_default(const char* name, const char *default_value) 23 | { 24 | const char* env = getenv(name); 25 | if (NULL == getenv(name)) { 26 | return default_value; 27 | } 28 | 29 | return env; 30 | } -------------------------------------------------------------------------------- /include/log.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | LOG_TRACE, 9 | LOG_DEBUG, 10 | LOG_INFO, 11 | LOG_WARN, 12 | LOG_ERROR, 13 | LOG_FATAL 14 | } log_level_t; 15 | 16 | void log_log(log_level_t level, const char *file, int line, const char *fmt, ...); 17 | int str_to_log_level(const char *str, log_level_t *level) ; 18 | 19 | #define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) 20 | #define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) 21 | #define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) 22 | #define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) 23 | #define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) 24 | #define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) 25 | 26 | #define log_errno(fmt, ...) log_error("%s: " fmt, strerror(errno), ##__VA_ARGS__) 27 | 28 | #endif -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "elf_utils.h" 4 | #include "log.h" 5 | #include "env.h" 6 | 7 | int main(int argc, const char *argv[], const char *envp[]) 8 | { 9 | if (2 > argc) { 10 | fprintf(stderr, "Usage: %s [elf] args...\n", argv[0]); 11 | goto error; 12 | } 13 | 14 | if (0 > init_env()) { 15 | goto error; 16 | } 17 | 18 | log_info("Got an Elf file: %s", argv[1]); 19 | 20 | struct elf_context ctx = {0}; 21 | if (0 > init_elf(argv[1], &ctx)) { 22 | goto error; 23 | } 24 | 25 | log_info("Parsring Elf"); 26 | if (0 > parse_elf(&ctx)) { 27 | goto error; 28 | } 29 | 30 | log_info("Loading Elf segments"); 31 | if (0 > mmap_elf_segments(&ctx)) { 32 | goto error; 33 | } 34 | 35 | log_info("Fixing argv and auxv"); 36 | fix_argv(argv); 37 | fix_auxv(&ctx, envp); 38 | 39 | log_info("Running Elf entry"); 40 | run_elf_entry(&ctx, argv); 41 | 42 | // should never get here 43 | 44 | error: 45 | return 1; 46 | } 47 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "log.h" 5 | #include "env.h" 6 | 7 | static const char *level_names[] = { 8 | "TRACE", 9 | "DEBUG", 10 | "INFO", 11 | "WARN", 12 | "ERROR", 13 | "FATAL" 14 | }; 15 | 16 | static const char *level_colors[] = { 17 | "\x1b[94m", 18 | "\x1b[36m", 19 | "\x1b[32m", 20 | "\x1b[33m", 21 | "\x1b[31m", 22 | "\x1b[35m" 23 | }; 24 | 25 | void log_log(log_level_t level, const char *file, int line, const char *fmt, ...) 26 | { 27 | if (level < env_g.log_level) { 28 | return; 29 | } 30 | 31 | fprintf(stderr, "%s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ", level_colors[level], level_names[level], file, line); 32 | 33 | va_list args; 34 | va_start(args, fmt); 35 | vfprintf(stderr, fmt, args); 36 | va_end(args); 37 | 38 | fprintf(stderr, "\n"); 39 | fflush(stderr); 40 | } 41 | 42 | int str_to_log_level(const char *str, log_level_t *level) 43 | { 44 | for (int i = 0; i < LOG_FATAL + 1; i++) { 45 | if (0 == strcmp(str, level_names[i])) { 46 | *level = i; 47 | return 0; 48 | } 49 | } 50 | 51 | return -1; 52 | } -------------------------------------------------------------------------------- /dummy/dummy.c: -------------------------------------------------------------------------------- 1 | #define BREAK __asm__ volatile("int $3;\n"); 2 | 3 | // Write syscall 4 | // ============= 5 | static long _write(long fd, const char* buf, unsigned long len) { 6 | 7 | register long ret asm ("rax"); 8 | register long _fd asm ("rdi") = fd; 9 | register const char* _buf asm ("rsi") = buf; 10 | register unsigned long _len asm ("rdx") = len; 11 | register int sys_write asm ("rax") = 1; 12 | asm volatile ( 13 | "syscall;" 14 | : "=r" (ret) 15 | : "r" (_fd), "r" (_buf), "r" (_len), "r" (sys_write) 16 | : 17 | ); 18 | return ret; 19 | } 20 | 21 | 22 | // exit syscall 23 | // ============= 24 | static long __exit(int errcode) { 25 | 26 | register long ret asm ("rax"); 27 | register int _errcode asm ("rdi") = errcode; 28 | register int sys__exit asm ("rax") = 60; 29 | asm volatile ( 30 | "syscall;" 31 | : "=r" (ret) 32 | : "r" (_errcode), "r" (sys__exit) 33 | : 34 | ); 35 | return ret; 36 | } 37 | 38 | void _start() 39 | { 40 | _write(0, "dummy\n", 7); 41 | __exit(123); 42 | } 43 | 44 | /*#include 45 | #include 46 | 47 | void main() 48 | { 49 | syscall(SYS_write, 0, "dummy\n", 7); 50 | syscall(SYS_exit_group, 123); 51 | }*/ 52 | -------------------------------------------------------------------------------- /src/elf_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "elf_utils.h" 13 | #include "align.h" 14 | #include "log.h" 15 | 16 | static int mmap_pt_load(struct elf_context *ctx, Elf64_Phdr *phdr); 17 | static int parse_elf_header(struct elf_context *ctx); 18 | static int parse_elf_program_header(struct elf_context *ctx); 19 | 20 | int init_elf(const char *filename, struct elf_context *ctx) 21 | { 22 | int fd = open(filename, O_RDONLY|O_CLOEXEC); 23 | if (0 > fd) { 24 | log_errno("open()"); 25 | goto error; 26 | } 27 | 28 | log_debug("Opened file %s, fd is %d", filename, fd); 29 | ctx->fd = fd; 30 | 31 | return 0; 32 | 33 | error: 34 | return -1; 35 | } 36 | 37 | int parse_elf(struct elf_context *ctx) 38 | { 39 | if (0 > parse_elf_header(ctx)) { 40 | goto error; 41 | } 42 | 43 | if (0 > parse_elf_program_header(ctx)) { 44 | goto error; 45 | } 46 | 47 | return 0; 48 | 49 | error: 50 | return -1; 51 | } 52 | 53 | static int parse_elf_header(struct elf_context *ctx) 54 | { 55 | if (0 > lseek(ctx->fd, 0, SEEK_SET)) { 56 | log_errno("lseek()"); 57 | goto error; 58 | } 59 | 60 | if (0 > read(ctx->fd, &(ctx->header), sizeof(ctx->header))) { 61 | log_errno("read()"); 62 | goto error; 63 | } 64 | 65 | if (ELFMAG0 != ctx->header.e_ident[EI_MAG0] || 66 | ELFMAG1 != ctx->header.e_ident[EI_MAG1] || 67 | ELFMAG2 != ctx->header.e_ident[EI_MAG2] || 68 | ELFMAG3 != ctx->header.e_ident[EI_MAG3]) { 69 | log_error("Elf magic is wrong"); 70 | goto error; 71 | } 72 | 73 | if (ELFCLASS64 != ctx->header.e_ident[EI_CLASS]) { 74 | log_error("Elf is non 64bit binary"); 75 | goto error; 76 | } 77 | 78 | if (ET_EXEC != ctx->header.e_type && 79 | ET_DYN != ctx->header.e_type) { 80 | log_error("Elf is neither exe nor so"); 81 | goto error; 82 | } 83 | 84 | if (EM_X86_64 != ctx->header.e_machine) { 85 | log_error("Elf is non x86_64 arch"); 86 | goto error; 87 | } 88 | 89 | if (0 == ctx->header.e_entry) { 90 | log_error("Elf has no entry point"); 91 | goto error; 92 | } 93 | 94 | return 0; 95 | 96 | error: 97 | return -1; 98 | } 99 | 100 | static int parse_elf_program_header(struct elf_context *ctx) 101 | { 102 | if (0 == ctx->header.e_phoff) { 103 | log_error("Elf has no program header"); 104 | goto error; 105 | } 106 | 107 | if (0 == ctx->header.e_phnum) { 108 | log_error("Elf has no program header entries"); 109 | goto error; 110 | } 111 | 112 | uint32_t phsize = sizeof(Elf64_Phdr) * ctx->header.e_phnum; 113 | log_debug("Allocting program header, phnum=%d, phsize=%d", ctx->header.e_phnum, phsize); 114 | 115 | ctx->program_header = malloc(phsize); 116 | if (NULL == ctx->program_header) { 117 | log_errno("malloc()"); 118 | goto error; 119 | } 120 | 121 | if (0 > lseek(ctx->fd, ctx->header.e_phoff , SEEK_SET)) { 122 | log_errno("lseek()"); 123 | goto error; 124 | } 125 | 126 | if (0 > read(ctx->fd, ctx->program_header, phsize)) { 127 | log_errno("read()"); 128 | goto error; 129 | } 130 | 131 | return 0; 132 | 133 | error: 134 | if (NULL != ctx->program_header) { 135 | free(ctx->program_header); 136 | ctx->program_header = NULL; 137 | } 138 | 139 | return -1; 140 | } 141 | 142 | int mmap_elf_segments(struct elf_context *ctx) 143 | { 144 | for (int i = 0; i < ctx->header.e_phnum; i++) { 145 | log_trace("Parsing segment %d, type=0x%08x", i, ctx->program_header[i].p_type); 146 | 147 | if (PT_LOAD == ctx->program_header[i].p_type) { 148 | if (0 > mmap_pt_load(ctx, &ctx->program_header[i])) { 149 | goto error; 150 | } 151 | } 152 | } 153 | 154 | return 0; 155 | 156 | error: 157 | return -1; 158 | } 159 | 160 | static int mmap_pt_load(struct elf_context *ctx, Elf64_Phdr *phdr) 161 | { 162 | log_debug("PT_LOAD: offset=%p, vaddr=%p, filesz=%p, memsz=%p, flags=%p", 163 | phdr->p_offset, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz, phdr->p_flags); 164 | 165 | uint64_t vaddr_aligned = ALIGN_DOWN(phdr->p_vaddr, PAGE_SIZE); 166 | uint64_t start_offset_aligned = ALIGN_DOWN(phdr->p_offset, PAGE_SIZE); 167 | uint64_t end_offset_aligned = ALIGN_UP(phdr->p_offset + phdr->p_filesz, PAGE_SIZE); 168 | uint64_t filesz_aligned = end_offset_aligned - start_offset_aligned; 169 | 170 | if (MAP_FAILED == mmap((void *)vaddr_aligned, 171 | filesz_aligned, 172 | phdr->p_flags, 173 | MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 174 | ctx->fd, 175 | start_offset_aligned)) { 176 | log_errno("mmap()"); 177 | goto error; 178 | } 179 | 180 | if (0 == start_offset_aligned) { 181 | log_debug("Elf base is at 0x%x", vaddr_aligned); 182 | ctx->elf_base = (Elf64_Ehdr *)vaddr_aligned; 183 | } 184 | 185 | uint64_t mmap_physical_end = vaddr_aligned + filesz_aligned; 186 | uint64_t mmap_virtual_end = phdr->p_vaddr + phdr->p_memsz; 187 | if (mmap_virtual_end <= mmap_physical_end) { 188 | return 0; 189 | } 190 | 191 | uint64_t mmap_virtual_size = mmap_virtual_end - mmap_physical_end; 192 | if (MAP_FAILED == mmap((void *)mmap_physical_end, 193 | mmap_virtual_size, 194 | phdr->p_flags, 195 | MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, 196 | -1, 197 | 0)) { 198 | log_errno("mmap()"); 199 | goto error; 200 | } 201 | 202 | return 0; 203 | 204 | error: 205 | return -1; 206 | } 207 | 208 | void fix_auxv(struct elf_context *ctx, const char *envp[]) 209 | { 210 | Elf32_auxv_t *auxv; 211 | while (*envp++ != NULL); 212 | 213 | for (auxv = (Elf32_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) { 214 | if (auxv->a_type == AT_PHDR) { 215 | auxv->a_un.a_val = (uint64_t)ctx->elf_base + ctx->header.e_phoff; 216 | } 217 | else if (auxv->a_type == AT_PHNUM) { 218 | auxv->a_un.a_val = ctx->header.e_phnum; 219 | } 220 | } 221 | } 222 | 223 | void fix_argv(const char *argv[]) 224 | { 225 | uint64_t *stack = (uint64_t *)argv; 226 | stack[0] = stack[-1] - 1; 227 | } 228 | 229 | void run_elf_entry(struct elf_context *ctx, const char *argv[]) 230 | { 231 | __asm__( 232 | "mov %0,%%rsp;" 233 | "mov %1,%%rbx;" 234 | "mov $0,%%rdx;" 235 | "jmp *%%rbx;" 236 | :: "r" (argv), "r" (ctx->header.e_entry) 237 | ); 238 | } --------------------------------------------------------------------------------