file / readelf
ELF 32-bit?
seo@ubuntu:~/study/LACTF2024/technically-correct$ file technically_correct technically_correct: ELF 32-bit MSB *unknown arch 0x3e00*
elf 헤더가 깨져있음.
seo@ubuntu:~/study/LACTF2024/technically-correct$ readelf -a ./technically_correct ELF Header: Magic: 7f 45 4c 46 01 02 a8 9e b6 21 74 80 06 55 b8 e5 Class: ELF32 Data: 2's complement, big endian Version: 168 <unknown> OS/ABI: <unknown: 9e> ABI Version: 182 Type: <unknown>: 200 Machine: <unknown>: 0x3e00 Version: 0x6ed7b4c7 Entry point address: 0x37c184d0 Start of program headers: 3338993664 (bytes into file) Start of section headers: 973078528 (bytes into file) Flags: 0x0 Size of this header: 36406 (bytes) Size of program headers: 8300 (bytes) Number of program headers: 15801 Size of section headers: 35328 (bytes) Number of section headers: 56772 Section header string table index: 5298 readelf: Warning: The e_shentsize field in the ELF header is larger than the size of an ELF section header readelf: Error: Reading 2005641216 bytes extends past end of file for section headers readelf: Error: Section headers are not available! readelf: Error: Too many program headers - 0x3db9 - the file is not that big There is no dynamic section in this file. readelf: Error: Too many program headers - 0x3db9 - the file is not that big
IDA Pro / Ghidra로도 열리지 않음.


