Skip to content

Latest commit

 

History

History
338 lines (268 loc) · 21.4 KB

File metadata and controls

338 lines (268 loc) · 21.4 KB

언어: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

← 문서 색인 · ← NeverC 프로젝트

NeverC DynCode 컴파일러

C 소스를 위치 독립·제로 재배치·제로 데이터 섹션 플랫 바이너리 dyncode로 직접 컴파일합니다.

가이드


핵심 목표

  1. 일반 C만 작성 — dyncode 전용 트릭이 필요 없습니다.
  2. 완전 자동 파이프라인 — static int counter = 0, const char s[] = "...", 재귀, write/exit/read/..., 큰 상수 배열 등은 사용자 코드 수정 없이 내부에서 처리됩니다.
  3. 외부 의존성 제로 — 출력 .bin은 dyld·libSystem·데이터 섹션을 참조하지 않는 순수 명령 스트림입니다.
  4. CLI는 TableGen 정의 — 각 -fdyncode-*는 neverc/include/neverc/Invoke/Options.td.h에 등록됩니다(하드코딩 문자열 매칭 아님). 오타 시 did-you-mean, --help에 모든 옵션 표시.
  5. 출력 수준 제약 검증 가능 — -fdyncode-bad-bytes= / -fdyncode-bad-byte-profile=는 post-extract 후 최종 .bin을 스캔하며, 금지 바이트 적중 시 오프셋·바이트·컨텍스트를 보고하고 출력을 거부합니다.
  6. 크로스 플랫폼 단일 파이프라인 — TargetDesc 테이블로 구동. 동일 C 소스로 macOS / Linux / Android / Windows dyncode 생성. 새 플랫폼은 pass 5벌 복제 대신 테이블 한 행 + 추출기 하나 추가.

지원 대상

