├── README.md ├── CMakeLists.txt ├── .gitignore ├── src ├── main.c ├── sigdream.h └── sigdream.c └── dump.sh /README.md: -------------------------------------------------------------------------------- 1 | a *cursed* sigreturn-oriented programming (srop) based sleep obfuscation for linux that encrypts PT_LOAD segments + heap -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(sigdream LANGUAGES C) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | set(CMAKE_C_STANDARD_REQUIRED ON) 6 | 7 | find_package(OpenSSL REQUIRED) 8 | include_directories(${OPENSSL_INCLUDE_DIR}) 9 | 10 | string(APPEND CMAKE_C_FLAGS " -Wno-deprecated-declarations") 11 | 12 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 13 | add_compile_definitions(DEBUG) 14 | string(APPEND CMAKE_C_FLAGS " -Wall -Wextra -Wpedantic -g") 15 | endif() 16 | 17 | include_directories(src) 18 | 19 | add_library(sigdream_lib STATIC "src/sigdream.c") 20 | target_link_libraries(sigdream_lib ${OPENSSL_LIBRARIES}) 21 | 22 | add_executable(sigdream "src/main.c") 23 | target_link_libraries(sigdream sigdream_lib ${OPENSSL_LIBRARIES}) 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Help tools that honor '.gitignore' (redundant for Git itself). 2 | /.git 3 | 4 | /CMakeUserPresets.json 5 | 6 | # Common build directories 7 | /build*/ 8 | 9 | # CI jobs that run in symlinked trees produce these artifacts. 10 | /real_work/ 11 | /work 12 | 13 | # MacOS Finder files. 14 | .DS_Store 15 | 16 | # Python compile output. 17 | *.pyc 18 | 19 | # See Utilities/Sphinx/tutorial_archive.cmake 20 | /Help/_generated 21 | 22 | # CLion work directory 23 | /.idea/ 24 | # CLion build directories 25 | /cmake-build-*/ 26 | 27 | # QtCreator files. 28 | /CMakeLists.txt.user* 29 | 30 | # Visual Studio Code 31 | /.vscode/ 32 | /.cache/ 33 | 34 | # Visual Studio work directory 35 | /.vs/ 36 | # Visual Studio build directory 37 | /out/ 38 | 39 | # clang-tidy output 40 | /clang-tidy-fixes.patch -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "sigdream.h" 6 | 7 | static void do_work(void) { 8 | static int counter = 0; 9 | time_t now = time(NULL); 10 | struct tm *tm_info = localtime(&now); 11 | char timestamp[20]; 12 | 13 | strftime(timestamp, sizeof(timestamp), "%H:%M:%S", tm_info); 14 | printf("(%d) im alive @ %s\n", counter++, timestamp); 15 | } 16 | 17 | int main(void) { 18 | DEBUG_PRINT("pid: %d", getpid()); 19 | sigdream_ctx ctx = {0}; 20 | 21 | if (!sigdream_init(&ctx)) { 22 | printf("failed to initialize sigdream\n"); 23 | return 1; 24 | } 25 | 26 | while (1) { 27 | do_work(); 28 | // DEBUG_PRINT("debug sleep to view mem"); 29 | // sleep(5); /* sleep delay for debugging */ 30 | sigdream_sleep(&ctx, 5); 31 | } 32 | 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /src/sigdream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifdef DEBUG 7 | #define DEBUG_PRINT(fmt, ...) printf("[DEBUG] " fmt "\n", ##__VA_ARGS__) 8 | #else 9 | #define DEBUG_PRINT(fmt, ...) ((void)0) 10 | #endif 11 | 12 | #define RC4_KEY_SIZE 16 13 | 14 | typedef struct srop_sigcontext { 15 | uint64_t r8, r9, r10, r11, r12, r13, r14, r15; 16 | uint64_t rdi, rsi, rbp, rbx, rdx, rax, rcx, rsp, rip; 17 | uint64_t eflags; 18 | uint16_t cs, gs, fs, ss; 19 | uint64_t err, trapno, oldmask, cr2; 20 | uint64_t fpstate; 21 | uint64_t reserved[8]; 22 | } srop_sigcontext_t; 23 | 24 | /* ucontext subset needed for rt_sigreturn */ 25 | typedef struct srop_ucontext { 26 | uint64_t uc_flags; 27 | uint64_t uc_link; 28 | uint64_t uc_stack_sp; 29 | uint64_t uc_stack_flags; 30 | uint64_t uc_stack_size; 31 | srop_sigcontext_t uc_mcontext; 32 | uint64_t uc_sigmask; 33 | } srop_ucontext_t; 34 | 35 | /* signal frame layout expected by rt_sigreturn */ 36 | typedef struct srop_frame { 37 | uint64_t pretcode; 38 | srop_ucontext_t uc; 39 | } __attribute__((packed)) srop_frame_t; 40 | 41 | typedef struct sigdream_context { 42 | void *prog_base; 43 | int initialized; 44 | } sigdream_ctx; 45 | 46 | /* @brief initializes sigdream context 47 | * @param ctx: sigdream context to initialize 48 | * @retval 1 on success, 0 on failure 49 | */ 50 | int sigdream_init(sigdream_ctx *ctx); 51 | 52 | /* @brief encrypts memory regions, sleeps, then decrypts 53 | * @param ctx: initialized sigdream context 54 | * @param seconds: sleep duration in seconds 55 | * @retval 1 on success, 0 on failure 56 | */ 57 | int sigdream_sleep(sigdream_ctx *ctx, int seconds); 58 | -------------------------------------------------------------------------------- /dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BYTES=128 4 | PID=$(pgrep sigdream) 5 | 6 | if [ -z "$PID" ]; then 7 | echo "error: no sigdream process found" 8 | exit 1 9 | fi 10 | 11 | if [ ! -d "/proc/$PID" ]; then 12 | echo "error: process $PID not found" 13 | exit 1 14 | fi 15 | 16 | echo "========================================" 17 | echo "pid: $PID" 18 | echo "========================================" 19 | echo 20 | 21 | dump_region() { 22 | local name="$1" 23 | local pattern="$2" 24 | local alt_pattern="$3" 25 | 26 | local info=$(grep -E "$pattern" /proc/$PID/maps | head -1) 27 | 28 | if [ -z "$info" ] && [ -n "$alt_pattern" ]; then 29 | info=$(grep -E "$alt_pattern" /proc/$PID/maps | head -1) 30 | fi 31 | 32 | if [ -z "$info" ]; then 33 | echo "[$name] not found" 34 | echo 35 | return 36 | fi 37 | 38 | local perms=$(echo $info | awk '{print $2}') 39 | local start_addr=$(echo $info | cut -d' ' -f1 | cut -d'-' -f1) 40 | local end_addr=$(echo $info | cut -d' ' -f1 | cut -d'-' -f2) 41 | local start_dec=$((0x$start_addr)) 42 | local end_dec=$((0x$end_addr)) 43 | local size=$((end_dec - start_dec)) 44 | local dump_size=$((BYTES > size ? size : BYTES)) 45 | 46 | echo "[$name]" 47 | echo " address: 0x$start_addr - 0x$end_addr" 48 | echo " perms: $perms" 49 | echo " size: $size bytes" 50 | echo " dumping: $dump_size bytes" 51 | echo 52 | 53 | dd if=/proc/$PID/mem bs=1 count=$dump_size skip=$start_dec 2>/dev/null | \ 54 | hexdump -C | head -n $((dump_size / 16 + 1)) 55 | 56 | echo 57 | } 58 | 59 | echo "--- ELF HEADER ---" 60 | dump_region "elf" '.*r--p 00000000.*sigdream$' '.*rw-p 00000000.*sigdream$' 61 | 62 | echo "--- TEXT SECTION ---" 63 | dump_region "text" '.*r-xp.*sigdream$' '.*rw-p.*sigdream$' 64 | 65 | echo "--- HEAP ---" 66 | HEAP_INFO=$(grep '\[heap\]' /proc/$PID/maps) 67 | if [ -n "$HEAP_INFO" ]; then 68 | START_ADDR=$(echo $HEAP_INFO | cut -d' ' -f1 | cut -d'-' -f1) 69 | END_ADDR=$(echo $HEAP_INFO | cut -d' ' -f1 | cut -d'-' -f2) 70 | PERMS=$(echo $HEAP_INFO | awk '{print $2}') 71 | START_DEC=$((0x$START_ADDR)) 72 | END_DEC=$((0x$END_ADDR)) 73 | SIZE=$((END_DEC - START_DEC)) 74 | DUMP_SIZE=$((BYTES > SIZE ? SIZE : BYTES)) 75 | 76 | echo " address: 0x$START_ADDR - 0x$END_ADDR" 77 | echo " perms: $PERMS" 78 | echo " size: $SIZE bytes" 79 | echo " dumping: $DUMP_SIZE bytes" 80 | echo 81 | dd if=/proc/$PID/mem bs=1 count=$DUMP_SIZE skip=$START_DEC 2>/dev/null | \ 82 | hexdump -C | head -n $((DUMP_SIZE / 16 + 1)) 83 | echo 84 | else 85 | echo " [heap] not found" 86 | echo 87 | fi 88 | 89 | echo "--- ALL MAPPINGS ---" 90 | cat /proc/$PID/maps | grep -E '(sigdream|\[heap\]|\[stack\])' 91 | echo 92 | -------------------------------------------------------------------------------- /src/sigdream.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "sigdream.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define ElfW(type) Elf64_##type 14 | 15 | typedef struct mem_range { 16 | void *start; 17 | size_t size; 18 | int prot; 19 | } mem_range_t; 20 | 21 | /* mov eax, 15; syscall */ 22 | static const unsigned char sigret_gadget_eax[] = {0xb8, 0x0f, 0x00, 0x00, 23 | 0x00, 0x0f, 0x05}; 24 | 25 | /* mov rax, 15; syscall */ 26 | static const unsigned char sigret_gadget_rax[] = {0x48, 0xc7, 0xc0, 0x0f, 0x00, 27 | 0x00, 0x00, 0x0f, 0x05}; 28 | 29 | static void *g_stub_addr = NULL; 30 | static void *g_ret_addr = NULL; 31 | static uint64_t g_saved_rsp = 0; 32 | static uint64_t g_saved_rbp = 0; 33 | 34 | static void init_srop_frame(srop_frame_t *frame, uintptr_t fn, uint64_t rdi, 35 | uint64_t rsi, uint64_t rdx, uint64_t rcx, 36 | void *next_rsp) { 37 | memset(frame, 0, sizeof(*frame)); 38 | frame->pretcode = (uint64_t)g_stub_addr; 39 | frame->uc.uc_mcontext.rdi = rdi; 40 | frame->uc.uc_mcontext.rsi = rsi; 41 | frame->uc.uc_mcontext.rdx = rdx; 42 | frame->uc.uc_mcontext.rcx = rcx; 43 | frame->uc.uc_mcontext.rip = fn; 44 | frame->uc.uc_mcontext.rsp = (uint64_t)next_rsp; 45 | frame->uc.uc_mcontext.cs = 0x33; 46 | frame->uc.uc_mcontext.ss = 0x2b; 47 | } 48 | 49 | static void init_restore_frame(srop_frame_t *frame, uint64_t saved_rsp, 50 | uint64_t saved_rbp) { 51 | memset(frame, 0, sizeof(*frame)); 52 | frame->pretcode = (uint64_t)g_stub_addr; 53 | frame->uc.uc_mcontext.rip = (uint64_t)g_ret_addr; 54 | frame->uc.uc_mcontext.rsp = saved_rsp; 55 | frame->uc.uc_mcontext.rbp = saved_rbp; 56 | frame->uc.uc_mcontext.cs = 0x33; 57 | frame->uc.uc_mcontext.ss = 0x2b; 58 | } 59 | 60 | static inline __attribute__((always_inline, noreturn)) void 61 | trigger_sigreturn(void *frame) { 62 | __asm__ volatile("mov %0, %%rsp\n" 63 | "xor %%rax, %%rax\n" 64 | "mov $15, %%al\n" 65 | "syscall\n" 66 | : 67 | : "r"(frame) 68 | : "memory", "rax"); 69 | __builtin_unreachable(); 70 | } 71 | 72 | static void *find_gadget_in_region(void *start, size_t size) { 73 | unsigned char *p = (unsigned char *)start; 74 | unsigned char *end = p + size - sizeof(sigret_gadget_rax); 75 | 76 | for (; p < end; p++) { 77 | if (memcmp(p, sigret_gadget_rax, sizeof(sigret_gadget_rax)) == 0) { 78 | DEBUG_PRINT("found sigret gadget (mov rax) @ %p", p); 79 | return p; 80 | } 81 | if (memcmp(p, sigret_gadget_eax, sizeof(sigret_gadget_eax)) == 0) { 82 | DEBUG_PRINT("found sigret gadget (mov eax) @ %p", p); 83 | return p; 84 | } 85 | } 86 | 87 | return NULL; 88 | } 89 | 90 | static void *find_ret_in_region(void *start, size_t size) { 91 | unsigned char *p = (unsigned char *)start; 92 | unsigned char *end = p + size; 93 | 94 | for (; p < end; p++) { 95 | if (*p == 0xc3) { 96 | return p; 97 | } 98 | } 99 | return NULL; 100 | } 101 | 102 | static int scan_libc_for_gadget(void) { 103 | FILE *maps = fopen("/proc/self/maps", "r"); 104 | if (!maps) 105 | return 0; 106 | 107 | char line[512]; 108 | while (fgets(line, sizeof(line), maps)) { 109 | if (strstr(line, "r-xp") && strstr(line, "libc")) { 110 | uintptr_t start, end; 111 | if (sscanf(line, "%lx-%lx", &start, &end) == 2) { 112 | size_t size = end - start; 113 | void *gadget = find_gadget_in_region((void *)start, size); 114 | if (gadget) { 115 | g_stub_addr = gadget; 116 | g_ret_addr = find_ret_in_region((void *)start, size); 117 | fclose(maps); 118 | DEBUG_PRINT("using libc gadget, ret @ %p", g_ret_addr); 119 | return 1; 120 | } 121 | } 122 | } 123 | } 124 | 125 | fclose(maps); 126 | return 0; 127 | } 128 | 129 | static int init_stub(void) { 130 | if (g_stub_addr) 131 | return 1; 132 | 133 | if (scan_libc_for_gadget()) { 134 | return 1; 135 | } 136 | 137 | fprintf(stderr, "no sigret gadget found in libc\n"); 138 | return 0; 139 | } 140 | 141 | static int get_prog_base(sigdream_ctx *ctx) { 142 | ElfW(Phdr) *phdr = (ElfW(Phdr) *)getauxval(AT_PHDR); 143 | if (!phdr) 144 | return 0; 145 | 146 | ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)((uintptr_t)phdr - phdr->p_offset); 147 | ctx->prog_base = (void *)ehdr; 148 | DEBUG_PRINT("program base @ %p", ctx->prog_base); 149 | return 1; 150 | } 151 | 152 | static mem_range_t *get_pt_load_ranges(void *img_base, int *num_ranges) { 153 | ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)img_base; 154 | 155 | if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { 156 | fprintf(stderr, "invalid ELF header\n"); 157 | return NULL; 158 | } 159 | 160 | ElfW(Phdr) *phdr = (ElfW(Phdr) *)((char *)img_base + ehdr->e_phoff); 161 | int count = 0; 162 | 163 | for (int i = 0; i < ehdr->e_phnum; i++) { 164 | if (phdr[i].p_type == PT_LOAD) 165 | count++; 166 | } 167 | 168 | mem_range_t *ranges = malloc(sizeof(mem_range_t) * count); 169 | int j = 0; 170 | int page_size = getpagesize(); 171 | 172 | for (int i = 0; i < ehdr->e_phnum; i++) { 173 | ElfW(Phdr) *seg = &phdr[i]; 174 | if (seg->p_type != PT_LOAD) 175 | continue; 176 | 177 | int prot = 0; 178 | if (seg->p_flags & PF_R) 179 | prot |= PROT_READ; 180 | if (seg->p_flags & PF_W) 181 | prot |= PROT_WRITE; 182 | if (seg->p_flags & PF_X) 183 | prot |= PROT_EXEC; 184 | 185 | ranges[j].start = (char *)img_base + (seg->p_vaddr & ~(page_size - 1)); 186 | ranges[j].size = (seg->p_memsz + page_size - 1) & ~(page_size - 1); 187 | ranges[j].prot = prot; 188 | j++; 189 | } 190 | 191 | *num_ranges = count; 192 | return ranges; 193 | } 194 | 195 | static void get_heap_range(mem_range_t *heap_range) { 196 | FILE *maps = fopen("/proc/self/maps", "r"); 197 | if (!maps) 198 | return; 199 | 200 | char line[256]; 201 | while (fgets(line, sizeof(line), maps)) { 202 | if (strstr(line, "[heap]")) { 203 | uintptr_t start, end; 204 | if (sscanf(line, "%lx-%lx", &start, &end) == 2) { 205 | heap_range->start = (void *)start; 206 | heap_range->size = end - start; 207 | } 208 | break; 209 | } 210 | } 211 | fclose(maps); 212 | } 213 | 214 | int sigdream_init(sigdream_ctx *ctx) { 215 | if (!ctx) 216 | return 0; 217 | 218 | memset(ctx, 0, sizeof(sigdream_ctx)); 219 | 220 | if (!init_stub()) 221 | return 0; 222 | 223 | if (!get_prog_base(ctx)) 224 | return 0; 225 | 226 | ctx->initialized = 1; 227 | DEBUG_PRINT("sigdream initialized"); 228 | return 1; 229 | } 230 | 231 | int sigdream_sleep(sigdream_ctx *ctx, int seconds) { 232 | /* capture stack state for return after srop chain */ 233 | uint64_t frame_ptr; 234 | __asm__ volatile("mov %%rbp, %0" : "=r"(frame_ptr)); 235 | g_saved_rbp = *(uint64_t *)frame_ptr; 236 | g_saved_rsp = frame_ptr + 8; 237 | 238 | if (!ctx || !ctx->initialized || seconds <= 0) 239 | return 0; 240 | 241 | int num_ranges = 0; 242 | mem_range_t *ranges = get_pt_load_ranges(ctx->prog_base, &num_ranges); 243 | if (!ranges) 244 | return 0; 245 | 246 | mem_range_t heap_range = {0}; 247 | get_heap_range(&heap_range); 248 | 249 | unsigned char key_bytes[RC4_KEY_SIZE]; 250 | if (RAND_bytes(key_bytes, RC4_KEY_SIZE) != 1) { 251 | fprintf(stderr, "failed to generate RC4 key\n"); 252 | free(ranges); 253 | return 0; 254 | } 255 | DEBUG_PRINT("generated rc4 key for this cycle"); 256 | 257 | RC4_KEY rc4_key; 258 | struct timespec ts = {.tv_sec = seconds, .tv_nsec = 0}; 259 | 260 | /* 261 | * chain layout: 262 | * 1. mprotect regions to RW 263 | * 2. RC4_set_key + encrypt heap 264 | * 3. RC4_set_key + encrypt regions 265 | * 4. nanosleep 266 | * 5. RC4_set_key + decrypt regions 267 | * 6. RC4_set_key + decrypt heap 268 | * 7. mprotect regions back to original 269 | * 8. restore context and return 270 | */ 271 | int total_frames = (num_ranges * 4) + 8; 272 | size_t frames_size = sizeof(srop_frame_t) * total_frames; 273 | 274 | srop_frame_t *frames = mmap(NULL, frames_size, PROT_READ | PROT_WRITE, 275 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 276 | if (frames == MAP_FAILED) { 277 | free(ranges); 278 | return 0; 279 | } 280 | 281 | int idx = 0; 282 | 283 | for (int i = 0; i < num_ranges; i++) { 284 | init_srop_frame(&frames[idx], (uintptr_t)mprotect, 285 | (uint64_t)ranges[i].start, ranges[i].size, 286 | PROT_READ | PROT_WRITE, 0, &frames[idx + 1]); 287 | idx++; 288 | } 289 | 290 | init_srop_frame(&frames[idx], (uintptr_t)RC4_set_key, (uint64_t)&rc4_key, 291 | RC4_KEY_SIZE, (uint64_t)key_bytes, 0, &frames[idx + 1]); 292 | idx++; 293 | init_srop_frame(&frames[idx], (uintptr_t)RC4, (uint64_t)&rc4_key, 294 | heap_range.size, (uint64_t)heap_range.start, 295 | (uint64_t)heap_range.start, &frames[idx + 1]); 296 | idx++; 297 | 298 | init_srop_frame(&frames[idx], (uintptr_t)RC4_set_key, (uint64_t)&rc4_key, 299 | RC4_KEY_SIZE, (uint64_t)key_bytes, 0, &frames[idx + 1]); 300 | idx++; 301 | for (int i = 0; i < num_ranges; i++) { 302 | init_srop_frame(&frames[idx], (uintptr_t)RC4, (uint64_t)&rc4_key, 303 | ranges[i].size, (uint64_t)ranges[i].start, 304 | (uint64_t)ranges[i].start, &frames[idx + 1]); 305 | idx++; 306 | } 307 | 308 | init_srop_frame(&frames[idx], (uintptr_t)nanosleep, (uint64_t)&ts, 0, 0, 0, 309 | &frames[idx + 1]); 310 | idx++; 311 | 312 | init_srop_frame(&frames[idx], (uintptr_t)RC4_set_key, (uint64_t)&rc4_key, 313 | RC4_KEY_SIZE, (uint64_t)key_bytes, 0, &frames[idx + 1]); 314 | idx++; 315 | for (int i = 0; i < num_ranges; i++) { 316 | init_srop_frame(&frames[idx], (uintptr_t)RC4, (uint64_t)&rc4_key, 317 | ranges[i].size, (uint64_t)ranges[i].start, 318 | (uint64_t)ranges[i].start, &frames[idx + 1]); 319 | idx++; 320 | } 321 | 322 | init_srop_frame(&frames[idx], (uintptr_t)RC4_set_key, (uint64_t)&rc4_key, 323 | RC4_KEY_SIZE, (uint64_t)key_bytes, 0, &frames[idx + 1]); 324 | idx++; 325 | init_srop_frame(&frames[idx], (uintptr_t)RC4, (uint64_t)&rc4_key, 326 | heap_range.size, (uint64_t)heap_range.start, 327 | (uint64_t)heap_range.start, &frames[idx + 1]); 328 | idx++; 329 | 330 | for (int i = 0; i < num_ranges; i++) { 331 | init_srop_frame(&frames[idx], (uintptr_t)mprotect, 332 | (uint64_t)ranges[i].start, ranges[i].size, 333 | ranges[i].prot, 0, &frames[idx + 1]); 334 | idx++; 335 | } 336 | 337 | free(ranges); 338 | 339 | __asm__ volatile("" ::: "memory"); 340 | 341 | init_restore_frame(&frames[idx], g_saved_rsp, g_saved_rbp); 342 | 343 | DEBUG_PRINT("sleeping for %d seconds", seconds); 344 | trigger_sigreturn(&frames[0].uc); 345 | 346 | __builtin_unreachable(); 347 | } 348 | --------------------------------------------------------------------------------