strace
ptrace 안티 디버깅이 들어가있음.
seo@ubuntu:~/study/LACTF2024/technically-correct$ strace -f ./technically_correct execve("./technically_correct", ["./technically_correct"], 0x7ffcae644878 /* 24 vars */) = 0 ptrace(PTRACE_TRACEME) = -1 EPERM (Operation not permitted) exit(0) = ? +++ exited with 0 +++
gdb
ELF 파일 포맷이 이상해서 gdb로도 안붙여짐.
seo@ubuntu:~/study/LACTF2024/technically-correct$ gdb ./technically_correct ... GEF for linux ready, type `gef' to start, `gef config' to configure 90 commands loaded and 5 functions added for GDB 9.2 in 1.37ms using Python engine 3.8 "/home/seo/study/LACTF2024/technically-correct/./technically_correct": not in executable format: file format not recognized
gdbserver
디버거 붙이기전에 바이너리 내에서 ptrace(PTRACE_TRACEME)
를 호출하기에 디버깅이 안붙여지는거 같음.
seo@ubuntu:~/study/LACTF2024/technically-correct$ gdbserver :1234 ./technically_correct Process ./technically_correct created; pid = 4052 Listening on port 1234 ... seo@ubuntu:~/study/LACTF2024/technically-correct/ftrace-hook$ gdb -p 4052 warning: process 4052 is already traced by process 4048 ptrace: Operation not permitted.
Run
놀랍게도, ELF 구조 꺠져도 실행은 됨. no 출력.
seo@ubuntu:~/study/LACTF2024/technically-correct$ ./technically_correct no
디버깅 방법
https://github.com/ilammy/ftrace-hook
ftrace-hook 프로젝트를 이용하여 ptrace 커널 함수 후킹 코드 추가.
ptrace 후킹 함수에 다음 조건을 추가함.
- 프로세스 이름이 문제 파일 이름인 “prob”일 경우
- “/tmp/exit” 파일이 존재하지 않을 경우, 계속 무한 루프
- 파일이 존재할 경우,
real_sys_ptrace
호출하지 않고 return 0; 반환.
--- ./ftrace_hook.c.1 2025-04-21 05:10:09.857303366 -0700 +++ ./ftrace_hook.c 2025-04-21 05:09:38.213003904 -0700 @@ -15,6 +15,7 @@ #include <linux/uaccess.h> #include <linux/version.h> #include <linux/kprobes.h> +#include <linux/delay.h> MODULE_DESCRIPTION("Example module hooking clone() and execve() via ftrace"); MODULE_AUTHOR("ilammy <[email protected]>"); @@ -310,6 +311,42 @@ return ret; } + +/* 원본 syscall 함수 포인터 */ +static asmlinkage long (*real_sys_ptrace)(struct pt_regs *regs); + +/* 후킹 함수 */ +static asmlinkage long fh_sys_ptrace(struct pt_regs *regs) +{ + long ret; + unsigned long request = regs->di; /* 첫 번째 인자: 요청 타입 */ + pid_t target = (pid_t)regs->si; /* 두 번째 인자: 대상 PID */ + + pr_info("ptrace called by %s (pid:%d), req:%lu, target:%d\n", + current->comm, current->pid, + request, target); + + if (strcmp(current->comm, "prob") == 0) { + struct file *file; + + while (1) { + file = filp_open("/tmp/exit", O_RDONLY, 0); + if (!IS_ERR(file)) { + /* 파일을 찾았으면 닫고 루프 탈출 */ + filp_close(file, NULL); + pr_info("fh_sys_exit: /tmp/exit found, proceeding exit\n"); + return 0; + } + /* 없으면 1초 대기 후 다시 확인 */ + msleep(1000); + } + } + + /* 원본 ptrace 호출 */ + ret = real_sys_ptrace(regs); + + return ret; +} #else static asmlinkage long (*real_sys_execve)(const char __user *filename, const char __user *const __user *argv, @@ -356,6 +393,7 @@ static struct ftrace_hook demo_hooks[] = { HOOK("sys_clone", fh_sys_clone, &real_sys_clone), HOOK("sys_execve", fh_sys_execve, &real_sys_execve), + HOOK("sys_ptrace", fh_sys_ptrace, &real_sys_ptrace), }; static int fh_init(void)
후킹 모듈을 설치하면, ptrace에서 멈춰있다가
seo@ubuntu:~/study/LACTF2024/technically-correct$ strace ./prob execve("./prob", ["./prob"], 0x7ffe501d2cb0 /* 24 vars */) = 0 ptrace(PTRACE_TRACEME
/tmp/exit 파일 생성시, 계속 진행되어 안티 디버깅이 우회됨.
seo@ubuntu:~/study/LACTF2024/technically-correct$ strace ./prob execve("./prob", ["./prob"], 0x7ffe501d2cb0 /* 24 vars */) = 0 ptrace(PTRACE_TRACEME) = 0 write(1, "no\n", 3no ) = 3 exit(0) = ? +++ exited with 0 +++
gdbserver로 다시 디버깅 붙여보면, 그래도 안됨.
/build/gdb-gAcuhh/gdb-9.2/gdb/gdbserver/regcache.c:257: A problem internal to GDBserver has been detected. Unknown register pkru requested ... gef➤ target remote :1234 Remote debugging using :1234 Remote connection closed
/tmp/exit 파일을 지우고, ./prob 실행. ptrace 단계에 멈춰있는 상태에서 gdb -p <pid>로 붙임.
그러면, gdb 창에서 Attaching to process 5333 메시지와 함께 멈춤.
이 상태에서 /tmp/exit 파일 생성하면, gdb 창에서 디버거가 성공적으로 붙여짐.
seo@ubuntu:~/study/LACTF2024/technically-correct/ftrace-hook$ gdb -p 5333 GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.2) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". GEF for linux ready, type `gef' to start, `gef config' to configure 90 commands loaded and 5 functions added for GDB 9.2 in 2.02ms using Python engine 3.8 Attaching to process 5333 warning: "/home/seo/study/LACTF2024/technically-correct/prob": not in executable format: file format not recognized warning: `/home/seo/study/LACTF2024/technically-correct/prob': can't read symbols: file format not recognized. warning: Could not load vsyscall page because no executable was specified 0x000005c7d084c577 in ?? () [ Legend: Modified register | Code | Heap | Stack | String ] ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ──── [!] Command 'registers' failed to execute properly, reason: max() arg is an empty sequence ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ──── 0x007fff0b643ca0│+0x0000: 0x0000000000000001 0x007fff0b643ca8│+0x0008: 0x007fff0b64573f → 0x5300626f72702f2e ("./prob"?) 0x007fff0b643cb0│+0x0010: 0x0000000000000000 0x007fff0b643cb8│+0x0018: 0x007fff0b645746 → "SHELL=/bin/bash" 0x007fff0b643cc0│+0x0020: 0x007fff0b645756 → "PWD=/home/seo/study/LACTF2024/technically-correct" 0x007fff0b643cc8│+0x0028: 0x007fff0b645788 → "LOGNAME=seo" 0x007fff0b643cd0│+0x0030: 0x007fff0b645794 → "XDG_SESSION_TYPE=tty" 0x007fff0b643cd8│+0x0038: 0x007fff0b6457a9 → "MOTD_SHOWN=pam" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:generic: ──── 0x5c7d084c56a test DWORD PTR [rdi+0x3da33687], ecx 0x5c7d084c570 mov eax, 0x65 0x5c7d084c575 syscall [!] Command 'context' failed to execute properly, reason: gef➤
Analysis
쉘코드는 다음과 같음.
gef➤ x/50i $rip-7 0x5c7d084c570: mov eax,0x65 0x5c7d084c575: syscall => 0x5c7d084c577: test eax,eax 0x5c7d084c579: jne 0x5c7d084c603 0x5c7d084c57f: pop rsi 0x5c7d084c580: cmp rsi,0x2 0x5c7d084c584: jl 0x5c7d084c5de 0x5c7d084c586: pop rsi 0x5c7d084c587: pop rsi 0x5c7d084c588: movabs rbx,0xf84bc1f88e8 0x5c7d084c592: movzx eax,BYTE PTR [rsi] 0x5c7d084c595: cmp al,0xa 0x5c7d084c597: je 0x5c7d084c5cf 0x5c7d084c599: cmp al,0x0 0x5c7d084c59b: je 0x5c7d084c5cf 0x5c7d084c59d: cmp al,0x7e 0x5c7d084c59f: ja 0x5c7d084c5de 0x5c7d084c5a1: sub al,0x20 0x5c7d084c5a3: jb 0x5c7d084c5de 0x5c7d084c5a5: lea rdx,[rbx+rax*8] 0x5c7d084c5a9: mov rbx,QWORD PTR [rdx] 0x5c7d084c5ac: xor rbx,rdx 0x5c7d084c5af: movabs r8,0xb216cb3c48c1e693 0x5c7d084c5b9: imul rbx,r8 0x5c7d084c5bd: movabs r8,0xc200c6d3267c529d 0x5c7d084c5c7: add rbx,r8 0x5c7d084c5ca: inc rsi 0x5c7d084c5cd: jmp 0x5c7d084c592 0x5c7d084c5cf: movabs rcx,0x7038fc00be0 0x5c7d084c5d9: cmp rbx,rcx 0x5c7d084c5dc: je 0x5c7d084c5ea 0x5c7d084c5de: push 0xa6f6e 0x5c7d084c5e3: mov edx,0x3 0x5c7d084c5e8: jmp 0x5c7d084c5f4 0x5c7d084c5ea: push 0xa736579 0x5c7d084c5ef: mov edx,0x4 0x5c7d084c5f4: mov eax,0x1 0x5c7d084c5f9: mov edi,0x1 0x5c7d084c5fe: mov rsi,rsp 0x5c7d084c601: syscall 0x5c7d084c603: mov eax,0x3c 0x5c7d084c608: xor edi,edi 0x5c7d084c60a: syscall
쉘코드 덤프.
dump binary memory sc.bin 0x5c7d084c570 0x5c7d084c60c
검사
rsp에 있는 값을 pop해서 rsi 값이 2보다 작으면 분기.
seg000:000000000000000F pop rsi seg000:0000000000000010 cmp rsi, 2 seg000:0000000000000014 jl short loc_6E ; 'no\n'
여기서 rsi는 곧 argc
를 의미함. 즉, 프로그램 실행시킬 때 매개변수 필요.
gef➤ x/10i $rip => 0x5c7d084c57f: pop rsi 0x5c7d084c580: cmp rsi,0x2 0x5c7d084c584: jl 0x5c7d084c5de 0x5c7d084c586: pop rsi 0x5c7d084c587: pop rsi 0x5c7d084c588: movabs rbx,0xf84bc1f88e8 0x5c7d084c592: movzx eax,BYTE PTR [rsi] 0x5c7d084c595: cmp al,0xa 0x5c7d084c597: je 0x5c7d084c5cf 0x5c7d084c599: cmp al,0x0 gef➤ x/4gx $rsp 0x7ffe4800aab0: 0x0000000000000001 0x00007ffe4800b73f 0x7ffe4800aac0: 0x0000000000000000 0x00007ffe4800b746 gef➤ x/gx 0x00007ffe4800b73f 0x7ffe4800b73f: 0x5300626f72702f2e gef➤ x/4s 0x00007ffe4800b73f 0x7ffe4800b73f: "./prob" 0x7ffe4800b746: "SHELL=/bin/bash" 0x7ffe4800b756: "PWD=/home/seo/study/LACTF2024/technically-correct" 0x7ffe4800b788: "LOGNAME=seo"
loc_22 (0x5c7d084c592)
실행:
./prob ABCDEFGHIJKLMNOPQRSTUVWXYZ
al = 0x41 al이 0xa일 경우, loc_5F 분기.
gef➤ x/15i $rip => 0x5c7d084c592: movzx eax,BYTE PTR [rsi] 0x5c7d084c595: cmp al,0xa 0x5c7d084c597: je 0x5c7d084c5cf gef➤ info reg rsi rsi 0x7fff4e2ae72b 0x7fff4e2ae72b gef➤ x/gx 0x7fff4e2ae72b 0x7fff4e2ae72b: 0x4847464544434241 gef➤ si gef➤ x/15i $rip => 0x5c7d084c595: cmp al,0xa gef➤ info reg al al 0x41 0x41
al = 0x41 al이 0일 경우, loc_5F 분기.
gef➤ x/10i $rip => 0x5c7d084c599: cmp al,0x0 0x5c7d084c59b: je 0x5c7d084c5cf 0x5c7d084c59d: cmp al,0x7e gef➤ info reg al al 0x41 0x41
- loc_5F
rcx가 7038FC00BE0값이면, “yes\n”
seg000:000000000000005F loc_5F: ; CODE XREF: sub_0+27↑j seg000:000000000000005F ; sub_0+2B↑j seg000:000000000000005F mov rcx, 7038FC00BE0h seg000:0000000000000069 cmp rbx, rcx seg000:000000000000006C jz short loc_7A ; 'yes\n'
al = 0x41 if al > 0x7E → jump “no\n” if al < 0x20 → jump “no\n”
sub al, 20h
에 의해 al 값은 0x20 감소.
cmp al, 7Eh ; '~' ja short loc_6E ; if al > 0x7E → jump sub al, 20h ; ' ' jb short loc_6E ; if al < 0x20 → jump
전체 분기문 어셈블리 코드는 다음과 같음.
seg000:0000000000000022 seg000:0000000000000022 loc_22: ; CODE XREF: sub_0+5D↓j seg000:0000000000000022 movzx eax, byte ptr [rsi] seg000:0000000000000025 cmp al, 0Ah seg000:0000000000000027 jz short loc_5F seg000:0000000000000029 cmp al, 0 seg000:000000000000002B jz short loc_5F seg000:000000000000002D cmp al, 7Eh ; '~' seg000:000000000000002F ja short loc_6E ; 'no\n' seg000:0000000000000031 sub al, 20h ; ' ' seg000:0000000000000033 jb short loc_6E ; 'no\n' seg000:0000000000000035 lea rdx, [rbx+rax*8] seg000:0000000000000039 mov rbx, [rdx] seg000:000000000000003C xor rbx, rdx seg000:000000000000003F mov r8, 0B216CB3C48C1E693h seg000:0000000000000049 imul rbx, r8 seg000:000000000000004D mov r8, 0C200C6D3267C529Dh seg000:0000000000000057 add rbx, r8 seg000:000000000000005A inc rsi seg000:000000000000005D jmp short loc_22
전체적으로, c언어로 재구성하면, 다음과 같음.
#include <stdio.h> #include <stdint.h> int readbuf(uint64_t addr, void* output, size_t size) { memcpy((void*)output, addr, size); return 0; } uint64_t read64(uint64_t what) { uint64_t value = 0; readbuf(what, &value, sizeof(value)); return value; } int main(int argc, char *argv[], char *envp[]) { uint64_t rbx = 0x0F84BC1F88E8; //loop_21 char *input = argv[1]; printf("argv[1]: %s\n", input); int rsi = 0; while (1) { if(input[rsi] == '\n') goto loc_5F; if(input[rsi] == '\0') goto loc_5F; if(input[rsi] > 0x7E) goto loc_6E; if(input[rsi] < 0x20) goto loc_6E; input[rsi] -= 0x20; uint64_t rdx = rbx + input[rsi]*8; rbx = read64(rdx) ^ rdx; uint64_t r8 = 0x0B216CB3C48C1E693; rbx *= r8; r8 = 0x0C200C6D3267C529D; rbx += r8; rsi+=1; } loc_5F: printf("rbx: 0x%lx\n", rbx); if(rbx == 0x7038FC00BE0) { puts("yes\n"); return 0; } loc_6E: puts("no\n"); return 0; }
문제 환경을 재구성시키기 위해 덤프함
- dump_all.py
import gdb import re import os class DumpAllMappings(gdb.Command): """dump-all: info proc mappings를 통해 모든 메모리 영역을 파일로 덤프합니다. 사용법: (gdb) dump-all """ def __init__(self): super(DumpAllMappings, self).__init__("dump-all", gdb.COMMAND_USER) def invoke(self, arg, from_tty): inferior = gdb.selected_inferior() try: mappings = gdb.execute("info proc mappings", to_string=True) except gdb.error as e: gdb.write(f"info proc mappings 명령을 사용할 수 없습니다: {e}\n", gdb.STDERR) return # 정규 표현식을 사용하여 각 메모리 영역의 시작 주소와 끝 주소를 추출합니다. pattern = re.compile(r'^\s*([0-9a-fA-Fx]+)\s+([0-9a-fA-Fx]+)\s+.*$') for line in mappings.splitlines(): match = pattern.match(line) if not match: continue start_str, end_str = match.groups() try: start = int(start_str, 16) end = int(end_str, 16) size = end - start # 메모리 읽기 try: mem = inferior.read_memory(start, size) except gdb.MemoryError: gdb.write(f"메모리 읽기 실패: {start_str} - {end_str}\n", gdb.STDERR) continue # 파일명 생성 및 쓰기 filename = f"{start_str}_{size}.bin" try: with open(filename, "wb") as f: f.write(mem.tobytes()) gdb.write(f"메모리 덤프 완료: {filename}\n") except IOError as e: gdb.write(f"파일 쓰기 실패: {filename}: {e}\n", gdb.STDERR) except ValueError: continue DumpAllMappings()
- Result
gef➤ dump-all 메모리 덤프 완료: 0x1bd4eff000_4096.bin 메모리 덤프 완료: 0x387efe4000_4096.bin 메모리 덤프 완료: 0xe634a9f000_4096.bin ...
덤프시킨 여러 bin 파일들을 ./dumped 폴더에 놓고 컴파일해서 실행시키면
문제 환경을 재구성시킬 수 있음.
- prob_self.c
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <string.h> #include <sys/mman.h> #include <errno.h> #include <stdint.h> #define DUMP_DIR "./dumped" int mmap_rwx_from_dumped() { DIR *dir = opendir(DUMP_DIR); if (!dir) { perror("opendir"); return 1; } struct dirent *entry; char path[1024]; while ((entry = readdir(dir)) != NULL) { if (entry->d_type != DT_REG) continue; // skip non-regular files // 파일명 파싱: 0x주소_크기.bin uint64_t base_addr = 0; size_t size = 0; if (sscanf(entry->d_name, "0x%lx_%lu.bin", &base_addr, &size) != 2) { fprintf(stderr, "파일명 파싱 실패: %s\n", entry->d_name); continue; } snprintf(path, sizeof(path), "%s/%s", DUMP_DIR, entry->d_name); // 파일 열기 int fd = open(path, O_RDONLY); if (fd < 0) { perror("open"); continue; } // mmap void *mapped = mmap((void *)base_addr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_FIXED, fd, 0); if (mapped == MAP_FAILED) { fprintf(stderr, "mmap 실패: %s - %s\n", entry->d_name, strerror(errno)); close(fd); continue; } printf("매핑 성공: %s -> %p (%lu bytes)\n", entry->d_name, mapped, size); close(fd); } closedir(dir); return 0; } int readbuf(uint64_t addr, void* output, size_t size) { memcpy((void*)output, addr, size); return 0; } uint64_t read64(uint64_t what) { uint64_t value = 0; readbuf(what, &value, sizeof(value)); return value; } int main(int argc, char *argv[], char *envp[]) { mmap_rwx_from_dumped(); uint64_t rbx = 0x0F84BC1F88E8; //loop_21 char *input = argv[1]; printf("argv[1]: %s\n", input); int rsi = 0; while (1) { if(input[rsi] == '\n') goto loc_5F; if(input[rsi] == '\0') goto loc_5F; if(input[rsi] > 0x7E) goto loc_6E; if(input[rsi] < 0x20) goto loc_6E; input[rsi] -= 0x20; uint64_t rdx = rbx + input[rsi]*8; rbx = read64(rdx) ^ rdx; uint64_t r8 = 0x0B216CB3C48C1E693; rbx *= r8; r8 = 0x0C200C6D3267C529D; rbx += r8; rsi+=1; } loc_5F: printf("rbx: 0x%lx\n", rbx); if(rbx == 0x7038FC00BE0) { puts("yes\n"); return 0; } loc_6E: puts("no\n"); return 0; }
- Result
seo@ubuntu:~/study/LACTF2024/technically-correct$ gcc -o prob_self prob_self.c ... seo@ubuntu:~/study/LACTF2024/technically-correct$ ./prob_self AA ... 매핑 성공: 0x1b5cc2b1000_4096.bin -> 0x1b5cc2b1000 (4096 bytes) argv[1]: AA rbx: 0xe58854d6000 no
solve.c
덤프된 메모리 구간에서 특정 암호화된 패턴을 복호화해 flag를 찾음.
./dumped
폴더에 저장된0x주소_크기.bin
파일들을 원래의 주소로RWX
권한으로mmap()
함
역연산 파트: prob_self.c / solve.c
- prob_self.c
... rbx = read64(rdx) ^ rdx; uint64_t r8 = 0x0B216CB3C48C1E693; rbx *= r8; r8 = 0x0C200C6D3267C529D; rbx += r8;
- solve.c
- rdx 값을 찾아내기 위해 맵핑된 rwx 영역들 중에 8바이트를 읽어 상응하는 rdx 값을 찾아냄.
uint64_t rbx = 0x7038FC00BE0; rbx -= 0x0C200C6D3267C529D rbx *= modinv_newton(0x0B216CB3C48C1E693); uint64_t rdx = find_matching_rdx_in_mapped_regions(rbx); ...
- solve.c
이후 rbx 값을 key 값 범위 0~0x5e까지 브루트포싱하여find_matching_rdx_in_mapped_regions
에서 상응하는 rdx 값을 찾지 못할시 계속 key 값을 늘림.
찾을 경우, +0x20을 더해 문자 하나씩 붙임.
이후에, 초기 rbx값이 0x0F84BC1F88E8라면, 복호화가 끝났기에 success 구문으로 이동.
uint64_t saved_rdx = rdx; int key = 0; while(key < 0x5e) { rbx = saved_rdx - key*8; // printf("rbx_a: 0x%lx\n", rbx); if(rbx == 0x0F84BC1F88E8) goto success; rbx -= 0x0C200C6D3267C529D; // printf("rbx_b: 0x%lx\n", rbx); rbx *= modinv_newton(0x0B216CB3C48C1E693); // printf("rbx_c: 0x%lx\n", rbx); rdx = find_matching_rdx_in_mapped_regions(rbx); if(rdx == 0) { key++; continue; } success: // printf("for rdx: 0x%lx\n", rdx); saved_rdx = rdx; char ch = (char)key+0x20; // printf("key: %c\n", ch); char temp[2]; temp[0] = ch; temp[1] = '\0'; strcat(flag, temp); key = 0; }
solve.c
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <string.h> #include <sys/mman.h> #include <errno.h> #include <stdint.h> #define DUMP_DIR "./dumped" int mmap_rwx_from_dumped() { DIR *dir = opendir(DUMP_DIR); if (!dir) { perror("opendir"); return 1; } struct dirent *entry; char path[1024]; while ((entry = readdir(dir)) != NULL) { if (entry->d_type != DT_REG) continue; // skip non-regular files // 파일명 파싱: 0x주소_크기.bin uint64_t base_addr = 0; size_t size = 0; if (sscanf(entry->d_name, "0x%lx_%lu.bin", &base_addr, &size) != 2) { fprintf(stderr, "파일명 파싱 실패: %s\n", entry->d_name); continue; } snprintf(path, sizeof(path), "%s/%s", DUMP_DIR, entry->d_name); // 파일 열기 int fd = open(path, O_RDONLY); if (fd < 0) { perror("open"); continue; } // mmap void *mapped = mmap((void *)base_addr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_FIXED, fd, 0); if (mapped == MAP_FAILED) { fprintf(stderr, "mmap 실패: %s - %s\n", entry->d_name, strerror(errno)); close(fd); continue; } // printf("매핑 성공: %s -> %p (%lu bytes)\n", entry->d_name, mapped, size); close(fd); } closedir(dir); return 0; } int readbuf(uint64_t addr, void* output, size_t size) { memcpy((void*)output, addr, size); return 0; } uint64_t read64(uint64_t what) { uint64_t value = 0; readbuf(what, &value, sizeof(value)); return value; } uint64_t find_matching_rdx_in_mapped_regions(uint64_t target_xor_result) { uint64_t saved_addr = 0; FILE *maps = fopen("/proc/self/maps", "r"); if (!maps) { perror("fopen /proc/self/maps"); return 1; } char line[256]; while (fgets(line, sizeof(line), maps)) { unsigned long start, end; char perms[5]; // maps 파일 한 줄에서 시작주소-끝주소 권한 부분만 파싱 if (sscanf(line, "%lx-%lx %4s", &start, &end, perms) != 3) continue; // RWX 권한을 가진 구간인지 확인 if (perms[0] != 'r' || perms[1] != 'w' || perms[2] != 'x') continue; // 구간을 8바이트 단위로 순회 for (uint64_t addr = start; addr + 8 <= end; addr += 8) { uint64_t val = read64(addr); // printf("val: 0x%lx\n", val); if ((val ^ addr) == target_xor_result) { // printf("찾음! rdx = 0x%016lx (read64=0x%016lx)\n", // addr, val); saved_addr = addr; } } // printf("start: 0x%lx, end: 0x%lx\n", start, end); } fclose(maps); return saved_addr; } // 1) 뉴턴 방법으로 모듈러 역원 구하기 uint64_t modinv_newton(uint64_t a) { uint64_t x = 1; // a mod 2 = 1 이므로 역원도 1 for (int i = 0; i < 6; i++) { x = x * (2 - a * x); // x_{n+1} = x_n * (2 - a*x_n) } return x; } void print_reverse(const char* str) { int len = strlen(str); for (int i = len - 1; i >= 0; i--) { putchar(str[i]); } putchar('\n'); // 줄 바꿈 } int main(int argc, char *argv[], char *envp[]) { mmap_rwx_from_dumped(); char flag[128]; memset(flag, 0, 128); uint64_t rbx = 0x7038FC00BE0; rbx -= 0x0C200C6D3267C529D; // printf("rbx2: 0x%lx\n", rbx); rbx *= modinv_newton(0x0B216CB3C48C1E693); // printf("rbx: 0x%lx\n", rbx); uint64_t rdx = find_matching_rdx_in_mapped_regions(rbx); // printf("rdx: 0x%lx\n", rdx); uint64_t saved_rdx = rdx; int key = 0; while(key < 0x5e) { rbx = saved_rdx - key*8; // printf("rbx_a: 0x%lx\n", rbx); if(rbx == 0x0F84BC1F88E8) goto success; rbx -= 0x0C200C6D3267C529D; // printf("rbx_b: 0x%lx\n", rbx); rbx *= modinv_newton(0x0B216CB3C48C1E693); // printf("rbx_c: 0x%lx\n", rbx); rdx = find_matching_rdx_in_mapped_regions(rbx); if(rdx == 0) { key++; continue; } success: // printf("for rdx: 0x%lx\n", rdx); saved_rdx = rdx; char ch = (char)key+0x20; // printf("key: %c\n", ch); char temp[2]; temp[0] = ch; temp[1] = '\0'; strcat(flag, temp); key = 0; } printf("flag: "); print_reverse(flag); printf("\n"); return 0; }
Result
seo@ubuntu:~/study/LACTF2024/technically-correct$ ./solve flag: lactf{i_l0v3_l1nux_elf_p4rs1ng}
seo@ubuntu:~/study/LACTF2024/technically-correct$ ./technically_correct lactf{i_l0v3_l1nux_elf_p4rs1ng} yes