Triple 형식 사용자 모드 syscall Ring-0 리졸버 상태
arm64-apple-macos* Mach-O svc #0x80 (Darwin BSD) DarwinXNUKextShim 네이티브 loader 왕복 + 커널 리졸버 지원
x86_64-apple-macos* Mach-O syscall (BSD class mask 0x2000000) DarwinXNUKextShim 컴파일·추출 통과; x86_64 __text reloc 없음
aarch64-linux-gnu ELF svc #0 (x8 = nr) LinuxKallsymsShim 컴파일·추출·커널 리졸버 통과
x86_64-linux-gnu ELF syscall (rax = nr) LinuxKallsymsShim 컴파일·추출·커널 리졸버 통과
aarch64-linux-android* ELF Linux arm64와 동일 LinuxKallsymsShim (GKI) 컴파일·추출 통과
x86_64-linux-android* ELF Linux x86_64와 동일 LinuxKallsymsShim (GKI) 컴파일·추출 통과
aarch64-pc-windows-msvc PE/COFF PEB 워크 (ldr xN, [x18, #0x60]) WindowsKernelResolverShim 사용자 모드 PEB 읽기 바이트 32 40 f9 검증; ring-0는 loader 리졸버
x86_64-pc-windows-msvc PE/COFF PEB 모듈 워크 + PE export 테이블 조회 WindowsKernelResolverShim 사용자 모드는 IR 수준 PEB 워크; ring-0는 PEB 재사용 안 함

8개 (OS, arch) triple은 동일 pass 집합으로 구동됩니다. 차이는 TargetDesc.cpp 테이블 항목과 세 추출기 아키텍처 분기에 격리됩니다. 새 플랫폼 = 테이블 한 행 + 각 추출기 case 하나. ExecutionLevel은 직교: User는 사용자 모드 syscall / PEB 파이프라인, Kernel은 둘 다 끄고 KernelImportPass로 extern 호출을 리졸버 shim으로 재작성. kernel-mode-dyncode.md 참고.


빠른 시작

# Always pass -target — output triple is independent of the compiler host.

# 1) Pure computation dyncode — no system calls
neverc -fdyncode -target arm64-apple-macos add.c -o add.bin

# 2) Darwin hello world — write/exit → svc #0x80
neverc -fdyncode -target arm64-apple-macos -mdyncode-syscall hello.c -o hello.bin

# 3) Linux arm64: svc #0 + x8=nr
neverc -fdyncode -target aarch64-linux-gnu -mdyncode-syscall \
       hello.c -o hello_linux_arm64.bin

# 4) Linux x86_64: syscall + rax=nr
neverc -fdyncode -target x86_64-linux-gnu -mdyncode-syscall \
       hello.c -o hello_linux_x64.bin

# 5) Windows x86_64 (PEB walk for API calls)
neverc -fdyncode -target x86_64-pc-windows-msvc \
       -mdyncode-win-peb-import win.c -o win.bin

# 6) Custom entry symbol
neverc -fdyncode -target arm64-apple-macos -fdyncode-entry=dyncode_main kernel.c -o k.bin

# 7) Keep intermediate object for audit (otool / llvm-objdump / dumpbin)
neverc -fdyncode -target arm64-apple-macos -fdyncode-keep-obj=/tmp/dump.obj x.c -o x.bin

# 8) Reject forbidden bytes in final .bin
neverc -fdyncode -target arm64-apple-macos -fdyncode-bad-bytes=00,0a,0d x.c -o x.bin

# 9) Built-in bad-byte profile (same as forbidding 00/0a/0d)
neverc -fdyncode -target arm64-apple-macos -fdyncode-bad-byte-profile=http-newline x.c -o x.bin

# 10) Run on macOS (platform-specific loader)
./loader_arm64_macos add.bin 3 4   # exit code = 7

# 11) Verbose extractor summary
neverc -v -fdyncode -target arm64-apple-macos fib.c -o fib.bin
#   dyncode-extractor: wrote 64 bytes to 'fib.bin'
#   dyncode-extractor: target   = arm64-apple-macos (Mach-O)
#   dyncode-extractor: entry symbol = _main
#   dyncode-extractor: patched 1 BRANCH26, 0 PAGE21, 0 PAGEOFF12 intra-section reloc(s)

CLI 옵션(모두 Options.td.h에 정의)

옵션 설명
-fdyncode dyncode 컴파일 모드 활성화.
-fno-dyncode 이전 -fdyncode 취소.
-fdyncode-all-blr 공격적 모드: 모듈 내 직접 호출을 blr xN / call *rax로 간접화해 상대 분기 reloc 제거. 일반 사용 불필요.
-mdyncode-syscall syscall stub 명시 활성화(Darwin/Linux/Android에서 -fdyncode 시 기본). 의도·스크립트 호환용.
-mdyncode-libsystem -mdyncode-syscall의 Darwin 레거시 별칭.
-mdyncode-win-peb-import Windows PEB 임포트 명시 활성화(-fdyncode + Windows triple 시 기본).
-fdyncode-keep-obj=<path> 중간 객체 파일을 <path>에 복사해 네이티브 디스어셈블러로 감사.
-fdyncode-entry=<name> 기본 진입 심볼 덮어쓰기. main / _main / dyncode_entry / _dyncode_entry 허용.
-fdyncode-bad-bytes=<hex-list> 금지 바이트 쉼표 목록(예 00,0a,0d). post-extract 후 최종 .bin 스캔; 적중 시 실패·파일 미기록.
-fdyncode-bad-byte-profile=<name> 내장 금지 바이트 프로필: null, c-string, http-newline, line, whitespace, ascii-control. -fdyncode-bad-bytes=와 병용.
-fdyncode-obfuscate=<spec> Plugin API로 등록된 IR 수준 플러그인 훅으로 전달. 플러그인 미로드 시 no-op. ir-pass-design.md §9 — Obfuscation Interposes.
-fdyncode-mir-obfuscate=<spec> MIR 수준 난독화 훅(RunBeforePreEmit / RunAfterPreEmit)으로 전달. 미설정 시 -fdyncode-obfuscate=로 폴백. mir-pass-design.md §3 — User Obfuscation Interposes.

아키텍처 개요

파이프라인은 대상 독립 IR pass + 대상별 추출기로 나뉩니다:

flowchart TD
    Driver["neverc -fdyncode · OptTable + Options.td.h"]
    Frontend["C23 Frontend · PIC default"]
    Driver -->|describeTriple| Frontend
    Frontend -->|LLVM IR| ZRP

    subgraph IR["Target-Independent IR Passes"]
        direction TB
        ZRP["① ZeroRelocPass — Prep\ninternal + always_inline\nreject ctors / thread_local / extern_weak"]
        IBP["② IndirectBrPass\ncomputed-goto → switch"]
        SSP["③ SyscallStubPass\nlibc → svc #0x80 / svc #0 / syscall"]
        WPP["④ WinPEBImportPass\nextern Win32 API → PEB-walk thunk"]
        MIP["⑤ MemIntrinPass\nmemcpy/memset/str* → byte-loop"]
        CRP["⑥ CompilerRtPass\ni128 div/mod → inline long-division"]
        D2T1["⑦ Data2TextPass — Phase 1\nconst GV → stack stores"]
        ZRP --> IBP --> SSP --> WPP --> MIP --> CRP --> D2T1
    end

    Backend["AArch64 / X86 Backend\nSROA · InstCombine · AlwaysInliner · SLP"]
    D2T1 --> Backend

    Backend --> D2T2
    subgraph Post["Post-Backend IR"]
        direction TB
        D2T2["⑧ Data2TextPass — Phase 2\nvector const split"]
        ZRS["⑨ ZeroRelocPass — Stackify\nglobals → entry alloca"]
        ABP["⑩ AllBlrPass (optional)\ndirect call → indirect call"]
        D2T2 --> ZRS --> ABP
    end

    Codegen["Codegen · IR → MIR → Register Allocation"]
    ABP --> Codegen

    Codegen --> MH1
    subgraph MIR["MIR Layer"]
        direction TB
        MH1["⑪ RunBeforePreEmit interpose"]
        MIRP["⑫ DynCodeMIRPrepPass\nstrip CFI / EH_LABEL / XRay / StackMap"]
        MH2["⑬ RunAfterPreEmit interpose\ninstruction-level obfuscation entry"]
        MH1 --> MIRP --> MH2
    end

    MH2 -->|"Mach-O / ELF / COFF .o"| Extractor

    subgraph Extract["Extractor Layer"]
        Extractor["DynCodeExtractor\nMachO · ELF · COFF\npatch intra-.text relocs\nreject external reloc / data section\nbad-byte audit"]
    end

    Extractor --> Output(["flat .bin dyncode"])
Loading

테이블 기반 플랫폼 차이

neverc/include/neverc/DynCode/Pipeline/TargetDesc.h는 각 (OS, arch) 조합의 차이를 설명하는 TargetDesc 구조체를 정의합니다:

  • TextSectionName: Mach-O __text / ELF .text / COFF .text
  • SyscallABI: enum value (DarwinSvc80 / LinuxSvc0 / LinuxSyscall / WindowsPEB / None)
  • AsmTemplate: svc #0x80 / svc #0 / syscall
  • SyscallNumberReg: x16 / x8 / rax
  • SyscallRetReg: x0 / rax
  • ArgRegs: ordered list of platform ABI argument registers + count
  • TCBReadAsm / TCBReadConstraint: inline-asm single-instruction template for reading TEB/PEB pointer (Windows x86_64 = movq %gs:0x60, $0, Windows arm64 = ldr $0, [x18, #0x60]). WinPEBImportPass reads directly from the table.
  • DriverInjectFlags: platform-specific driver flags as a null-terminated static array (x86_64 Unix gets -fpic -mcmodel=small; Windows gets -mno-stack-arg-probe / /GS-). perTargetInjectFlags reads from the table.

SyscallStubPass와 WinPEBImportPass는 TargetDesc 필드에서 InlineAsm을 생성합니다. 백엔드는 TableGen 정의 명령 패턴을 사용합니다. 새 대상 = describeTriple에 한 행, 각 추출기 switch에 case 하나.

추출기 계층

형식 구현 패치 가능한 섹션 내 reloc
Mach-O MachOExtractor.cpp arm64: ARM64_RELOC_BRANCH26 / PAGE21 / PAGEOFF12; x86_64: X86_64_RELOC_SIGNED / SIGNED_1/2/4 / BRANCH (intra-__text pcrel32); UNSIGNED / GOT_LOAD / GOT / SUBTRACTOR / TLV rejected
ELF ELFExtractor.cpp arm64: R_AARCH64_CALL26 / JUMP26 / ADR_PREL_PG_HI21(_NC) / ADD_ABS_LO12_NC / LDST{8,16,32,64,128}_ABS_LO12_NC / PREL32; x86_64: R_X86_64_PC32 / PLT32 (GOTPCREL rejected)
COFF COFFExtractor.cpp arm64: IMAGE_REL_ARM64_BRANCH26 / PAGEBASE_REL21 / PAGEOFFSET_12A / PAGEOFFSET_12L / REL32; x86_64: IMAGE_REL_AMD64_REL32 / REL32_[1-5]

기타 유형·섹션 간 reloc은 힌트와 함께 하드 실패(libc 추측 → syscall stub / _Complex → 수동 struct / 리터럴 풀 백엔드 폴백 등).


사용자 코드 기능 매트릭스

시나리오 사용자 코드 지원 메커니즘
정수·비트 연산 int f(int a) { return a*3+1; } 예 순수 명령 스트림
재귀·루프 int fib(int n) { ... } 예 static + always_inline
switch / case switch (op) { case 0: ... } 예 드라이버가 -fno-jump-tables 주입
구조체 값 전달 struct Vec3 v = {...}; dot(v); 예 스택화 + always_inline
부동소수점 double y = x * 3.14; 예 Data2Text가 ConstantFP를 volatile 로드 비트 패턴으로
작은 상수 배열 const int t[4] = {1,2,3,4}; 예 Data2Text 스택화
큰 상수 배열 (256B+) const unsigned char tbl[256] = {...} 예 Data2Text, 크기 제한 없음
문자열 리터럴 const char s[] = "hi\n"; 예 Data2Text 스택화
memcpy / memset / memmove / memcmp memcpy(dst, src, n); 예 MemIntrinPass 바이트 루프 래퍼
strlen / strcpy / strcmp 등 strlen(buf); 예 MemIntrinPass 바이트 루프 래퍼
__int128 나눗셈·나머지 u128 q = a / b; 예 CompilerRtPass 인라인 장나눗셈
_Atomic / __atomic_* / __sync_* __atomic_fetch_add(&c, 1, ...) 예 인라인 LDXR/STXR (arm64) / LOCK (x86_64)
__builtin_* 계열 __builtin_popcount(x) 예 백엔드 단일 명령 선택
VLA / 가변 배열 / 복합 리터럴 일반 C99/C11 예 -fno-jump-tables + Data2Text
변경 가능 전역 static int counter = 0; 예 ZeroReloc 스택화
libc write/exit write(1, s, 3); 예(-mdyncode-syscall) Syscall 래퍼
POSIX include #include <unistd.h> 예(dyncode 모드 shim 자동 전환) 드라이버가 __NEVERC_DYNCODE__ 주입
Win32 API WriteFile(h, buf, n, &w, 0); 예(-mdyncode-win-peb-import) PEB 워크 thunk
Windows SDK include #include <windows.h> 예(dyncode 모드 shim) 경량 shim 헤더
사용자 정의 진입명 int dyncode_main(...) 예(-fdyncode-entry=...) 드라이버 통과
전역 생성자 __attribute__((constructor)) 아니오 실행 시 트리거할 런타임 없음
TLS / thread_local thread_local int x; static으로 자동 강등 ZeroRelocPass.Prep 조용히 강등
C++ / ObjC — 아니오 프로젝트는 C만

디렉터리 구조

neverc/
├── include/neverc/Invoke/Options.td.h           # -fdyncode-* TableGen definitions
├── include/neverc/DynCode/                  # Headers (organized by subsystem)
│   ├── Pipeline/                              # Pipeline / driver integration
│   │   ├── Pipeline.h                         # IR + MIR interpose registration
│   │   ├── DriverIntegration.h
│   │   ├── TargetDesc.h                       # Platform table / descriptors
│   │   ├── DynCodeOptions.h                 # Cross-subsystem config
│   │   ├── Diagnostics.h                      # Cross-subsystem diagnostics
│   │   └── SymbolNames.h                      # Cross-subsystem symbol utilities
│   ├── Extractor/
│   │   └── DynCodeExtractor.h
│   ├── IR/                                    # IR-level passes and ABIs
│   │   ├── ZeroRelocPass.h / ZeroRelocABI.h
│   │   ├── Data2TextPass.h / Data2TextABI.h
│   │   ├── AllBlrPass.h / IndirectBrPass.h
│   │   ├── MemIntrinPass.h                    # memcpy/memset/str* inlining
│   │   ├── StringRuntimePass.h / StringRuntimeABI.h
│   │   ├── HeapArenaPass.h                    # malloc/free → arena + OS fallback
│   │   ├── MmapABI.h                          # 공유 mmap 상수 (prot/flags)
│   │   ├── DynCodeIRHelpers.h               # 공통 IR 유틸리티 (getSizeType 등)
│   │   ├── ExternRewriter.h                   # Extern function rewrite utilities
│   │   └── CompilerRtPass.h                   # __int128 division inline
│   ├── MIR/
│   │   └── MIRPrepPass.h                      # Catch-all MachineFunctionPass
│   ├── Import/                                # User-mode + kernel-mode import resolution
│   │   ├── SyscallStub.h / SyscallTables.h
│   │   ├── WinPEBImport.h / WinImportTables.h
│   │   ├── KernelImportPass.h / KernelImportABI.h
│   │   └── PtrCacheHelpers.h                  # Shared address cache encryption helpers
│   └── Tables/                                # User-extensible .def tables
├── lib/DynCode/                             # Implementation (mirrors header structure)
│   ├── Pipeline/ Extractor/ IR/ MIR/ Import/
└── lib/Invoke/Core/Driver.cpp

tests/neverc/                                   # Tests (GTest)
├── DynCodeTests.cpp                         # Core dyncode round-trip tests
├── DynCodeStressTests.cpp                   # Stress tests (VLA, __sync_*, __int128, etc.)
├── DynCodeCrossTargetTests.cpp              # Cross-target compile-only smoke tests
├── dyncode/
│   ├── loader_arm64_macos.c / loader_linux.c / loader_windows.c
│   └── test_dyncode_*.c

docs/dyncode-compiler/
├── README.md                                  ← 영어판
├── README.ko.md                               ← 한국어
├── arm64-assembly-tutorial/README.md
├── cross-platform-architecture/README.md
├── ir-pass-design/README.md
├── kernel-mode-dyncode/README.md
├── mir-pass-design/README.md
├── pipeline-and-pic/README.md
├── platform-extension-guide/README.md
├── progress/README.md
└── roadmap/README.md

전제 조건(크로스 플랫폼)

  1. dyncode 로드 주소는 4KB 정렬 필수 — mmap / VirtualAlloc의 자연스러운 동작; 모든 loader 코드가 이미 준수.
  2. 호출 규약은 대상 OS 네이티브 ABI를 따릅니다:
    • Darwin / Linux / Android: System V AMD64 or AAPCS64
    • Windows: Win64 (rcx/rdx/r8/r9)
  3. i-cache flush(arm64) / FlushInstructionCache(Windows)는 loader 책임.

난독화 및 플러그인 확장

dyncode 파이프라인 자체는 "코드가 올바르게 실행됨"만 보장합니다. 난독화·다형성·단계별 인코더 등 전략 계층 기능은 의도적으로 내장되지 않으며, Plugin API를 통해 아웃오브트리 플러그인으로 제공됩니다.

파이프라인은 3계층에 걸쳐 11개 훅 포인트를 노출하며, 모두 C Plugin API(NEVERC_INTERPOSE_SC_*)로 접근할 수 있습니다:

IR 계층(6 훅):

  • NEVERC_INTERPOSE_SC_BEFORE_PREP — dyncode pass 이전
  • NEVERC_INTERPOSE_SC_AFTER_PREP — 링크 속성 통일(internal + always_inline)
  • NEVERC_INTERPOSE_SC_BEFORE_INLINING — AlwaysInliner 전 마지막 기회
  • NEVERC_INTERPOSE_SC_AFTER_INLINING — IR이 하나의 큰 함수로 압축된 후
  • NEVERC_INTERPOSE_SC_AFTER_STACKIFY — 최종 IR 형태, 다음은 코드 생성
  • NEVERC_INTERPOSE_SC_AFTER_FINAL_IR — AllBlrPass 후, 진짜 마지막 IR 훅

MIR 계층(3 훅):

  • NEVERC_INTERPOSE_SC_BEFORE_PREEMIT — 레지스터 할당됨, CFI/EH 의사 명령 남음
  • NEVERC_INTERPOSE_SC_AFTER_PREEMIT — 내장 MIRPrepPass가 의사 명령 제거, AsmPrinter가 볼 바이트에 가장 가까움; 명령 수준 난독화·레지스터 이름 변경에 적합
  • NEVERC_INTERPOSE_SC_AFTER_FINAL_MIR — LLVM addPreEmitPass2() 후, AsmPrinter 직전의 진짜 마지막 MIR 훅

바이트 스트림 계층(2 훅):

  • NEVERC_INTERPOSE_SC_POST_EXTRACT — 추출기가 .text 내 reloc 패치·데이터 섹션 감사 완료 후 .bin 기록 전. 전체 페이로드 암호화·정크 바이트·커스텀 헤더용.
  • NEVERC_INTERPOSE_SC_POST_FINALIZE — 모든 finalize 후; NeverC는 더 이상 감사하지 않음.

전체 훅 목록, pass 등록, 코드 예제는 Plugin API 문서를 참조하세요.


현재 제한

  • 8가지 (OS, arch) 조합 지원(위 표 참고). RISC-V·PowerPC·32비트 x86·빅엔디안 ARM 등은 describeTriple()에서 거부되며 지원 목록 힌트 제공. 각 행에 독립 User / Kernel 컨텍스트, 총 16 (OS, arch, level) 변형.
  • Windows PEB 워크는 멀티 DLL 디스패치로 완전 구현. __neverc_win_resolve는 (dll_hash, api_hash) 쌍 수용. 화이트리스트: kernel32.dll(~125 API), ntdll.dll(~26), user32.dll(~13), ws2_32.dll(~23), advapi32.dll(~16), shell32.dll(~6). API 추가 = Tables/Win32Apis.def 한 행 + lib/Headers/windows.h 선언 하나.
  • 외부 함수 화이트리스트는 Darwin BSD / Linux / Android 일반 syscall(~80+)과 Win32 API(~190)만. stdio 등 무거운 런타임 미포함 — dyncode에 stdio 상태 머신 전체 불가.
  • C++ / ObjC / CUDA 미지원 — NeverC는 설계상 C만.