├── .clang-format ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── analysis.py ├── builder ├── __init__.py ├── cctarget.py └── exceptions.py ├── packer1 ├── build.py ├── example.c └── loader.c ├── packer2 ├── build.py ├── example.c └── loader.c ├── packer3 ├── build.py ├── example.c └── loader.c ├── packer4 ├── build.py ├── compression_packer.c ├── compression_packer.h ├── example.c ├── loader.c └── utils.py ├── packer5 ├── build.py ├── example.c ├── loader.c ├── png_decode.c ├── png_decode.h ├── rsrc.rc └── utils.py ├── packer6 ├── anti_debug.c ├── anti_debug.h ├── assembly_utils.c ├── assembly_utils.h ├── build.py ├── example.c ├── load_pe.c ├── load_pe.h └── loader.c ├── packer7 ├── build.py ├── example.c ├── junkcode.h ├── load_pe.c ├── load_pe.h └── loader.c ├── packer8 ├── README.md ├── build.py ├── example.c ├── load_pe.c ├── load_pe.h ├── loader.c └── obfuscator │ ├── .clang-format │ ├── CMakeLists.txt │ └── bcf.cpp ├── packer9 ├── packer.py ├── rsrc.rc ├── sample.c └── verify.py ├── requirements.txt └── setup.cfg /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | ColumnLimit: 120 4 | IndentPPDirectives: AfterHash 5 | AllowShortFunctionsOnASingleLine: Empty 6 | NamespaceIndentation: All 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | __pycache__ 3 | *.pyc 4 | *.exe 5 | *.o 6 | venv 7 | typings 8 | .cache 9 | *.png 10 | 11 | .clangd 12 | .vscode 13 | .idea 14 | 15 | *.dll 16 | *.exe 17 | *.o 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnnewb/learning-packer/2b8745c9ca6641215e007926f82cc6e756c5841a/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 weak_ptr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learning-packer 2 | 3 | ## 介绍 4 | 5 | 各案例的主题: 6 | 7 | 1. 基本的 PE32 程序加载。 8 | 2. 从内存加载 PE32 程序。 9 | 3. 从内存加载不支持 ASLR 的 PE32 程序。 10 | 4. 从内存加载 zlib 压缩过的 PE32 程序。 11 | 5. 从资源加载编码为 PNG 灰度图的 PE32 程序。 12 | 6. 几种反调试技术。 13 | 7. 关于反反汇编(花指令)技术。 14 | 8. 基于LLVM Pass 的简单代码混淆技术。 15 | 16 | 构造输出结果的 python 脚本也可以用 c++ 代码结合 LIEF 库,接口和逻辑都一样。 17 | 18 | 出于保持简单的考虑,另外 LIEF 也没提供 MinGW 版本的库,所以统一为使用 Python 版本的 LIEF。 19 | 20 | *2021年10月22日* 为止,lief 没有提供类型存根,造成 VSCode 对 lief 库的函数和模块无法提供补全提示和即时的代码检查。 为了 VSCode 正确提供 LIEF 库的智能提示,可以使用 mypy 提供的 `stubgen` 脚本生成 `pyi` 存根。 21 | 22 | 这是可选的,提供存根可以有效提高在 VSCode 里编写代码的体验。 23 | 24 | ```shell 25 | ./venv/Scripts/Activate.ps1 26 | pip install mypy 27 | stubgen -p lief -o typings 28 | ``` 29 | 30 | ## 开发环境 31 | 32 | 开发工具: 33 | 34 | - nasm 35 | - Python 3.8 36 | - MSYS2/mingw-w64-i686 37 | 38 | Python 第三方包: 39 | 40 | - lief 41 | - pypng 42 | 43 | ```shell 44 | pip install lief pypng 45 | ``` 46 | 47 | C 第三方库: 48 | 49 | - zlib 50 | - libpng 51 | 52 | ```shell 53 | # 在 MSYS2 命令行环境里执行 54 | pacman -Sy mingw-w64-i686-zlib mingw-w64-i686-libpng 55 | ``` 56 | 57 | 注意需要把 `nasm` 和 `[/path/to/your/msys64]/mingw32/bin` 加入 `PATH` 环境变量。 58 | 59 | ## 实验方式 60 | 61 | 准备好环境之后执行案例目录里的 build.py 脚本即可。 62 | 63 | ```shell 64 | # pacman -Sy mingw-w64-i686-zlib mingw-w64-i686-libpng 65 | # python38 -m venv venv 66 | # ./venv/Scripts/Activate.ps1 67 | # pip install lief pypng 68 | cd packer4 69 | python build.py 70 | ./packed.exe 71 | ``` 72 | 73 | ## LICENSE 74 | 75 | MIT License 76 | 77 | Copyright (c) 2021 weak_ptr [](mailto:weak_ptr@outlook.com) 78 | -------------------------------------------------------------------------------- /analysis.py: -------------------------------------------------------------------------------- 1 | import lief 2 | from lief import PE 3 | 4 | packed = PE.parse('packed.exe') 5 | loader = PE.parse('shifted-loader.exe') 6 | 7 | with open('packed-analysis.txt', 'w+', encoding='utf-8') as out: 8 | print('-----'*20, file=out) 9 | print('packed.exe', file=out) 10 | print('-----'*20, file=out) 11 | print(packed.header, file=out) 12 | print(packed.optional_header, file=out) 13 | 14 | for entry in packed.data_directories: 15 | print(entry, file=out) 16 | 17 | for s in packed.sections: 18 | print(s, file=out) 19 | 20 | with open('loader-analysis.txt', 'w+', encoding='utf-8') as out: 21 | print('-----'*20, file=out) 22 | print('shifted-loader.exe', file=out) 23 | print('-----'*20, file=out) 24 | print(loader.header, file=out) 25 | print(loader.optional_header, file=out) 26 | 27 | for entry in loader.data_directories: 28 | print(entry, file=out) 29 | 30 | for s in loader.sections: 31 | print(s, file=out) 32 | -------------------------------------------------------------------------------- /builder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnnewb/learning-packer/2b8745c9ca6641215e007926f82cc6e756c5841a/builder/__init__.py -------------------------------------------------------------------------------- /builder/cctarget.py: -------------------------------------------------------------------------------- 1 | from os import lstat, makedirs, path 2 | import pathlib 3 | import re 4 | from subprocess import PIPE, STDOUT, run 5 | from typing import Sequence, Set 6 | import locale 7 | 8 | from builder.exceptions import CompilationError, CompilerFeatureProbeFail, NoSuchFileOrDirectoryException 9 | from logging import Logger, debug, getLogger 10 | 11 | _CC_LOGGER: Logger = getLogger('CC') 12 | 13 | 14 | class _GccSrcDepProb: 15 | def __init__(self, src_path: str, dep_path: str) -> None: 16 | self.src_path = src_path 17 | self.dep_path = dep_path 18 | 19 | @property 20 | def need_reprobe(self): 21 | if not path.exists(self.dep_path): 22 | return True 23 | 24 | return lstat(self.src_path).st_mtime_ns >= lstat(self.dep_path).st_mtime_ns 25 | 26 | def probe(self): 27 | if self.need_reprobe: 28 | cmd = ' '.join(['gcc', self.src_path, '-c', '-MMD', '-MF', self.dep_path]) 29 | proc = run(cmd, shell=True, stdout=PIPE, stderr=STDOUT) 30 | if proc.returncode != 0: 31 | output = proc.stdout.decode(locale.getpreferredencoding()) 32 | raise CompilerFeatureProbeFail(f'source dependencies probe fail: {output}') 33 | 34 | dependencies = [] 35 | with open(self.dep_path, 'r', encoding=locale.getpreferredencoding()) as f: 36 | first = True 37 | for line in f: 38 | if first is True: 39 | _, paths = line.split(':', 1) 40 | else: 41 | paths = line 42 | 43 | valid_dependencies = filter(lambda s: bool(s), map(lambda s: s.strip(), paths.strip().split(' '))) 44 | for src_dep_path in valid_dependencies: 45 | if src_dep_path[-1] == '\\': 46 | src_dep_path = src_dep_path[:-1].strip() 47 | 48 | if src_dep_path: 49 | dependencies.append(src_dep_path) 50 | 51 | if line.endswith('\\'): 52 | continue 53 | 54 | return dependencies 55 | 56 | 57 | class _CCSource: 58 | def __init__(self, src_path, compiler, compile_flags, obj_path, dep_path): 59 | """C/C++ source file abstraction 60 | 61 | Args: 62 | src_path (str): source file absolute path 63 | compiler (str): compiler absolute path 64 | compile_flags (List[str]): compiler flags 65 | obj_path (str): object file absolute path 66 | dep_path (str): depend definition absolute path 67 | """ 68 | self._compiler = compiler 69 | self._src_path = src_path 70 | self._compile_flags = compile_flags 71 | self._obj_path = obj_path 72 | self._dep_path = dep_path 73 | # TODO: 应该根据compiler选择 74 | self._is_cpp = re.match(r'.*?\.(cc|cpp|cxx)', self._src_path) is not None 75 | self._dependencies_probe = _GccSrcDepProb(src_path, self._dep_path) 76 | 77 | @property 78 | def _obj_modified_at(self): 79 | return lstat(self._obj_path).st_mtime_ns 80 | 81 | @property 82 | def _src_modified_at(self): 83 | return lstat(self._src_path).st_mtime_ns 84 | 85 | @property 86 | def src_path(self): 87 | return self._src_path 88 | 89 | @property 90 | def obj_path(self): 91 | return self._obj_path 92 | 93 | @property 94 | def need_recompile(self): 95 | if not path.exists(self._obj_path): 96 | return True 97 | 98 | for depend in self._dependencies_probe.probe(): 99 | if lstat(depend).st_mtime_ns >= self._obj_modified_at: 100 | return True 101 | 102 | return False 103 | 104 | def compile(self): 105 | # TODO: -o 并不通用 106 | cmd = [self._compiler, self._src_path, *self._compile_flags, '-c', '-o', self._obj_path] 107 | _CC_LOGGER.debug('Run: ' + ' '.join(cmd)) 108 | proc = run(cmd, stdout=PIPE, stderr=STDOUT) 109 | if proc.returncode != 0: 110 | raise CompilationError( 111 | f'{self._src_path}: compile fail\n{proc.stdout.decode(locale.getpreferredencoding())}\n') 112 | 113 | 114 | class _CCBuilder: 115 | def __init__(self, sources: Sequence[_CCSource], compiler: str, linker: str, c_flags: Sequence[str], cxx_flags: Sequence[str], ld_flags: Sequence[str], output: str): 116 | self._compiler = compiler 117 | self._linker = linker 118 | self._c_flags = c_flags 119 | self._cxx_flags = cxx_flags 120 | self._ld_flags = ld_flags 121 | self._sources: Sequence[_CCSource] = sources 122 | self._output = output 123 | 124 | @property 125 | def need_rebuild(self): 126 | if not path.exists(self._output): 127 | return True 128 | 129 | return any(map(lambda src: src.need_recompile, self._sources)) 130 | 131 | def _compile(self): 132 | for src in self._sources: 133 | if not src.need_recompile: 134 | _CC_LOGGER.debug(f'Skip: {src._src_path}') 135 | continue 136 | _CC_LOGGER.info(f'Compiling: {src._src_path}') 137 | src.compile() 138 | 139 | def _link(self): 140 | _CC_LOGGER.info(f'Linking: {self._output}') 141 | obj_files = [src.obj_path for src in self._sources] 142 | # TODO: -o 并不通用 143 | cmd = [self._linker, *obj_files, *self._ld_flags, '-o', self._output] 144 | _CC_LOGGER.debug(' '.join(cmd)) 145 | proc = run(cmd, stdout=PIPE, stderr=STDOUT) 146 | if proc.returncode != 0: 147 | raise CompilationError(f'{self._output}: link fail\n{proc.stdout.decode(locale.getpreferredencoding())}\n') 148 | 149 | def build(self): 150 | _CC_LOGGER.info(f'Building: {self._output}') 151 | if self.need_rebuild: 152 | self._compile() 153 | self._link() 154 | 155 | 156 | class CCTarget: 157 | def __init__(self, name, compiler='gcc', linker='gcc', bin_dir='build/bin', obj_dir='build/obj', dep_dir='build/dep') -> None: 158 | self.name = name 159 | self._compiler: str = compiler 160 | self._linker: str = linker 161 | self._bin_dir: str = bin_dir 162 | self._obj_dir: str = obj_dir 163 | self._dep_dir: str = dep_dir 164 | 165 | self._sources: Set[str] = set() 166 | self._LD_FLAGS: Set[str] = set() 167 | self._C_FLAGS: Set[str] = set() 168 | self._CXX_FLAGS: Set[str] = set() 169 | 170 | @property 171 | def compiler(self): 172 | return self._compiler 173 | 174 | @compiler.setter 175 | def compiler(self, compiler): 176 | self._compiler = compiler 177 | 178 | @property 179 | def linker(self): 180 | return self._linker 181 | 182 | @linker.setter 183 | def linker(self, linker): 184 | self._linker = linker 185 | 186 | @property 187 | def bin_dir(self): 188 | return self._bin_dir 189 | 190 | @bin_dir.setter 191 | def bin_dir(self, binary_dir): 192 | self._bin_dir = binary_dir 193 | 194 | @property 195 | def obj_dir(self): 196 | return self._obj_dir 197 | 198 | @property 199 | def dep_dir(self): 200 | return self._dep_dir 201 | 202 | @property 203 | def c_flags(self): 204 | return self._C_FLAGS 205 | 206 | @property 207 | def cxx_flags(self): 208 | return self._CXX_FLAGS 209 | 210 | @property 211 | def ld_flags(self): 212 | return self._LD_FLAGS 213 | 214 | @property 215 | def target_binary_path(self): 216 | return path.join(self.bin_dir, self.name) 217 | 218 | def add_include_dir(self, *includes): 219 | for inc in includes: 220 | if path.exists(inc): 221 | self._C_FLAGS.add(f'-I{inc}') 222 | self._CXX_FLAGS.add(f'-I{inc}') 223 | else: 224 | raise NoSuchFileOrDirectoryException(f'{inc}: include dir not exists') 225 | 226 | def add_link_libraries(self, *libraries): 227 | for lib in libraries: 228 | self._LD_FLAGS.add(f'-l{lib}') 229 | 230 | def add_c_flags(self, *flags): 231 | for flag in flags: 232 | self._C_FLAGS.add(flag) 233 | 234 | def add_cxx_flags(self, *flags): 235 | for flag in flags: 236 | self._CXX_FLAGS.add(flag) 237 | 238 | def add_c_cxx_flags(self, *flags): 239 | for flag in flags: 240 | self._C_FLAGS.add(flag) 241 | self._CXX_FLAGS.add(flag) 242 | 243 | def add_ld_flags(self, *ld_flags): 244 | for flag in ld_flags: 245 | self._LD_FLAGS.add(flag) 246 | 247 | def add_sources(self, *sources): 248 | for src in sources: 249 | if not path.exists(src): 250 | raise NoSuchFileOrDirectoryException(f'{src}: source file not exists') 251 | self._sources.add(path.abspath(src)) 252 | 253 | def build(self): 254 | makedirs(self.bin_dir, exist_ok=True) 255 | makedirs(self.obj_dir, exist_ok=True) 256 | makedirs(self.dep_dir, exist_ok=True) 257 | sources = [ 258 | _CCSource( 259 | path.abspath(src), 260 | self.compiler, 261 | self.c_flags if src.endswith('.c') else self.cxx_flags, 262 | path.abspath(path.join(self.obj_dir, pathlib.Path(src).with_suffix('.obj').name)), 263 | path.abspath(path.join(self.dep_dir, pathlib.Path(src).with_suffix('.dep').name)) 264 | ) 265 | for src in self._sources 266 | ] 267 | bin = path.abspath(path.join(self.bin_dir, self.name)) 268 | builder = _CCBuilder(sources, self.compiler, self.linker, self.c_flags, self.cxx_flags, self.ld_flags, bin) 269 | builder.build() 270 | 271 | _CC_LOGGER.info(f'Artifact built: {bin}') 272 | -------------------------------------------------------------------------------- /builder/exceptions.py: -------------------------------------------------------------------------------- 1 | class NoSuchFileOrDirectoryException(Exception): 2 | pass 3 | 4 | 5 | class CompilationError(Exception): 6 | pass 7 | 8 | 9 | class CompilerFeatureProbeFail(Exception): 10 | pass 11 | -------------------------------------------------------------------------------- /packer1/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import lief 3 | from subprocess import STDOUT, CalledProcessError, check_output 4 | 5 | 6 | # %% 7 | # compile origin demo program 8 | try: 9 | check_output('gcc example.c -m32 -O2 -o example.exe', shell=True, stderr=STDOUT) 10 | except CalledProcessError as e: 11 | print(f'[!] demo program compilation failed, {e.stdout.decode()}') 12 | raise 13 | 14 | binary = lief.PE.parse('example.exe') 15 | print('[+] compile origin demo program success.') 16 | 17 | # %% 18 | # compile loader program 19 | compile_args = [ 20 | 'loader.c', 21 | '-m32', 22 | '-O2', 23 | '-Wall', 24 | '-o', 25 | 'loader.exe' 26 | ] 27 | 28 | try: 29 | check_output(' '.join(['gcc', *compile_args]), shell=True, stderr=STDOUT) 30 | print('[+] compile loader program success.') 31 | except CalledProcessError as e: 32 | print(f'[!] loader compilation failed, {e.stdout.decode()}') 33 | raise 34 | 35 | loader = lief.PE.parse('loader.exe') 36 | 37 | print('[+] compilation success. Now you can run loader program "./loader.exe ./example.exe"') -------------------------------------------------------------------------------- /packer1/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer1/loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | void *load_PE(char *PE_data); 8 | void fix_iat(char *, IMAGE_NT_HEADERS *); 9 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 10 | 11 | int main(int argc, char const *argv[]) { 12 | if (argc < 2) { 13 | printf("missing path argument\n"); 14 | return 1; 15 | } 16 | 17 | FILE *exe_file = fopen(argv[1], "rb"); 18 | if (!exe_file) { 19 | printf("error opening file\n"); 20 | return 1; 21 | } 22 | 23 | // Get file size : put pointer at the end 24 | fseek(exe_file, 0L, SEEK_END); 25 | // and read its position 26 | long int file_size = ftell(exe_file); 27 | // put the pointer back at the beginning 28 | fseek(exe_file, 0L, SEEK_SET); 29 | 30 | // allocate memory and read the whole file 31 | char *exe_file_data = malloc(file_size + 1); 32 | 33 | // read whole file 34 | size_t n_read = fread(exe_file_data, 1, file_size, exe_file); 35 | if (n_read != file_size) { 36 | printf("reading error (%d)\n", n_read); 37 | return 1; 38 | } 39 | 40 | // load the PE in memory 41 | printf("[+] Loading PE file\n"); 42 | void *entry = load_PE(exe_file_data); 43 | if (entry != NULL) { 44 | // call its entrypoint 45 | ((void (*)(void))entry)(); 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | void *load_PE(char *PE_data) { 52 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 53 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 54 | 55 | // extract information from PE header 56 | DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; 57 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 58 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 59 | 60 | // allocate memory 61 | // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc 62 | char *p_image_base = (char *)VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 63 | if (p_image_base == NULL) { 64 | return NULL; 65 | } 66 | 67 | // copy PE headers in memory 68 | memcpy(p_image_base, PE_data, size_of_headers); 69 | 70 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 71 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 72 | 73 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 74 | // calculate the VA we need to copy the content, from the RVA 75 | // section[i].VirtualAddress is a RVA, mind it 76 | char *dest = p_image_base + sections[i].VirtualAddress; 77 | 78 | // check if there is Raw data to copy 79 | if (sections[i].SizeOfRawData > 0) { 80 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 81 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 82 | } else { 83 | memset(dest, 0, sections[i].Misc.VirtualSize); 84 | } 85 | } 86 | 87 | fix_iat(p_image_base, p_NT_headers); 88 | fix_base_reloc(p_image_base, p_NT_headers); 89 | 90 | // Set permission for the PE header to read only 91 | DWORD oldProtect; 92 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 93 | 94 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 95 | char *dest = p_image_base + sections[i].VirtualAddress; 96 | DWORD s_perm = sections[i].Characteristics; 97 | DWORD v_perm = 0; // flags are not the same between virtal protect and the section header 98 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 99 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 100 | } else { 101 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 102 | } 103 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 104 | } 105 | 106 | return (void *)(p_image_base + entry_point_RVA); 107 | } 108 | 109 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 110 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 111 | 112 | // load the address of the import descriptors array 113 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 114 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 115 | 116 | // this array is null terminated 117 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 118 | // Get the name of the dll, and import it 119 | char *module_name = p_image_base + import_descriptors[i].Name; 120 | HMODULE import_module = LoadLibraryA(module_name); 121 | if (import_module == NULL) { 122 | printf("import module is null"); 123 | abort(); 124 | } 125 | 126 | // the lookup table points to function names or ordinals => it is the IDT 127 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 128 | 129 | // the address table is a copy of the lookup table at first 130 | // but we put the addresses of the loaded function inside => that's the IAT 131 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 132 | 133 | // null terminated array, again 134 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 135 | void *function_handle = NULL; 136 | 137 | // Check the lookup table for the adresse of the function name to import 138 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 139 | 140 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 141 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 142 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 143 | // this struct points to the ASCII function name 144 | char *funct_name = (char *)&(image_import->Name); 145 | // get that function address from it's module and name 146 | function_handle = (void *)GetProcAddress(import_module, funct_name); 147 | } else { 148 | // import by ordinal, directly 149 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 150 | } 151 | 152 | if (function_handle == NULL) { 153 | printf("function handle is null"); 154 | abort(); 155 | } 156 | 157 | // change the IAT, and put the function address inside. 158 | address_table[i].u1.Function = (DWORD)function_handle; 159 | } 160 | } 161 | } 162 | 163 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 164 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 165 | 166 | // this is how much we shifted the ImageBase 167 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 168 | 169 | // if there is a relocation table, and we actually shitfted the ImageBase 170 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 171 | 172 | // calculate the relocation table address 173 | IMAGE_BASE_RELOCATION *p_reloc = 174 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 175 | 176 | // once again, a null terminated array 177 | while (p_reloc->VirtualAddress != 0) { 178 | 179 | // how any relocation in this block 180 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 181 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 182 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 183 | WORD *fixups = (WORD *)(p_reloc + 1); 184 | for (int i = 0; i < size; ++i) { 185 | // type is the first 4 bits of the relocation word 186 | int type = fixups[i] >> 12; 187 | // offset is the last 12 bits 188 | int offset = fixups[i] & 0x0fff; 189 | // this is the address we are going to change 190 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 191 | 192 | // there is only one type used that needs to make a change 193 | switch (type) { 194 | case IMAGE_REL_BASED_HIGHLOW: 195 | *change_addr += delta_VA_reloc; 196 | break; 197 | default: 198 | break; 199 | } 200 | } 201 | 202 | // switch to the next relocation block, based on the size 203 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /packer2/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import lief 3 | from subprocess import STDOUT, CalledProcessError, check_output 4 | 5 | 6 | # %% 7 | # compile origin demo program 8 | try: 9 | check_output('gcc example.c -m32 -O2 -o example.exe', shell=True, stderr=STDOUT) 10 | except CalledProcessError as e: 11 | print(f'[!] demo program compilation failed, {e.stdout.decode()}') 12 | raise 13 | 14 | binary = lief.PE.parse('example.exe') 15 | print('[+] compile origin demo program success.') 16 | 17 | # %% 18 | # compile loader program 19 | compile_args = [ 20 | 'loader.c', 21 | '-m32', 22 | '-O2', 23 | '-Wall', 24 | '-Wl,--entry=__start', 25 | '-nodefaultlibs', 26 | '-nostartfiles', 27 | '-lkernel32', 28 | '-o', 29 | 'loader.exe' 30 | ] 31 | 32 | try: 33 | check_output(' '.join(['gcc', *compile_args]), shell=True, stderr=STDOUT) 34 | print('[+] compile loader program success.') 35 | except CalledProcessError as e: 36 | print(f'[!] loader compilation failed, {e.stdout.decode()}') 37 | raise 38 | 39 | loader = lief.PE.parse('loader.exe') 40 | 41 | # %% 42 | # add packed section 43 | with open('example.exe', 'rb') as f: 44 | packed_section = lief.PE.Section('.packed') 45 | packed_section.content = list(f.read()) 46 | packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ | 47 | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 48 | loader.add_section(packed_section) 49 | 50 | # build output binary 51 | builder = lief.PE.Builder(loader) 52 | builder.build() 53 | builder.write('packed.exe') 54 | print('[+] create packed binary success.') 55 | -------------------------------------------------------------------------------- /packer2/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer2/loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void *load_PE(char *PE_data); 5 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 6 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 7 | int mystrcmp(const char *str1, const char *str2); 8 | void mymemcpy(char *dest, const char *src, size_t length); 9 | 10 | int _start(void) { 11 | char *unpacker_VA = (char *)GetModuleHandleA(NULL); 12 | 13 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)unpacker_VA; 14 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(((char *)unpacker_VA) + p_DOS_header->e_lfanew); 15 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 16 | 17 | char *packed = NULL; 18 | char packed_section_name[] = ".packed"; 19 | 20 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 21 | if (mystrcmp(sections[i].Name, packed_section_name) == 0) { 22 | packed = unpacker_VA + sections[i].VirtualAddress; 23 | break; 24 | } 25 | } 26 | 27 | if (packed != NULL) { 28 | void (*entrypoint)(void) = (void (*)(void))load_PE(packed); 29 | entrypoint(); 30 | } 31 | 32 | return 0; 33 | } 34 | 35 | void *load_PE(char *PE_data) { 36 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 37 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 38 | 39 | // extract information from PE header 40 | DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; 41 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 42 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 43 | 44 | // allocate memory 45 | // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc 46 | char *p_image_base = (char *)VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 47 | if (p_image_base == NULL) { 48 | return NULL; 49 | } 50 | 51 | // copy PE headers in memory 52 | mymemcpy(p_image_base, PE_data, size_of_headers); 53 | 54 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 55 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 56 | 57 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 58 | // calculate the VA we need to copy the content, from the RVA 59 | // section[i].VirtualAddress is a RVA, mind it 60 | char *dest = p_image_base + sections[i].VirtualAddress; 61 | 62 | // check if there is Raw data to copy 63 | if (sections[i].SizeOfRawData > 0) { 64 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 65 | mymemcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 66 | } else { 67 | for (size_t i = 0; i < sections[i].Misc.VirtualSize; i++) { 68 | dest[i] = 0; 69 | } 70 | } 71 | } 72 | 73 | fix_iat(p_image_base, p_NT_headers); 74 | fix_base_reloc(p_image_base, p_NT_headers); 75 | 76 | // Set permission for the PE header to read only 77 | DWORD oldProtect; 78 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 79 | 80 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 81 | char *dest = p_image_base + sections[i].VirtualAddress; 82 | DWORD s_perm = sections[i].Characteristics; 83 | DWORD v_perm = 0; // flags are not the same between virtal protect and the section header 84 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 85 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 86 | } else { 87 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 88 | } 89 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 90 | } 91 | 92 | return (void *)(p_image_base + entry_point_RVA); 93 | } 94 | 95 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 96 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 97 | 98 | // load the address of the import descriptors array 99 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 100 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 101 | 102 | // this array is null terminated 103 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 104 | // Get the name of the dll, and import it 105 | char *module_name = p_image_base + import_descriptors[i].Name; 106 | HMODULE import_module = LoadLibraryA(module_name); 107 | if (import_module == NULL) { 108 | // panic! 109 | ExitProcess(255); 110 | } 111 | 112 | // the lookup table points to function names or ordinals => it is the IDT 113 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 114 | 115 | // the address table is a copy of the lookup table at first 116 | // but we put the addresses of the loaded function inside => that's the IAT 117 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 118 | 119 | // null terminated array, again 120 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 121 | void *function_handle = NULL; 122 | 123 | // Check the lookup table for the adresse of the function name to import 124 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 125 | 126 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 127 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 128 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 129 | // this struct points to the ASCII function name 130 | char *funct_name = (char *)&(image_import->Name); 131 | // get that function address from it's module and name 132 | function_handle = (void *)GetProcAddress(import_module, funct_name); 133 | } else { 134 | // import by ordinal, directly 135 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 136 | } 137 | 138 | if (function_handle == NULL) { 139 | ExitProcess(255); 140 | } 141 | 142 | // change the IAT, and put the function address inside. 143 | address_table[i].u1.Function = (DWORD)function_handle; 144 | } 145 | } 146 | } 147 | 148 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 149 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 150 | 151 | // this is how much we shifted the ImageBase 152 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 153 | 154 | // if there is a relocation table, and we actually shitfted the ImageBase 155 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 156 | 157 | // calculate the relocation table address 158 | IMAGE_BASE_RELOCATION *p_reloc = 159 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 160 | 161 | // once again, a null terminated array 162 | while (p_reloc->VirtualAddress != 0) { 163 | 164 | // how any relocation in this block 165 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 166 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 167 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 168 | WORD *fixups = (WORD *)(p_reloc + 1); 169 | for (size_t i = 0; i < size; ++i) { 170 | // type is the first 4 bits of the relocation word 171 | int type = fixups[i] >> 12; 172 | // offset is the last 12 bits 173 | int offset = fixups[i] & 0x0fff; 174 | // this is the address we are going to change 175 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 176 | 177 | // there is only one type used that needs to make a change 178 | switch (type) { 179 | case IMAGE_REL_BASED_HIGHLOW: 180 | *change_addr += delta_VA_reloc; 181 | break; 182 | default: 183 | break; 184 | } 185 | } 186 | 187 | // switch to the next relocation block, based on the size 188 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 189 | } 190 | } 191 | } 192 | 193 | int mystrcmp(const char *str1, const char *str2) { 194 | while (*str1 == *str2 && *str1 != 0) { 195 | str1++; 196 | str2++; 197 | } 198 | if (*str1 == 0 && *str2 == 0) { 199 | return 0; 200 | } 201 | return -1; 202 | } 203 | 204 | void mymemcpy(char *dest, const char *src, size_t length) { 205 | for (size_t i = 0; i < length; i++) { 206 | dest[i] = src[i]; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /packer3/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import lief 3 | from subprocess import STDOUT, CalledProcessError, check_output 4 | 5 | 6 | def align(x, al): 7 | """ return aligned to """ 8 | return ((x+(al-1))//al)*al 9 | 10 | 11 | # %% 12 | # compile origin demo program 13 | try: 14 | check_output('gcc example.c -m32 -O2 -o example.exe', shell=True, stderr=STDOUT) 15 | except CalledProcessError as e: 16 | print(f'[!] demo program compilation failed, {e.stdout.decode()}') 17 | raise 18 | 19 | binary = lief.PE.parse('example.exe') 20 | print('[+] compile origin demo program success.') 21 | 22 | # %% 23 | # calculate shift offset and reserved section size 24 | image_base = binary.optional_header.imagebase 25 | lowest_rva = min([s.virtual_address for s in binary.sections]) 26 | highest_rva = max([s.virtual_address + s.size for s in binary.sections]) 27 | sect_alignment = binary.optional_header.section_alignment 28 | print('[+] analyze origin demo program binary success.') 29 | 30 | # %% 31 | # compile shifted loader program 32 | compile_args = [ 33 | 'loader.c', 34 | '-m32', 35 | '-O2', 36 | '-Wall', 37 | '-Wl,--entry=__start', 38 | '-nodefaultlibs', 39 | '-nostartfiles', 40 | '-lkernel32', 41 | '-luser32', 42 | f'-Wl,--image-base={hex(image_base)}', 43 | f'-Wl,--section-start=.text={hex(align(image_base+highest_rva,sect_alignment))}', 44 | '-o', 45 | 'shifted-loader.exe' 46 | ] 47 | 48 | try: 49 | check_output(' '.join(['gcc', *compile_args]), shell=True, stderr=STDOUT) 50 | print('[+] compile shifted loader program success.') 51 | except CalledProcessError as e: 52 | print(f'[!] loader compilation failed, {e.stdout.decode()}') 53 | raise 54 | 55 | shifted_loader = lief.PE.parse('shifted-loader.exe') 56 | sect_alignment = shifted_loader.optional_header.section_alignment 57 | file_alignment = shifted_loader.optional_header.file_alignment 58 | 59 | # %% 60 | # create new binary from scratch 61 | output = lief.PE.Binary('packed', lief.PE.PE_TYPE.PE32) 62 | 63 | # copy essential fields from shifted_loader 64 | output.optional_header.imagebase = shifted_loader.optional_header.imagebase 65 | output.optional_header.section_alignment = shifted_loader.optional_header.section_alignment 66 | output.optional_header.file_alignment = shifted_loader.optional_header.file_alignment 67 | 68 | # disable ASLR 69 | output.optional_header.dll_characteristics = 0 70 | 71 | # add .alloc section 72 | allocate_size = align(highest_rva-lowest_rva, sect_alignment) 73 | allocate_section = lief.PE.Section(".alloc") 74 | allocate_section.virtual_address = lowest_rva 75 | allocate_section.virtual_size = allocate_size 76 | allocate_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ 77 | | lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE 78 | | lief.PE.SECTION_CHARACTERISTICS.CNT_UNINITIALIZED_DATA) 79 | output.add_section(allocate_section) 80 | 81 | # copy sections 82 | for s in shifted_loader.sections: 83 | # let lief recalculate section offset and sizeof raw data 84 | s.offset = 0 85 | s.sizeof_raw_data = 0 86 | output.add_section(s) 87 | 88 | # add packed section 89 | with open('example.exe', 'rb') as f: 90 | packed_section = lief.PE.Section('.packed') 91 | packed_section.content = list(f.read()) 92 | packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ | 93 | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 94 | output.add_section(packed_section) 95 | 96 | # copy data directories 97 | for i in range(0, 15): 98 | src = shifted_loader.data_directories[i] 99 | output.data_directories[i].rva = src.rva 100 | output.data_directories[i].size = src.size 101 | 102 | # correct number of data directories 103 | # warning: size of data directories may disagree with IMAGE_NT_HEADERS.DataDirectory in winnt.h 104 | output.optional_header.numberof_rva_and_size = len(output.data_directories) 105 | # copy original address of entrypoint 106 | output.optional_header.addressof_entrypoint = shifted_loader.optional_header.addressof_entrypoint 107 | # let lief recalculate size of image 108 | output.optional_header.sizeof_image = 0 109 | 110 | # build output binary 111 | builder = lief.PE.Builder(output) 112 | builder.build() 113 | builder.write('packed.exe') 114 | print('[+] create packed binary success.') 115 | -------------------------------------------------------------------------------- /packer3/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer3/loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #pragma runtime_checks("", off) 4 | 5 | void *load_PE(char *PE_data); 6 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 7 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 8 | int mystrcmp(const char *str1, const char *str2); 9 | void mymemcpy(char *dest, const char *src, size_t length); 10 | 11 | int _start(void) { 12 | char *unpacker_VA = (char *)GetModuleHandleA(NULL); 13 | 14 | IMAGE_DOS_HEADER *p_DOS_header = (PIMAGE_DOS_HEADER)unpacker_VA; 15 | IMAGE_NT_HEADERS *p_NT_headers = (PIMAGE_NT_HEADERS)(unpacker_VA + p_DOS_header->e_lfanew); 16 | IMAGE_SECTION_HEADER *sections = (PIMAGE_SECTION_HEADER)(p_NT_headers + 1); 17 | sections = (PIMAGE_SECTION_HEADER)((char *)sections - (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 18 | p_NT_headers->OptionalHeader.NumberOfRvaAndSizes) * 19 | sizeof(IMAGE_DATA_DIRECTORY)); 20 | 21 | char *packed = NULL; 22 | char packed_section_name[] = ".packed"; 23 | 24 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 25 | if (mystrcmp((char *)§ions[i].Name[0], packed_section_name) == 0) { 26 | packed = unpacker_VA + sections[i].VirtualAddress; 27 | break; 28 | } 29 | } 30 | 31 | if (packed != NULL) { 32 | void (*entrypoint)(void) = (void (*)(void))load_PE(packed); 33 | entrypoint(); 34 | return 0; 35 | } 36 | 37 | MessageBoxA(NULL, ".packed section not found", "loader error", MB_OK); 38 | return 0; 39 | } 40 | 41 | void *load_PE(char *PE_data) { 42 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 43 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 44 | 45 | // extract information from PE header 46 | DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; 47 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 48 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 49 | 50 | // base address 51 | char *p_image_base = (char *)GetModuleHandleA(NULL); 52 | if (p_image_base == NULL) { 53 | return NULL; 54 | } 55 | 56 | // disable write protect 57 | DWORD old_protect; 58 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READWRITE, &old_protect); 59 | 60 | // copy PE headers in memory 61 | mymemcpy(p_image_base, PE_data, size_of_headers); 62 | 63 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 64 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 65 | 66 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 67 | // calculate the VA we need to copy the content, from the RVA 68 | // section[i].VirtualAddress is a RVA, mind it 69 | char *dest = p_image_base + sections[i].VirtualAddress; 70 | 71 | // check if there is Raw data to copy 72 | if (sections[i].SizeOfRawData > 0) { 73 | // make sure we can write in allocated sections 74 | VirtualProtect(dest, sections[i].SizeOfRawData, PAGE_READWRITE, &old_protect); 75 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 76 | mymemcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 77 | } else { 78 | VirtualProtect(dest, sections[i].Misc.VirtualSize, PAGE_READWRITE, &old_protect); 79 | for (size_t i = 0; i < sections[i].Misc.VirtualSize; i++) { 80 | dest[i] = 0; 81 | } 82 | } 83 | } 84 | 85 | fix_iat(p_image_base, p_NT_headers); 86 | fix_base_reloc(p_image_base, p_NT_headers); 87 | 88 | // Set permission for the PE header to read only 89 | DWORD oldProtect; 90 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &old_protect); 91 | 92 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 93 | char *dest = p_image_base + sections[i].VirtualAddress; 94 | DWORD s_perm = sections[i].Characteristics; 95 | DWORD v_perm = 0; // flags are not the same between virtal protect and the section header 96 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 97 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 98 | } else { 99 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 100 | } 101 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &old_protect); 102 | } 103 | 104 | return (void *)(p_image_base + entry_point_RVA); 105 | } 106 | 107 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 108 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 109 | 110 | // load the address of the import descriptors array 111 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 112 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 113 | 114 | // this array is null terminated 115 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 116 | // Get the name of the dll, and import it 117 | char *module_name = p_image_base + import_descriptors[i].Name; 118 | HMODULE import_module = LoadLibraryA(module_name); 119 | if (import_module == NULL) { 120 | // panic! 121 | ExitProcess(255); 122 | } 123 | 124 | // the lookup table points to function names or ordinals => it is the IDT 125 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 126 | 127 | // the address table is a copy of the lookup table at first 128 | // but we put the addresses of the loaded function inside => that's the IAT 129 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 130 | 131 | // null terminated array, again 132 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 133 | void *function_handle = NULL; 134 | 135 | // Check the lookup table for the adresse of the function name to import 136 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 137 | 138 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 139 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 140 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 141 | // this struct points to the ASCII function name 142 | char *funct_name = (char *)&(image_import->Name); 143 | // get that function address from it's module and name 144 | function_handle = (void *)GetProcAddress(import_module, funct_name); 145 | } else { 146 | // import by ordinal, directly 147 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 148 | } 149 | 150 | if (function_handle == NULL) { 151 | ExitProcess(255); 152 | } 153 | 154 | // change the IAT, and put the function address inside. 155 | address_table[i].u1.Function = (DWORD)function_handle; 156 | } 157 | } 158 | } 159 | 160 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 161 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 162 | 163 | // this is how much we shifted the ImageBase 164 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 165 | 166 | // if there is a relocation table, and we actually shitfted the ImageBase 167 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 168 | 169 | // calculate the relocation table address 170 | IMAGE_BASE_RELOCATION *p_reloc = 171 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 172 | 173 | // once again, a null terminated array 174 | while (p_reloc->VirtualAddress != 0) { 175 | 176 | // how any relocation in this block 177 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 178 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 179 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 180 | WORD *fixups = (WORD *)(p_reloc + 1); 181 | for (size_t i = 0; i < size; ++i) { 182 | // type is the first 4 bits of the relocation word 183 | int type = fixups[i] >> 12; 184 | // offset is the last 12 bits 185 | int offset = fixups[i] & 0x0fff; 186 | // this is the address we are going to change 187 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 188 | 189 | // there is only one type used that needs to make a change 190 | switch (type) { 191 | case IMAGE_REL_BASED_HIGHLOW: 192 | *change_addr += delta_VA_reloc; 193 | break; 194 | default: 195 | break; 196 | } 197 | } 198 | 199 | // switch to the next relocation block, based on the size 200 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 201 | } 202 | } 203 | } 204 | 205 | int mystrcmp(const char *str1, const char *str2) { 206 | while (*str1 == *str2 && *str1 != 0) { 207 | str1++; 208 | str2++; 209 | } 210 | if (*str1 == 0 && *str2 == 0) { 211 | return 0; 212 | } 213 | return -1; 214 | } 215 | 216 | void mymemcpy(char *dest, const char *src, size_t length) { 217 | for (size_t i = 0; i < length; i++) { 218 | dest[i] = src[i]; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /packer4/build.py: -------------------------------------------------------------------------------- 1 | 2 | # %% 3 | from os import path 4 | import struct 5 | import zlib 6 | 7 | from lief import PE 8 | 9 | from utils import align, compile 10 | 11 | src_dir = path.dirname(__file__) 12 | 13 | # %% 14 | # compile origin demo program 15 | compile(path.join(src_dir, 'example.c'), '-m32 -O2 -o example.exe') 16 | binary = PE.parse('example.exe') 17 | print('[+] compile origin demo program success.') 18 | 19 | # %% 20 | # calculate shift offset and reserved section size 21 | image_base = binary.optional_header.imagebase 22 | lowest_rva = min([s.virtual_address for s in binary.sections]) 23 | highest_rva = max([s.virtual_address + s.size for s in binary.sections]) 24 | sect_alignment = binary.optional_header.section_alignment 25 | print('[+] analyze origin demo program binary success.') 26 | 27 | # %% 28 | # compile shifted loader program 29 | cflags = [ 30 | '-m32', 31 | '-O2', 32 | '-Wall', 33 | '-I.', 34 | '-Wl,--entry=__start', 35 | '-nodefaultlibs', 36 | '-nostartfiles', 37 | '-lkernel32', 38 | '-luser32', 39 | '-lmsvcrt', 40 | '-lz', 41 | f'-Wl,--image-base={hex(image_base)}', 42 | f'-Wl,--section-start=.text={hex(align(image_base+highest_rva,sect_alignment))}', 43 | '-o', 44 | 'shifted-loader.exe' 45 | ] 46 | 47 | compile(path.join(src_dir, 'loader.c'), cflags) 48 | print('[+] compile shifted loader program success.') 49 | 50 | shifted_loader = PE.parse('shifted-loader.exe') 51 | sect_alignment = shifted_loader.optional_header.section_alignment 52 | file_alignment = shifted_loader.optional_header.file_alignment 53 | 54 | # %% 55 | # create new binary from scratch 56 | output = PE.Binary('packed', PE.PE_TYPE.PE32) 57 | 58 | # copy essential fields from shifted_loader 59 | output.optional_header.imagebase = shifted_loader.optional_header.imagebase 60 | output.optional_header.section_alignment = shifted_loader.optional_header.section_alignment 61 | output.optional_header.file_alignment = shifted_loader.optional_header.file_alignment 62 | 63 | # disable ASLR 64 | output.optional_header.dll_characteristics = 0 65 | 66 | # add .alloc section 67 | allocate_size = align(highest_rva-lowest_rva, sect_alignment) 68 | allocate_section = PE.Section(".alloc") 69 | allocate_section.virtual_address = lowest_rva 70 | allocate_section.virtual_size = allocate_size 71 | allocate_section.characteristics = (PE.SECTION_CHARACTERISTICS.MEM_READ 72 | | PE.SECTION_CHARACTERISTICS.MEM_WRITE 73 | | PE.SECTION_CHARACTERISTICS.CNT_UNINITIALIZED_DATA) 74 | output.add_section(allocate_section) 75 | 76 | # copy sections 77 | for s in shifted_loader.sections: 78 | # let lief recalculate section offset and sizeof raw data 79 | s.offset = 0 80 | s.sizeof_raw_data = 0 81 | output.add_section(s) 82 | 83 | # add packed section 84 | with open('example.exe', 'rb') as f: 85 | file_content = f.read() 86 | origin_length = len(file_content) 87 | compressed = zlib.compress(file_content, 9) # best compression 88 | compressed_length = len(compressed) 89 | section_content = struct.pack(' 4 | 5 | #ifdef __cplusplus 6 | external "C" { 7 | #endif 8 | 9 | int decompress(void *compressed, size_t length, void *decompressed, size_t decompressed_length); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif -------------------------------------------------------------------------------- /packer4/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { 4 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /packer4/loader.c: -------------------------------------------------------------------------------- 1 | #include "zlib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 8 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 9 | 10 | // load the address of the import descriptors array 11 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 12 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 13 | 14 | // this array is null terminated 15 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 16 | // Get the name of the dll, and import it 17 | char *module_name = p_image_base + import_descriptors[i].Name; 18 | HMODULE import_module = LoadLibraryA(module_name); 19 | if (import_module == NULL) { 20 | // panic! 21 | ExitProcess(255); 22 | } 23 | 24 | // the lookup table points to function names or ordinals => it is the IDT 25 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 26 | 27 | // the address table is a copy of the lookup table at first 28 | // but we put the addresses of the loaded function inside => that's the IAT 29 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 30 | 31 | // null terminated array, again 32 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 33 | void *function_handle = NULL; 34 | 35 | // Check the lookup table for the address of the function name to import 36 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 37 | 38 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 39 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 40 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 41 | // this struct points to the ASCII function name 42 | char *funct_name = (char *)&(image_import->Name); 43 | // get that function address from it's module and name 44 | function_handle = (void *)GetProcAddress(import_module, funct_name); 45 | } else { 46 | // import by ordinal, directly 47 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 48 | } 49 | 50 | if (function_handle == NULL) { 51 | ExitProcess(255); 52 | } 53 | 54 | // change the IAT, and put the function address inside. 55 | address_table[i].u1.Function = (DWORD)function_handle; 56 | } 57 | } 58 | } 59 | 60 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 61 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 62 | 63 | // this is how much we shifted the ImageBase 64 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 65 | 66 | // if there is a relocation table, and we actually shifted the ImageBase 67 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 68 | 69 | // calculate the relocation table address 70 | IMAGE_BASE_RELOCATION *p_reloc = 71 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 72 | 73 | // once again, a null terminated array 74 | while (p_reloc->VirtualAddress != 0) { 75 | 76 | // how any relocation in this block 77 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 78 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 79 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 80 | WORD *fixups = (WORD *)(p_reloc + 1); 81 | for (size_t i = 0; i < size; ++i) { 82 | // type is the first 4 bits of the relocation word 83 | int type = fixups[i] >> 12; 84 | // offset is the last 12 bits 85 | int offset = fixups[i] & 0x0fff; 86 | // this is the address we are going to change 87 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 88 | 89 | // there is only one type used that needs to make a change 90 | switch (type) { 91 | case IMAGE_REL_BASED_HIGHLOW: 92 | *change_addr += delta_VA_reloc; 93 | break; 94 | default: 95 | break; 96 | } 97 | } 98 | 99 | // switch to the next relocation block, based on the size 100 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 101 | } 102 | } 103 | } 104 | 105 | void decompress(void *compressed, size_t length, void *decompressed, size_t decompressed_length) { 106 | z_stream inflate_stream; 107 | inflate_stream.zalloc = Z_NULL; 108 | inflate_stream.zfree = Z_NULL; 109 | inflate_stream.opaque = Z_NULL; 110 | inflate_stream.avail_in = (uInt)length; 111 | inflate_stream.next_in = (Bytef *)compressed; 112 | inflate_stream.avail_out = (uInt)decompressed_length; 113 | inflate_stream.next_out = (Bytef *)decompressed; 114 | inflateInit(&inflate_stream); 115 | 116 | int err = inflate(&inflate_stream, Z_NO_FLUSH); 117 | if (err != Z_STREAM_END) { 118 | inflateEnd(&inflate_stream); 119 | MessageBoxA(NULL, "zlib decompression failed", "zlib", MB_OK); 120 | return; 121 | } 122 | inflateEnd(&inflate_stream); 123 | return; 124 | } 125 | 126 | void *load_PE(char *PE_data) { 127 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 128 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 129 | 130 | // extract information from PE header 131 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 132 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 133 | 134 | // base address 135 | char *p_image_base = (char *)GetModuleHandleA(NULL); 136 | if (p_image_base == NULL) { 137 | return NULL; 138 | } 139 | 140 | // disable write protect 141 | DWORD old_protect; 142 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READWRITE, &old_protect); 143 | 144 | // copy PE headers in memory 145 | memcpy(p_image_base, PE_data, size_of_headers); 146 | 147 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 148 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 149 | 150 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 151 | // calculate the VA we need to copy the content, from the RVA 152 | // section[i].VirtualAddress is a RVA, mind it 153 | char *dest = p_image_base + sections[i].VirtualAddress; 154 | 155 | // check if there is Raw data to copy 156 | if (sections[i].SizeOfRawData > 0) { 157 | // make sure we can write in allocated sections 158 | VirtualProtect(dest, sections[i].SizeOfRawData, PAGE_READWRITE, &old_protect); 159 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 160 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 161 | } else { 162 | VirtualProtect(dest, sections[i].Misc.VirtualSize, PAGE_READWRITE, &old_protect); 163 | for (size_t i = 0; i < sections[i].Misc.VirtualSize; i++) { 164 | dest[i] = 0; 165 | } 166 | } 167 | } 168 | 169 | fix_iat(p_image_base, p_NT_headers); 170 | fix_base_reloc(p_image_base, p_NT_headers); 171 | 172 | // Set permission for the PE header to read only 173 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &old_protect); 174 | 175 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 176 | char *dest = p_image_base + sections[i].VirtualAddress; 177 | DWORD s_perm = sections[i].Characteristics; 178 | DWORD v_perm = 0; // flags are not the same between virtual protect and the section header 179 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 180 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 181 | } else { 182 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 183 | } 184 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &old_protect); 185 | } 186 | 187 | return (void *)(p_image_base + entry_point_RVA); 188 | } 189 | 190 | int _start(void) { 191 | char *unpacker_VA = (char *)GetModuleHandleA(NULL); 192 | 193 | IMAGE_DOS_HEADER *p_DOS_header = (PIMAGE_DOS_HEADER)unpacker_VA; 194 | IMAGE_NT_HEADERS *p_NT_headers = (PIMAGE_NT_HEADERS)(unpacker_VA + p_DOS_header->e_lfanew); 195 | IMAGE_SECTION_HEADER *sections = (PIMAGE_SECTION_HEADER)(p_NT_headers + 1); 196 | sections = (PIMAGE_SECTION_HEADER)((char *)sections - (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 197 | p_NT_headers->OptionalHeader.NumberOfRvaAndSizes) * 198 | sizeof(IMAGE_DATA_DIRECTORY)); 199 | 200 | char *packed = NULL; 201 | char packed_section_name[] = ".packed"; 202 | 203 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 204 | if (strcmp((char *)§ions[i].Name[0], packed_section_name) == 0) { 205 | packed = unpacker_VA + sections[i].VirtualAddress; 206 | break; 207 | } 208 | } 209 | 210 | if (packed != NULL) { 211 | DWORD compressed_size = *((DWORD *)packed); // compressed size little-endian 212 | DWORD decompressed_size = *((DWORD *)(packed + 4)); // decompressed size little-endian 213 | void *compressed = (void *)(packed + 8); // compressed buffer 214 | void *decompressed = malloc(decompressed_size); // decompressed buffer 215 | if (decompressed == NULL) { 216 | MessageBoxA(NULL, "memory allocate failed", "malloc", MB_OK); 217 | return 0; 218 | } 219 | 220 | decompress(compressed, compressed_size, decompressed, decompressed_size); 221 | 222 | void (*entrypoint)(void) = (void (*)(void))load_PE(decompressed); 223 | entrypoint(); 224 | 225 | return 0; 226 | } 227 | 228 | MessageBoxA(NULL, ".packed section not found", "loader error", MB_OK); 229 | return 0; 230 | } 231 | -------------------------------------------------------------------------------- /packer4/utils.py: -------------------------------------------------------------------------------- 1 | from subprocess import STDOUT, run, PIPE 2 | 3 | 4 | def align(x, al): 5 | """ return aligned to """ 6 | return ((x+(al-1))//al)*al 7 | 8 | 9 | class CompilationError(Exception): 10 | def __init__(self, code, output) -> None: 11 | super().__init__(f'compilation failed') 12 | self.code = code 13 | self.output = output 14 | 15 | def __str__(self) -> str: 16 | return f'compilation failed: compiler exit code {self.code}' 17 | 18 | def __repr__(self) -> str: 19 | return f'' 20 | 21 | 22 | def compile(sources, flags): 23 | args = '' 24 | compiler = 'gcc' 25 | 26 | args += '' 27 | if isinstance(sources, (str, bytes)): 28 | args += sources 29 | elif isinstance(sources, (list, tuple)): 30 | args += ' '.join(sources) 31 | 32 | args += ' ' 33 | if isinstance(flags, (str, bytes)): 34 | args += flags 35 | elif isinstance(flags, (list, tuple)): 36 | args += ' '.join(flags) 37 | 38 | cmd = f'{compiler} {args}' 39 | proc = run(cmd, shell=True, stderr=STDOUT) 40 | if proc.returncode != 0: 41 | raise CompilationError(proc.returncode, proc.stdout) 42 | 43 | 44 | def windres(sources, output): 45 | executable = 'windres' 46 | args = '' 47 | if isinstance(sources, (str, bytes)): 48 | args += sources 49 | elif isinstance(sources, (list, tuple)): 50 | args += ' '.join(sources) 51 | 52 | cmd = f'{executable} {args} -o {output}' 53 | proc = run(cmd, shell=True, stderr=STDOUT) 54 | if proc.returncode != 0: 55 | raise CompilationError(proc.returncode, proc.stdout) 56 | -------------------------------------------------------------------------------- /packer5/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | from os import path 3 | import struct 4 | import png 5 | 6 | from lief import PE 7 | 8 | from utils import align, compile, windres 9 | 10 | src_dir = path.dirname(__file__) 11 | 12 | # %% 13 | # compile origin demo program 14 | compile(path.join(src_dir, 'example.c'), '-m32 -O2 -o example.exe') 15 | binary = PE.parse('example.exe') 16 | print('[+] compile origin demo program success.') 17 | 18 | # %% 19 | IMG_PATH = 'packer5-packed.png' 20 | ROW_LEN = 256 21 | with open('example.exe', 'rb') as f: 22 | arr = [] 23 | content = f.read() 24 | content = struct.pack(' 2 | 3 | int main(void) { 4 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /packer5/loader.c: -------------------------------------------------------------------------------- 1 | #include "png_decode.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 9 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 10 | 11 | // load the address of the import descriptors array 12 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 13 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 14 | 15 | // this array is null terminated 16 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 17 | // Get the name of the dll, and import it 18 | char *module_name = p_image_base + import_descriptors[i].Name; 19 | HMODULE import_module = LoadLibraryA(module_name); 20 | if (import_module == NULL) { 21 | // panic! 22 | ExitProcess(255); 23 | } 24 | 25 | // the lookup table points to function names or ordinals => it is the IDT 26 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 27 | 28 | // the address table is a copy of the lookup table at first 29 | // but we put the addresses of the loaded function inside => that's the IAT 30 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 31 | 32 | // null terminated array, again 33 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 34 | void *function_handle = NULL; 35 | 36 | // Check the lookup table for the address of the function name to import 37 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 38 | 39 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 40 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 41 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 42 | // this struct points to the ASCII function name 43 | char *funct_name = (char *)&(image_import->Name); 44 | // get that function address from it's module and name 45 | function_handle = (void *)GetProcAddress(import_module, funct_name); 46 | } else { 47 | // import by ordinal, directly 48 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 49 | } 50 | 51 | if (function_handle == NULL) { 52 | ExitProcess(255); 53 | } 54 | 55 | // change the IAT, and put the function address inside. 56 | address_table[i].u1.Function = (DWORD)function_handle; 57 | } 58 | } 59 | } 60 | 61 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 62 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 63 | 64 | // this is how much we shifted the ImageBase 65 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 66 | 67 | // if there is a relocation table, and we actually shifted the ImageBase 68 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 69 | 70 | // calculate the relocation table address 71 | IMAGE_BASE_RELOCATION *p_reloc = 72 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 73 | 74 | // once again, a null terminated array 75 | while (p_reloc->VirtualAddress != 0) { 76 | 77 | // how any relocation in this block 78 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 79 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 80 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 81 | WORD *fixups = (WORD *)(p_reloc + 1); 82 | for (size_t i = 0; i < size; ++i) { 83 | // type is the first 4 bits of the relocation word 84 | int type = fixups[i] >> 12; 85 | // offset is the last 12 bits 86 | int offset = fixups[i] & 0x0fff; 87 | // this is the address we are going to change 88 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 89 | 90 | // there is only one type used that needs to make a change 91 | switch (type) { 92 | case IMAGE_REL_BASED_HIGHLOW: 93 | *change_addr += delta_VA_reloc; 94 | break; 95 | default: 96 | break; 97 | } 98 | } 99 | 100 | // switch to the next relocation block, based on the size 101 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 102 | } 103 | } 104 | } 105 | 106 | void *load_PE(char *PE_data) { 107 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 108 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 109 | 110 | // extract information from PE header 111 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 112 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 113 | 114 | // allocate memory 115 | DWORD size_of_img = p_NT_headers->OptionalHeader.SizeOfImage; 116 | char *p_image_base = (char *)VirtualAlloc(NULL, size_of_img, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 117 | if (p_image_base == NULL) { 118 | MessageBoxA(NULL, "allocate memory failed", "VirtualAlloc", MB_OK); 119 | return NULL; 120 | } 121 | 122 | // copy PE headers in memory 123 | memcpy(p_image_base, PE_data, size_of_headers); 124 | 125 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 126 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 127 | 128 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 129 | // calculate the VA we need to copy the content, from the RVA 130 | // section[i].VirtualAddress is a RVA, mind it 131 | char *dest = p_image_base + sections[i].VirtualAddress; 132 | 133 | // check if there is Raw data to copy 134 | if (sections[i].SizeOfRawData > 0) { 135 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 136 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 137 | } else { 138 | memset(dest, 0, sections[i].Misc.VirtualSize); 139 | } 140 | } 141 | 142 | fix_iat(p_image_base, p_NT_headers); 143 | fix_base_reloc(p_image_base, p_NT_headers); 144 | 145 | // Set permission for the PE header to read only 146 | DWORD old_protect = 0; 147 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &old_protect); 148 | 149 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 150 | char *dest = p_image_base + sections[i].VirtualAddress; 151 | DWORD s_perm = sections[i].Characteristics; 152 | DWORD v_perm = 0; // flags are not the same between virtual protect and the section header 153 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 154 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 155 | } else { 156 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 157 | } 158 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &old_protect); 159 | } 160 | 161 | return (void *)(p_image_base + entry_point_RVA); 162 | } 163 | 164 | int get_resource(const char *name, void **buffer, size_t *length) { 165 | HRSRC res_found = FindResourceA(NULL, "BEAUTIFUL.PNG", RT_RCDATA); 166 | if (res_found == NULL) { 167 | MessageBoxA(NULL, "find resource failed", "FindResourceA", MB_OK); 168 | return 1; 169 | } 170 | 171 | DWORD sizeof_res = SizeofResource(NULL, res_found); 172 | if (sizeof_res == 0) { 173 | MessageBoxA(NULL, "sizeof resource failed", "SizeofResource", MB_OK); 174 | return 2; 175 | } 176 | 177 | HGLOBAL res_loaded = LoadResource(NULL, res_found); 178 | if (res_loaded == NULL) { 179 | MessageBoxA(NULL, "load resource failed", "LoadResource", MB_OK); 180 | return 3; 181 | } 182 | 183 | LPVOID res_acquired = LockResource(res_loaded); 184 | if (res_acquired == NULL) { 185 | MessageBoxA(NULL, "lock resource failed", "LockResource", MB_OK); 186 | return 3; 187 | } 188 | 189 | *buffer = malloc(sizeof_res); 190 | *length = sizeof_res; 191 | memcpy(*buffer, res_acquired, sizeof_res); 192 | UnlockResource(res_loaded); 193 | FreeResource(res_loaded); 194 | 195 | return 0; 196 | } 197 | 198 | int _start(void) { 199 | void *buffer = NULL; 200 | size_t length = 0; 201 | if (get_resource("BEAUTIFUL.PNG", &buffer, &length) != 0) { 202 | return 1; 203 | } 204 | 205 | u8p program = read_program_from_png((u8p)buffer, length); 206 | free(buffer); 207 | 208 | if (program != NULL) { 209 | void (*entrypoint)(void) = (void (*)(void))load_PE((char *)program); 210 | entrypoint(); 211 | 212 | free(program); 213 | return 0; 214 | } 215 | 216 | MessageBoxA(NULL, ".packed section not found", "loader error", MB_OK); 217 | return 0; 218 | } 219 | -------------------------------------------------------------------------------- /packer5/png_decode.c: -------------------------------------------------------------------------------- 1 | #include "png.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef uint8_t u8, *u8p; 9 | typedef uint32_t u32, *u32p; 10 | 11 | // decode PNG in memory 12 | // https://stackoverflow.com/questions/53237065/using-libpng-1-2-to-write-rgb-image-buffer-to-png-buffer-in-memory-causing-segme 13 | u8p read_program_from_png(u8p data, size_t length) { 14 | png_image image; 15 | memset(&image, 0, sizeof(image)); 16 | image.version = PNG_IMAGE_VERSION; 17 | if (png_image_begin_read_from_memory(&image, data, length) == 0) { 18 | return NULL; 19 | } 20 | 21 | png_bytep buffer; 22 | image.format = PNG_FORMAT_GRAY; 23 | size_t input_data_length = PNG_IMAGE_SIZE(image); 24 | buffer = (png_bytep)malloc(input_data_length); 25 | memset(buffer, 0, input_data_length); 26 | 27 | if (png_image_finish_read(&image, NULL, buffer, 0, NULL) == 0) { 28 | return NULL; 29 | } 30 | 31 | u32 actual_len = *((u32 *)buffer); 32 | void *program = malloc(actual_len); 33 | memcpy(program, buffer + 4, actual_len); 34 | free(buffer); 35 | 36 | return (u8p)program; 37 | } 38 | -------------------------------------------------------------------------------- /packer5/png_decode.h: -------------------------------------------------------------------------------- 1 | #ifndef PNG_DECODE_H_ 2 | #define PNG_DECODE_H_ 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef uint8_t u8, *u8p; 11 | typedef uint32_t u32, *u32p; 12 | 13 | u8p read_program_from_png(u8p data, size_t length); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif -------------------------------------------------------------------------------- /packer5/rsrc.rc: -------------------------------------------------------------------------------- 1 | beautiful.png RCDATA "packer5-packed.png" 2 | -------------------------------------------------------------------------------- /packer5/utils.py: -------------------------------------------------------------------------------- 1 | from subprocess import STDOUT, run, PIPE 2 | 3 | 4 | def align(x, al): 5 | """ return aligned to """ 6 | return ((x+(al-1))//al)*al 7 | 8 | 9 | class CompilationError(Exception): 10 | def __init__(self, code, output) -> None: 11 | super().__init__(f'compilation failed') 12 | self.code = code 13 | self.output = output 14 | 15 | def __str__(self) -> str: 16 | return f'compilation failed: compiler exit code {self.code}' 17 | 18 | def __repr__(self) -> str: 19 | return f'' 20 | 21 | 22 | def compile(sources, flags): 23 | args = '' 24 | compiler = 'gcc' 25 | 26 | args += '' 27 | if isinstance(sources, (str, bytes)): 28 | args += sources 29 | elif isinstance(sources, (list, tuple)): 30 | args += ' '.join(sources) 31 | 32 | args += ' ' 33 | if isinstance(flags, (str, bytes)): 34 | args += flags 35 | elif isinstance(flags, (list, tuple)): 36 | args += ' '.join(flags) 37 | 38 | cmd = f'{compiler} {args}' 39 | proc = run(cmd, shell=True, stderr=STDOUT) 40 | if proc.returncode != 0: 41 | raise CompilationError(proc.returncode, proc.stdout) 42 | 43 | 44 | def windres(sources, output): 45 | executable = 'windres' 46 | args = '' 47 | if isinstance(sources, (str, bytes)): 48 | args += sources 49 | elif isinstance(sources, (list, tuple)): 50 | args += ' '.join(sources) 51 | 52 | cmd = f'{executable} {args} -o {output}' 53 | proc = run(cmd, shell=True, stderr=STDOUT) 54 | if proc.returncode != 0: 55 | raise CompilationError(proc.returncode, proc.stdout) 56 | -------------------------------------------------------------------------------- /packer6/anti_debug.c: -------------------------------------------------------------------------------- 1 | #include "anti_debug.h" 2 | #include "assembly_utils.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void anti_debug_by_isDebuggerPresent(void) { 11 | if (IsDebuggerPresent() == TRUE) { 12 | MessageBoxA(NULL, "debugger detected", "IsDebuggerPresent", MB_OK); 13 | } 14 | } 15 | 16 | void anti_debug_by_PEB_BeingDebugged(void) { 17 | PPEB peb = GetPEB(); 18 | if (peb->BeingDebugged != 0) { 19 | MessageBoxA(NULL, "debugger detected", "PEB->BeingDebugged", MB_OK); 20 | } 21 | } 22 | 23 | void anti_debug_by_RtlGetNtGlobalFlags(void) { 24 | // 两种方式,直接读内存或者用undocumented接口 25 | PPEB peb = GetPEB(); 26 | if (*(PULONG)((PBYTE)peb + 0x68) & (0x20 | 0x40)) { 27 | MessageBoxA(NULL, "debugger detected", "PEB->NtGlobalFlag", MB_OK); 28 | } 29 | // 或者... 30 | HMODULE ntdll = LoadLibraryA("ntdll.dll"); 31 | FARPROC proc = GetProcAddress(ntdll, "RtlGetNtGlobalFlags"); 32 | typedef ULONG (*RtlGetNtGlobalFlags_t)(void); 33 | if (((RtlGetNtGlobalFlags_t)proc)() & (0x20 | 0x40)) { 34 | MessageBoxA(NULL, "debugger detected", "RtlGetNtGlobalFlags", MB_OK); 35 | } 36 | } 37 | 38 | // detect debugger by _HEAP->Flags and _HEAP->ForceFlags 39 | // 40 | // <== windbg display type ==> 41 | // 42 | // 0:000> dt _peb processheap 43 | // ntdll!_PEB 44 | // +0x018 ProcessHeap : Ptr32 Void 45 | // 0:000> dt _heap flags 46 | // ntdll!_HEAP 47 | // +0x040 Flags : Uint4B 48 | // 0:000> dt _heap forceflags 49 | // ntdll!_HEAP 50 | // +0x044 ForceFlags : Uint4B 51 | void anti_debug_by_PEB_HeapFlags(void) { 52 | PPEB peb = GetPEB(); 53 | PVOID heap = *(PDWORD)((PBYTE)peb + 0x18); 54 | PDWORD heapFlags = (PDWORD)((PBYTE)heap + 0x40); 55 | PDWORD forceFlags = (PDWORD)((PBYTE)heap + 0x44); 56 | 57 | if (*heapFlags & ~HEAP_GROWABLE || *forceFlags != 0) { 58 | MessageBoxA(NULL, "debugger detected", "PEB->_HEAP->HeapFlags,ForceFlags", MB_OK); 59 | } 60 | } 61 | 62 | BOOL volatile VEH_INT1_isDebuggerPresent = FALSE; 63 | 64 | LONG CALLBACK VEH_INT1_UnhandledExceptionFilter(_In_ EXCEPTION_POINTERS *lpEP) { 65 | switch (lpEP->ExceptionRecord->ExceptionCode) { 66 | case EXCEPTION_SINGLE_STEP: 67 | // handle single step exception if not handled by debugger 68 | VEH_INT1_isDebuggerPresent = FALSE; 69 | return EXCEPTION_CONTINUE_EXECUTION; 70 | default: 71 | return EXCEPTION_CONTINUE_SEARCH; 72 | } 73 | } 74 | 75 | void anti_debug_by_VEH_INT1(void) { 76 | VEH_INT1_isDebuggerPresent = TRUE; 77 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter 78 | SetUnhandledExceptionFilter(VEH_INT1_UnhandledExceptionFilter); 79 | // https://docs.microsoft.com/zh-cn/windows/win32/api/errhandlingapi/nf-errhandlingapi-addvectoredexceptionhandler?redirectedfrom=MSDN 80 | // https://docs.microsoft.com/en-us/windows/win32/api/winnt/nc-winnt-pvectored_exception_handler 81 | RaiseInt1(); 82 | if (VEH_INT1_isDebuggerPresent == TRUE) { 83 | MessageBoxA(NULL, "debugger detected", "VEH INT1", MB_OK); 84 | } 85 | } 86 | 87 | BOOL volatile VEH_INT3_isDebuggerPresent = FALSE; 88 | 89 | LONG CALLBACK VEH_INT3_UnhandledExceptionFilter(_In_ EXCEPTION_POINTERS *lpEP) { 90 | switch (lpEP->ExceptionRecord->ExceptionCode) { 91 | case EXCEPTION_BREAKPOINT: 92 | // handle single step exception if not handled by debugger 93 | VEH_INT3_isDebuggerPresent = FALSE; 94 | lpEP->ContextRecord->Eip += 1; 95 | return EXCEPTION_CONTINUE_EXECUTION; 96 | default: 97 | return EXCEPTION_CONTINUE_SEARCH; 98 | } 99 | } 100 | 101 | void anti_debug_by_VEH_INT3(void) { 102 | VEH_INT3_isDebuggerPresent = TRUE; 103 | SetUnhandledExceptionFilter(VEH_INT3_UnhandledExceptionFilter); 104 | RaiseInt3(); 105 | if (VEH_INT3_isDebuggerPresent == TRUE) { 106 | MessageBoxA(NULL, "debugger detected", "VEH INT3", MB_OK); 107 | } 108 | } 109 | 110 | // TODO: somehow not work on x32dbg 111 | BOOL VEH_OutputDebugStringException_isDebuggerPresent = FALSE; 112 | 113 | LONG CALLBACK VEH_OutputDebugStringException_UnhandledExceptionFilter(_In_ EXCEPTION_POINTERS *lpEP) { 114 | switch (lpEP->ExceptionRecord->ExceptionCode) { 115 | case DBG_PRINTEXCEPTION_WIDE_C: 116 | // handle exception if not handled by debugger 117 | VEH_OutputDebugStringException_isDebuggerPresent = FALSE; 118 | return EXCEPTION_CONTINUE_EXECUTION; 119 | default: 120 | return EXCEPTION_CONTINUE_SEARCH; 121 | } 122 | } 123 | 124 | // TODO: somehow not work on x32dbg 125 | void anti_debug_by_VEH_OutputDebugException(void) { 126 | ULONG_PTR args[4] = {0, 0, 0, 0}; 127 | args[0] = (ULONG_PTR)wcslen(L"debug") + 1; 128 | args[1] = (ULONG_PTR)L"debug"; 129 | AddVectoredExceptionHandler(0, VEH_OutputDebugStringException_UnhandledExceptionFilter); 130 | VEH_OutputDebugStringException_isDebuggerPresent = TRUE; 131 | RaiseException(DBG_PRINTEXCEPTION_WIDE_C, 0, 4, args); 132 | RemoveVectoredExceptionHandler(VEH_OutputDebugStringException_UnhandledExceptionFilter); 133 | if (VEH_OutputDebugStringException_isDebuggerPresent == TRUE) { 134 | MessageBoxA(NULL, "debugger detected", "OutputDebugString", MB_OK); 135 | } 136 | } 137 | 138 | LONG CALLBACK VEH_INVALID_HANDLE_UnhandledExceptionFilter(_In_ EXCEPTION_POINTERS *lpEP) { 139 | switch (lpEP->ExceptionRecord->ExceptionCode) { 140 | case EXCEPTION_INVALID_HANDLE: 141 | // if debug present 142 | MessageBoxA(NULL, "debugger detected", "INVALID HANDLE", MB_OK); 143 | return EXCEPTION_CONTINUE_EXECUTION; 144 | default: 145 | return EXCEPTION_CONTINUE_SEARCH; 146 | } 147 | } 148 | 149 | void anti_debug_by_VEH_INVALID_HANDLE(void) { 150 | AddVectoredExceptionHandler(0, VEH_INVALID_HANDLE_UnhandledExceptionFilter); 151 | CloseHandle((HANDLE)0xBAAD); 152 | RemoveVectoredExceptionHandler(VEH_INVALID_HANDLE_UnhandledExceptionFilter); 153 | } 154 | 155 | void anti_debug_by_CheckRemoteDebuggerPresent(void) { 156 | BOOL isRemoteDebuggerPresent = FALSE; 157 | if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &isRemoteDebuggerPresent)) { 158 | if (isRemoteDebuggerPresent == TRUE) { 159 | MessageBoxA(NULL, "debugger detected", "CheckRemoteDebuggerPresent", MB_OK); 160 | } 161 | } 162 | } 163 | 164 | void anti_debug_by_NtQueryInformationProcess(void) { 165 | HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); 166 | if (ntdll == NULL) { 167 | abort(); 168 | } 169 | 170 | FARPROC ntQueryInfoProc = GetProcAddress(ntdll, "NtQueryInformationProcess"); 171 | if (ntQueryInfoProc == NULL) { 172 | abort(); 173 | } 174 | 175 | DWORD isDebuggerPresent = FALSE; 176 | NTSTATUS status = ntQueryInfoProc(GetCurrentProcess(), ProcessDebugPort, &isDebuggerPresent, sizeof(DWORD), NULL); 177 | if (status == 0 && isDebuggerPresent) { 178 | MessageBoxA(NULL, "debugger detected", "NtQueryInformationProcess", MB_OK); 179 | return; 180 | } 181 | 182 | // ... ProcessDebugObject 183 | // ... ProcessDebugFlags 184 | } 185 | 186 | #ifdef UNICODE 187 | # define MY_STRCMP wcscmp 188 | #else 189 | # define MY_STRCMP strcmp 190 | #endif 191 | 192 | void anti_debug_by_NtQueryInformationProcess_BasicInformation(void) { 193 | HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); 194 | if (ntdll == NULL) { 195 | abort(); 196 | } 197 | 198 | FARPROC ntQueryInfoProc = GetProcAddress(ntdll, "NtQueryInformationProcess"); 199 | if (ntQueryInfoProc == NULL) { 200 | abort(); 201 | } 202 | 203 | PROCESS_BASIC_INFORMATION info; 204 | NTSTATUS status = ntQueryInfoProc(GetCurrentProcess(), ProcessBasicInformation, &info, sizeof(info), NULL); 205 | if (status == 0) { 206 | HANDLE hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 207 | if (hProcSnap == NULL) { 208 | abort(); 209 | } 210 | 211 | PROCESSENTRY32 pe32; 212 | pe32.dwSize = sizeof(PROCESSENTRY32); 213 | if (!Process32First(hProcSnap, &pe32)) { 214 | abort(); 215 | } 216 | 217 | do { 218 | if (pe32.th32ProcessID == info.InheritedFromUniqueProcessId) { 219 | if (MY_STRCMP(TEXT("devenv.exe"), pe32.szExeFile) == 0 || MY_STRCMP(TEXT("x32dbg.exe"), pe32.szExeFile) == 0 || 220 | MY_STRCMP(TEXT("x64dbg.exe"), pe32.szExeFile) == 0 || MY_STRCMP(TEXT("ollydbg.exe"), pe32.szExeFile) == 0) { 221 | MessageBoxA(NULL, "debugger detected", "BasicInformation", MB_OK); 222 | CloseHandle(hProcSnap); 223 | return; 224 | } 225 | } 226 | } while (Process32Next(hProcSnap, &pe32)); 227 | } 228 | } 229 | 230 | // detect hardware breakpoint 231 | void anti_debug_by_DebugRegister(void) { 232 | CONTEXT ctx; 233 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 234 | if (GetThreadContext(GetCurrentThread(), &ctx)) { 235 | if (ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0) { 236 | MessageBoxA(NULL, "debugger detected", "Dr0-Dr3", MB_OK); 237 | } 238 | } 239 | } 240 | 241 | typedef NTSTATUS(NTAPI *pfnNtSetInformationThread)(_In_ HANDLE ThreadHandle, _In_ ULONG ThreadInformationClass, 242 | _In_ PVOID ThreadInformation, _In_ ULONG ThreadInformationLength); 243 | void anti_debug_by_HideFromDebugger(void) { 244 | HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); 245 | if (ntdll == NULL) { 246 | abort(); 247 | } 248 | 249 | pfnNtSetInformationThread ntSetInfoThread = 250 | (pfnNtSetInformationThread)GetProcAddress(ntdll, "NtSetInformationThread"); 251 | if (ntSetInfoThread == NULL) { 252 | abort(); 253 | } 254 | 255 | ntSetInfoThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0); 256 | // ... NtCreateThreadEx THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 257 | } 258 | 259 | // TODO: somehow not work on windows 10, need more test. 260 | void anti_debug_by_SetLastError(void) { 261 | SetLastError(0x1234); 262 | OutputDebugString(TEXT("Hello Debugger!")); 263 | if (GetLastError() == 0x1234) { 264 | MessageBoxA(NULL, "debugger detected", "Set/Get LastError", MB_OK); 265 | } 266 | } 267 | 268 | // detect 0xcc interrupt code 269 | void anti_debug_by_SoftwareBreakPoint(PBYTE addr) { 270 | if (*addr == 0xcc) { 271 | MessageBoxA(NULL, "debugger detected", "SoftwareBreakpoint", MB_OK); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /packer6/anti_debug.h: -------------------------------------------------------------------------------- 1 | #ifndef ANTI_DEBUG_H_ 2 | #define ANTI_DEBUG_H_ 3 | 4 | #include 5 | 6 | void anti_debug_by_isDebuggerPresent(void); 7 | void anti_debug_by_PEB_BeingDebugged(void); 8 | void anti_debug_by_RtlGetNtGlobalFlags(void); 9 | void anti_debug_by_PEB_HeapFlags(void); 10 | void anti_debug_by_CheckRemoteDebuggerPresent(void); 11 | void anti_debug_by_NtQueryInformationProcess(void); 12 | void anti_debug_by_NtQueryInformationProcess_BasicInformation(void); 13 | void anti_debug_by_DebugRegister(void); 14 | void anti_debug_by_HideFromDebugger(void); 15 | void anti_debug_by_VEH_INT1(void); 16 | void anti_debug_by_VEH_INT3(void); 17 | void anti_debug_by_VEH_INVALID_HANDLE(void); 18 | // TODO: somehow not work on x32dbg 19 | void anti_debug_by_VEH_OutputDebugException(void); 20 | // TODO: somehow not work on windows 10/MinGW, need more test. 21 | void anti_debug_by_SetLastError(void); 22 | void anti_debug_by_SoftwareBreakPoint(PBYTE addr); 23 | 24 | #endif -------------------------------------------------------------------------------- /packer6/assembly_utils.c: -------------------------------------------------------------------------------- 1 | #include "assembly_utils.h" 2 | 3 | PPEB GetPEB(void) { 4 | PPEB ptr; 5 | asm("movl %%fs:0x30, %0" : "=r"(ptr)); 6 | return ptr; 7 | } 8 | 9 | void RaiseInt1(void) { 10 | asm("int $1"); 11 | } 12 | 13 | void RaiseInt3(void) { 14 | asm("int $3"); 15 | } -------------------------------------------------------------------------------- /packer6/assembly_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef ASSEMBLY_UTILS_H_ 2 | #define ASSEMBLY_UTILS_H_ 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | PPEB GetPEB(void); 10 | void RaiseInt1(void); 11 | void RaiseInt3(void); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif -------------------------------------------------------------------------------- /packer6/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import lief 3 | from subprocess import STDOUT, CalledProcessError, check_output 4 | 5 | 6 | # %% 7 | # compile origin demo program 8 | try: 9 | check_output('gcc example.c -m32 -O2 -o example.exe', shell=True, stderr=STDOUT) 10 | except CalledProcessError as e: 11 | print(f'[!] demo program compilation failed, {e.stdout.decode()}') 12 | raise 13 | 14 | binary = lief.PE.parse('example.exe') 15 | print('[+] compile origin demo program success.') 16 | 17 | # %% 18 | compile_args = [ 19 | 'loader.c', 20 | 'load_pe.c', 21 | 'anti_debug.c', 22 | 'assembly_utils.c', 23 | '-m32', 24 | '-O2', 25 | '-Wall', 26 | '-Wl,--entry=__start', 27 | '-nodefaultlibs', 28 | '-nostartfiles', 29 | '-lkernel32', 30 | '-luser32', 31 | '-lmsvcrt', 32 | '-o', 33 | 'loader.exe' 34 | ] 35 | 36 | try: 37 | check_output(' '.join(['gcc', *compile_args]), shell=True, stderr=STDOUT) 38 | print('[+] compile loader program success.') 39 | except CalledProcessError as e: 40 | print(f'[!] loader compilation failed, {e.stdout.decode()}') 41 | raise 42 | 43 | loader = lief.PE.parse('loader.exe') 44 | 45 | # %% 46 | # add packed section 47 | with open('example.exe', 'rb') as f: 48 | packed_section = lief.PE.Section('.packed') 49 | packed_section.content = list(f.read()) 50 | packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ | 51 | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 52 | loader.add_section(packed_section) 53 | 54 | # build output binary 55 | builder = lief.PE.Builder(loader) 56 | builder.build() 57 | builder.write('packed.exe') 58 | print('[+] create packed binary success.') 59 | -------------------------------------------------------------------------------- /packer6/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer6/load_pe.c: -------------------------------------------------------------------------------- 1 | #include "load_pe.h" 2 | #include 3 | #include 4 | 5 | void *load_PE(char *PE_data) { 6 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 7 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 8 | 9 | // extract information from PE header 10 | DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; 11 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 12 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 13 | 14 | // allocate memory 15 | // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc 16 | char *p_image_base = (char *)VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 17 | if (p_image_base == NULL) { 18 | return NULL; 19 | } 20 | 21 | // copy PE headers in memory 22 | memcpy(p_image_base, PE_data, size_of_headers); 23 | 24 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 25 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 26 | 27 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 28 | // calculate the VA we need to copy the content, from the RVA 29 | // section[i].VirtualAddress is a RVA, mind it 30 | char *dest = p_image_base + sections[i].VirtualAddress; 31 | 32 | // check if there is Raw data to copy 33 | if (sections[i].SizeOfRawData > 0) { 34 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 35 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 36 | } else { 37 | memset(dest, 0, sections[i].Misc.VirtualSize); 38 | } 39 | } 40 | 41 | fix_iat(p_image_base, p_NT_headers); 42 | fix_base_reloc(p_image_base, p_NT_headers); 43 | 44 | // Set permission for the PE header to read only 45 | DWORD oldProtect; 46 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 47 | 48 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 49 | char *dest = p_image_base + sections[i].VirtualAddress; 50 | DWORD s_perm = sections[i].Characteristics; 51 | DWORD v_perm = 0; // flags are not the same between virtual protect and the section header 52 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 53 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 54 | } else { 55 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 56 | } 57 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 58 | } 59 | 60 | return (void *)(p_image_base + entry_point_RVA); 61 | } 62 | 63 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 64 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 65 | 66 | // load the address of the import descriptors array 67 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 68 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 69 | 70 | // this array is null terminated 71 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 72 | // Get the name of the dll, and import it 73 | char *module_name = p_image_base + import_descriptors[i].Name; 74 | HMODULE import_module = LoadLibraryA(module_name); 75 | if (import_module == NULL) { 76 | // panic! 77 | ExitProcess(255); 78 | } 79 | 80 | // the lookup table points to function names or ordinals => it is the IDT 81 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 82 | 83 | // the address table is a copy of the lookup table at first 84 | // but we put the addresses of the loaded function inside => that's the IAT 85 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 86 | 87 | // null terminated array, again 88 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 89 | void *function_handle = NULL; 90 | 91 | // Check the lookup table for the addresses of the function name to import 92 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 93 | 94 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 95 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 96 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 97 | // this struct points to the ASCII function name 98 | char *funct_name = (char *)&(image_import->Name); 99 | // get that function address from it's module and name 100 | function_handle = (void *)GetProcAddress(import_module, funct_name); 101 | } else { 102 | // import by ordinal, directly 103 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 104 | } 105 | 106 | if (function_handle == NULL) { 107 | ExitProcess(255); 108 | } 109 | 110 | // change the IAT, and put the function address inside. 111 | address_table[i].u1.Function = (DWORD)function_handle; 112 | } 113 | } 114 | } 115 | 116 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 117 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 118 | 119 | // this is how much we shifted the ImageBase 120 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 121 | 122 | // if there is a relocation table, and we actually shifted the ImageBase 123 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 124 | 125 | // calculate the relocation table address 126 | IMAGE_BASE_RELOCATION *p_reloc = 127 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 128 | 129 | // once again, a null terminated array 130 | while (p_reloc->VirtualAddress != 0) { 131 | 132 | // how any relocation in this block 133 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 134 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 135 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 136 | WORD *fixups = (WORD *)(p_reloc + 1); 137 | for (size_t i = 0; i < size; ++i) { 138 | // type is the first 4 bits of the relocation word 139 | int type = fixups[i] >> 12; 140 | // offset is the last 12 bits 141 | int offset = fixups[i] & 0x0fff; 142 | // this is the address we are going to change 143 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 144 | 145 | // there is only one type used that needs to make a change 146 | switch (type) { 147 | case IMAGE_REL_BASED_HIGHLOW: 148 | *change_addr += delta_VA_reloc; 149 | break; 150 | default: 151 | break; 152 | } 153 | } 154 | 155 | // switch to the next relocation block, based on the size 156 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /packer6/load_pe.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_PE_H_ 2 | #define LOAD_PE_H_ 3 | #include 4 | #include 5 | 6 | void *load_PE(char *PE_data); 7 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 8 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 9 | 10 | #endif -------------------------------------------------------------------------------- /packer6/loader.c: -------------------------------------------------------------------------------- 1 | #include "anti_debug.h" 2 | #include "load_pe.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int _start(void) { 9 | // too powerful, can't debug 10 | // anti_debug_by_HideFromDebugger(); 11 | // anti_debug_by_RtlGetNtGlobalFlags(); 12 | // anti_debug_by_isDebuggerPresent(); 13 | // anti_debug_by_PEB_BeingDebugged(); 14 | // anti_debug_by_PEB_HeapFlags(); 15 | // anti_debug_by_CheckRemoteDebuggerPresent(); 16 | // anti_debug_by_NtQueryInformationProcess(); 17 | // anti_debug_by_NtQueryInformationProcess_BasicInformation(); 18 | // anti_debug_by_DebugRegister(); 19 | // anti_debug_by_VEH_INT1(); 20 | // anti_debug_by_VEH_INT3(); 21 | // TODO: somehow not work on x32dbg 22 | // anti_debug_by_VEH_OutputDebugException(); 23 | // anti_debug_by_VEH_INVALID_HANDLE(); 24 | // TODO: somehow not work on windows 10/MinGW, need more test. 25 | // anti_debug_by_SetLastError(); 26 | anti_debug_by_SoftwareBreakPoint((PBYTE)&load_PE); 27 | 28 | char *unpacker_VA = (char *)GetModuleHandleA(NULL); 29 | 30 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)unpacker_VA; 31 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(((char *)unpacker_VA) + p_DOS_header->e_lfanew); 32 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 33 | 34 | char *packed = NULL; 35 | char packed_section_name[] = ".packed"; 36 | 37 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 38 | if (strcmp((char *)sections[i].Name, packed_section_name) == 0) { 39 | packed = unpacker_VA + sections[i].VirtualAddress; 40 | break; 41 | } 42 | } 43 | 44 | if (packed != NULL) { 45 | void (*entrypoint)(void) = (void (*)(void))load_PE(packed); 46 | entrypoint(); 47 | } 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /packer7/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import logging 3 | import sys 4 | from os import path 5 | 6 | from lief import PE 7 | 8 | logging.basicConfig(format='[%(asctime)s] %(levelname)1s %(name)s: %(message)s', 9 | datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO) 10 | 11 | sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) 12 | from builder.cctarget import CCTarget 13 | 14 | # %% 15 | # compile origin demo program 16 | demo = CCTarget('example.exe') 17 | demo.add_c_flags('-m32', '-O2') 18 | demo.add_sources('example.c') 19 | demo.build() 20 | print('[+] compile origin demo program success.') 21 | 22 | 23 | # %% 24 | loader_target = CCTarget('loader.exe') 25 | loader_target.add_sources('loader.c', 'load_pe.c') 26 | loader_target.add_c_flags('-m32', '-O2', '-Wall') 27 | loader_target.add_ld_flags( 28 | '-Wl,--entry=__start', 29 | '-nodefaultlibs', 30 | '-nostartfiles', 31 | '-lkernel32', 32 | '-luser32', 33 | '-lmsvcrt' 34 | ) 35 | loader_target.build() 36 | print('[+] compile loader program success.') 37 | 38 | # %% 39 | # add packed section 40 | packed_section = PE.Section('.packed') 41 | with open(demo.target_binary_path, 'rb') as f: 42 | packed_section.content = list(f.read()) 43 | packed_section.characteristics = (PE.SECTION_CHARACTERISTICS.MEM_READ | 44 | PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 45 | 46 | # build output binary 47 | loader = PE.parse(loader_target.target_binary_path) 48 | loader.add_section(packed_section) 49 | builder = PE.Builder(loader) 50 | builder.build() 51 | builder.write('packed.exe') 52 | print('[+] create packed binary success.') 53 | -------------------------------------------------------------------------------- /packer7/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer7/junkcode.h: -------------------------------------------------------------------------------- 1 | #ifndef JUNK_CODE_H_ 2 | #define JUNK_CODE_H_ 3 | 4 | #define ANTI_LINEAR_DISASSEMBLE_ALGORITHM_1 asm("jmp next\n.byte 0xe8;\nnext:\n") 5 | #define ANTI_CONTROLFLOW_DISASSEMBLE_ALGORITHM_1 asm(".byte 0xeb,0xff,0xc0\ndec %eax\n") 6 | #define CALL_RET_JUNK asm( \ 7 | "call next;\n" \ 8 | "next:\n" \ 9 | "movl $continue,(%esp);\n" \ 10 | "ret;\n" \ 11 | "continue:\n" \ 12 | ) 13 | 14 | #endif -------------------------------------------------------------------------------- /packer7/load_pe.c: -------------------------------------------------------------------------------- 1 | #include "load_pe.h" 2 | #include 3 | #include 4 | 5 | void *load_PE(char *PE_data) { 6 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 7 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 8 | 9 | // extract information from PE header 10 | DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; 11 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 12 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 13 | 14 | // allocate memory 15 | // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc 16 | char *p_image_base = (char *)VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 17 | if (p_image_base == NULL) { 18 | return NULL; 19 | } 20 | 21 | // copy PE headers in memory 22 | memcpy(p_image_base, PE_data, size_of_headers); 23 | 24 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 25 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 26 | 27 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 28 | // calculate the VA we need to copy the content, from the RVA 29 | // section[i].VirtualAddress is a RVA, mind it 30 | char *dest = p_image_base + sections[i].VirtualAddress; 31 | 32 | // check if there is Raw data to copy 33 | if (sections[i].SizeOfRawData > 0) { 34 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 35 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 36 | } else { 37 | memset(dest, 0, sections[i].Misc.VirtualSize); 38 | } 39 | } 40 | 41 | fix_iat(p_image_base, p_NT_headers); 42 | fix_base_reloc(p_image_base, p_NT_headers); 43 | 44 | // Set permission for the PE header to read only 45 | DWORD oldProtect; 46 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 47 | 48 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 49 | char *dest = p_image_base + sections[i].VirtualAddress; 50 | DWORD s_perm = sections[i].Characteristics; 51 | DWORD v_perm = 0; // flags are not the same between virtual protect and the section header 52 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 53 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 54 | } else { 55 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 56 | } 57 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 58 | } 59 | 60 | return (void *)(p_image_base + entry_point_RVA); 61 | } 62 | 63 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 64 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 65 | 66 | // load the address of the import descriptors array 67 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 68 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 69 | 70 | // this array is null terminated 71 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 72 | // Get the name of the dll, and import it 73 | char *module_name = p_image_base + import_descriptors[i].Name; 74 | HMODULE import_module = LoadLibraryA(module_name); 75 | if (import_module == NULL) { 76 | // panic! 77 | ExitProcess(255); 78 | } 79 | 80 | // the lookup table points to function names or ordinals => it is the IDT 81 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 82 | 83 | // the address table is a copy of the lookup table at first 84 | // but we put the addresses of the loaded function inside => that's the IAT 85 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 86 | 87 | // null terminated array, again 88 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 89 | void *function_handle = NULL; 90 | 91 | // Check the lookup table for the addresses of the function name to import 92 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 93 | 94 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 95 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 96 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 97 | // this struct points to the ASCII function name 98 | char *funct_name = (char *)&(image_import->Name); 99 | // get that function address from it's module and name 100 | function_handle = (void *)GetProcAddress(import_module, funct_name); 101 | } else { 102 | // import by ordinal, directly 103 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 104 | } 105 | 106 | if (function_handle == NULL) { 107 | ExitProcess(255); 108 | } 109 | 110 | // change the IAT, and put the function address inside. 111 | address_table[i].u1.Function = (DWORD)function_handle; 112 | } 113 | } 114 | } 115 | 116 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 117 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 118 | 119 | // this is how much we shifted the ImageBase 120 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 121 | 122 | // if there is a relocation table, and we actually shifted the ImageBase 123 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 124 | 125 | // calculate the relocation table address 126 | IMAGE_BASE_RELOCATION *p_reloc = 127 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 128 | 129 | // once again, a null terminated array 130 | while (p_reloc->VirtualAddress != 0) { 131 | 132 | // how any relocation in this block 133 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 134 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 135 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 136 | WORD *fixups = (WORD *)(p_reloc + 1); 137 | for (size_t i = 0; i < size; ++i) { 138 | // type is the first 4 bits of the relocation word 139 | int type = fixups[i] >> 12; 140 | // offset is the last 12 bits 141 | int offset = fixups[i] & 0x0fff; 142 | // this is the address we are going to change 143 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 144 | 145 | // there is only one type used that needs to make a change 146 | switch (type) { 147 | case IMAGE_REL_BASED_HIGHLOW: 148 | *change_addr += delta_VA_reloc; 149 | break; 150 | default: 151 | break; 152 | } 153 | } 154 | 155 | // switch to the next relocation block, based on the size 156 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /packer7/load_pe.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_PE_H_ 2 | #define LOAD_PE_H_ 3 | #include 4 | #include 5 | 6 | void *load_PE(char *PE_data); 7 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 8 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 9 | 10 | #endif -------------------------------------------------------------------------------- /packer7/loader.c: -------------------------------------------------------------------------------- 1 | #include "load_pe.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "junkcode.h" 8 | 9 | int _start(void) { 10 | CALL_RET_JUNK; 11 | char *unpacker_VA = (char *)GetModuleHandleA(NULL); 12 | 13 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)unpacker_VA; 14 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(((char *)unpacker_VA) + p_DOS_header->e_lfanew); 15 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 16 | 17 | char *packed = NULL; 18 | char packed_section_name[] = ".packed"; 19 | 20 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 21 | if (strcmp((char *)sections[i].Name, packed_section_name) == 0) { 22 | packed = unpacker_VA + sections[i].VirtualAddress; 23 | break; 24 | } 25 | } 26 | 27 | if (packed != NULL) { 28 | void (*entrypoint)(void) = (void (*)(void))load_PE(packed); 29 | entrypoint(); 30 | } 31 | 32 | return 0; 33 | } -------------------------------------------------------------------------------- /packer8/README.md: -------------------------------------------------------------------------------- 1 | ## development requires 2 | 3 | **Windows only**. 4 | 5 | - MSYS2 6 | - `mingw-w64-clang-i686-toolchain` install by `pacman` package manager in MSYS2 command prompt 7 | - `mingw-w64-i686-toolchain` install by `pacman` package manager in MSYS2 command prompt 8 | 9 | Add `/path/to/your/msys64/clang32/bin` and `/path/to/your/msys64/mingw32/bin` to your PATH environment variable. 10 | 11 | ## version pinning 12 | 13 | LLVM/clang version: 14 | 15 | ```plain 16 | clang version 13.0.0 17 | Target: i686-w64-windows-gnu 18 | Thread model: posix 19 | InstalledDir: C:/Users/weakptr/scoop/apps/msys2/current/clang32/bin 20 | ``` 21 | 22 | ## build obfuscator module 23 | 24 | ```shell 25 | cd obfuscator 26 | mkdir build 27 | cd build 28 | cmake .. -G Ninaj -DCMAKE_C_COMPILER=clang 29 | cmake --build . --config Debug 30 | # if everything is ok, bcf.dll should appear in packer8/obfuscator/build 31 | cp bcf.dll ../../ 32 | ``` 33 | 34 | ## build packed program 35 | 36 | ```shell 37 | cd packer8 38 | python build.py 39 | ``` 40 | 41 | ## FAQ 42 | 43 | Q: build.py shown unknown error 0xC1 44 | A: cause by missing DLL dependencies, please correct your PATH 45 | -------------------------------------------------------------------------------- /packer8/build.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import logging 3 | import subprocess 4 | import sys 5 | from os import path 6 | 7 | from lief import PE 8 | from subprocess import run 9 | 10 | logging.basicConfig(format='[%(asctime)s] %(levelname)1s %(name)s: %(message)s', 11 | datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO) 12 | 13 | sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) 14 | from builder.cctarget import CCTarget 15 | 16 | # %% 17 | # compile origin demo program 18 | demo = CCTarget('example.exe', 'clang', 'clang') 19 | demo.add_c_flags('-m32', '-O2') 20 | demo.add_sources('example.c') 21 | demo.build() 22 | print('[+] compile origin demo program success.') 23 | 24 | # %% 25 | 26 | if not path.exists('bcf.dll'): 27 | print(f'[!] you must build obfuscator plugin first') 28 | print(f' see {path.abspath(path.dirname(__file__))}/README.md for how to build LLVM obfuscator module.') 29 | exit(1) 30 | 31 | proc = run( 32 | 'clang ' 33 | '-v ' 34 | '-Xclang ' 35 | '-load ' 36 | '-Xclang ' 37 | 'bcf.dll ' 38 | # explicitly disable new pass manager 39 | '-fno-experimental-new-pass-manager ' 40 | '-m32 ' 41 | '-O0 ' 42 | '-Wall ' 43 | 'loader.c ' 44 | 'load_pe.c ' 45 | '-Wl,--entry=__start ' 46 | '-nodefaultlibs ' 47 | '-nostartfiles ' 48 | '-lkernel32 ' 49 | '-luser32 ' 50 | '-lmsvcrt ' 51 | '-o ' 52 | 'build/bin/loader.exe', shell=True) 53 | if proc.returncode != 0: 54 | raise Exception("compile failed") 55 | 56 | print('[+] compile loader program success.') 57 | 58 | # %% 59 | # add packed section 60 | packed_section = PE.Section('.packed') 61 | with open(demo.target_binary_path, 'rb') as f: 62 | packed_section.content = list(f.read()) 63 | packed_section.characteristics = (PE.SECTION_CHARACTERISTICS.MEM_READ | 64 | PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 65 | 66 | # build output binary 67 | loader = PE.parse('build/bin/loader.exe') 68 | loader.add_section(packed_section) 69 | builder = PE.Builder(loader) 70 | builder.build() 71 | builder.write('packed.exe') 72 | print('[+] create packed binary success.') 73 | -------------------------------------------------------------------------------- /packer8/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer8/load_pe.c: -------------------------------------------------------------------------------- 1 | #include "load_pe.h" 2 | #include 3 | #include 4 | 5 | void *load_PE(char *PE_data) { 6 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; 7 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); 8 | 9 | // extract information from PE header 10 | DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; 11 | DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; 12 | DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; 13 | 14 | // allocate memory 15 | // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc 16 | char *p_image_base = (char *)VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 17 | if (p_image_base == NULL) { 18 | return NULL; 19 | } 20 | 21 | // copy PE headers in memory 22 | memcpy(p_image_base, PE_data, size_of_headers); 23 | 24 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 25 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 26 | 27 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 28 | // calculate the VA we need to copy the content, from the RVA 29 | // section[i].VirtualAddress is a RVA, mind it 30 | char *dest = p_image_base + sections[i].VirtualAddress; 31 | 32 | // check if there is Raw data to copy 33 | if (sections[i].SizeOfRawData > 0) { 34 | // We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file 35 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 36 | } else { 37 | memset(dest, 0, sections[i].Misc.VirtualSize); 38 | } 39 | } 40 | 41 | fix_iat(p_image_base, p_NT_headers); 42 | fix_base_reloc(p_image_base, p_NT_headers); 43 | 44 | // Set permission for the PE header to read only 45 | DWORD oldProtect; 46 | VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 47 | 48 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { 49 | char *dest = p_image_base + sections[i].VirtualAddress; 50 | DWORD s_perm = sections[i].Characteristics; 51 | DWORD v_perm = 0; // flags are not the same between virtual protect and the section header 52 | if (s_perm & IMAGE_SCN_MEM_EXECUTE) { 53 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 54 | } else { 55 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 56 | } 57 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 58 | } 59 | 60 | return (void *)(p_image_base + entry_point_RVA); 61 | } 62 | 63 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 64 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 65 | 66 | // load the address of the import descriptors array 67 | IMAGE_IMPORT_DESCRIPTOR *import_descriptors = 68 | (IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 69 | 70 | // this array is null terminated 71 | for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 72 | // Get the name of the dll, and import it 73 | char *module_name = p_image_base + import_descriptors[i].Name; 74 | HMODULE import_module = LoadLibraryA(module_name); 75 | if (import_module == NULL) { 76 | // panic! 77 | ExitProcess(255); 78 | } 79 | 80 | // the lookup table points to function names or ordinals => it is the IDT 81 | IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); 82 | 83 | // the address table is a copy of the lookup table at first 84 | // but we put the addresses of the loaded function inside => that's the IAT 85 | IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); 86 | 87 | // null terminated array, again 88 | for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { 89 | void *function_handle = NULL; 90 | 91 | // Check the lookup table for the addresses of the function name to import 92 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 93 | 94 | if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 95 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 96 | IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); 97 | // this struct points to the ASCII function name 98 | char *funct_name = (char *)&(image_import->Name); 99 | // get that function address from it's module and name 100 | function_handle = (void *)GetProcAddress(import_module, funct_name); 101 | } else { 102 | // import by ordinal, directly 103 | function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); 104 | } 105 | 106 | if (function_handle == NULL) { 107 | ExitProcess(255); 108 | } 109 | 110 | // change the IAT, and put the function address inside. 111 | address_table[i].u1.Function = (DWORD)function_handle; 112 | } 113 | } 114 | } 115 | 116 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { 117 | IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; 118 | 119 | // this is how much we shifted the ImageBase 120 | DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; 121 | 122 | // if there is a relocation table, and we actually shifted the ImageBase 123 | if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 124 | 125 | // calculate the relocation table address 126 | IMAGE_BASE_RELOCATION *p_reloc = 127 | (IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 128 | 129 | // once again, a null terminated array 130 | while (p_reloc->VirtualAddress != 0) { 131 | 132 | // how any relocation in this block 133 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 134 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; 135 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 136 | WORD *fixups = (WORD *)(p_reloc + 1); 137 | for (size_t i = 0; i < size; ++i) { 138 | // type is the first 4 bits of the relocation word 139 | int type = fixups[i] >> 12; 140 | // offset is the last 12 bits 141 | int offset = fixups[i] & 0x0fff; 142 | // this is the address we are going to change 143 | DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); 144 | 145 | // there is only one type used that needs to make a change 146 | switch (type) { 147 | case IMAGE_REL_BASED_HIGHLOW: 148 | *change_addr += delta_VA_reloc; 149 | break; 150 | default: 151 | break; 152 | } 153 | } 154 | 155 | // switch to the next relocation block, based on the size 156 | p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /packer8/load_pe.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_PE_H_ 2 | #define LOAD_PE_H_ 3 | #include 4 | #include 5 | 6 | void *load_PE(char *PE_data); 7 | void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 8 | void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); 9 | 10 | #endif -------------------------------------------------------------------------------- /packer8/loader.c: -------------------------------------------------------------------------------- 1 | #include "load_pe.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int _start(void) { 8 | char *unpacker_VA = (char *)GetModuleHandleA(NULL); 9 | 10 | IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)unpacker_VA; 11 | IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(((char *)unpacker_VA) + p_DOS_header->e_lfanew); 12 | IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); 13 | 14 | char *packed = NULL; 15 | char packed_section_name[] = ".packed"; 16 | 17 | for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { 18 | if (strcmp((char *)sections[i].Name, packed_section_name) == 0) { 19 | packed = unpacker_VA + sections[i].VirtualAddress; 20 | break; 21 | } 22 | } 23 | 24 | if (packed != NULL) { 25 | void (*entrypoint)(void) = (void (*)(void))load_PE(packed); 26 | entrypoint(); 27 | } 28 | 29 | return 0; 30 | } -------------------------------------------------------------------------------- /packer8/obfuscator/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | ColumnLimit: 120 3 | NamespaceIndentation: All -------------------------------------------------------------------------------- /packer8/obfuscator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.4) 2 | project(bcf) 3 | find_package(LLVM REQUIRED CONFIG) 4 | 5 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 6 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 7 | 8 | include_directories(${LLVM_INCLUDE_DIRS}) 9 | separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) 10 | add_definitions(${LLVM_DEFINITIONS_LIST}) 11 | 12 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 13 | include(AddLLVM) 14 | add_llvm_library(bcf MODULE bcf.cpp PLUGIN_TOOL opt) 15 | -------------------------------------------------------------------------------- /packer8/obfuscator/bcf.cpp: -------------------------------------------------------------------------------- 1 | // LLVM include 2 | #include "llvm/ADT/SmallVector.h" 3 | #include "llvm/ADT/Statistic.h" 4 | #include "llvm/ADT/Twine.h" 5 | #include "llvm/CodeGen/ISDOpcodes.h" 6 | #include "llvm/IR/BasicBlock.h" 7 | #include "llvm/IR/Constants.h" 8 | #include "llvm/IR/Function.h" 9 | #include "llvm/IR/GlobalValue.h" 10 | #include "llvm/IR/InstrTypes.h" 11 | #include "llvm/IR/Instruction.h" 12 | #include "llvm/IR/Instructions.h" 13 | #include "llvm/IR/LLVMContext.h" 14 | #include "llvm/IR/LegacyPassManager.h" 15 | #include "llvm/IR/Module.h" 16 | #include "llvm/IR/Type.h" 17 | #include "llvm/IR/User.h" 18 | #include "llvm/IR/Value.h" 19 | #include "llvm/Pass.h" 20 | #include "llvm/Support/Casting.h" 21 | #include "llvm/Support/CommandLine.h" 22 | #include "llvm/Support/Debug.h" 23 | #include "llvm/Support/raw_ostream.h" 24 | #include "llvm/Transforms/IPO.h" 25 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 26 | #include "llvm/Transforms/Utils/BasicBlockUtils.h" 27 | #include "llvm/Transforms/Utils/Cloning.h" 28 | #include "llvm/Transforms/Utils/ValueMapper.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | using namespace std; 35 | using namespace llvm; 36 | 37 | namespace { 38 | struct BCFPass : public FunctionPass { 39 | static char ID; 40 | BCFPass() : FunctionPass(ID) {} 41 | bool runOnFunction(Function &F) override { 42 | if (!isObfuscateable(F)) { 43 | errs() << "function " << F.getName() << " is not obfuscateable\n"; 44 | return false; 45 | } 46 | 47 | list blocks; 48 | for (BasicBlock &block : F) { 49 | blocks.push_back(&block); 50 | } 51 | 52 | for (BasicBlock *block : blocks) { 53 | // 原始块分割为三个基本块:entry、original、terminator 54 | // 通过两个恒真条件连接 55 | auto entryBB = block; 56 | auto originalBB = entryBB->splitBasicBlock(entryBB->getFirstNonPHIOrDbgOrLifetime(), Twine("original")); 57 | auto terminatorBB = originalBB->splitBasicBlock(--originalBB->end(), Twine("terminator")); 58 | 59 | // 构造伪造块 60 | // 这一步已经构造好了 altered 跳转 original 61 | auto alteredBB = createAlteredBB(originalBB, F); 62 | 63 | // 清理 terminator,重新构造跳转关系 64 | entryBB->getTerminator()->eraseFromParent(); 65 | originalBB->getTerminator()->eraseFromParent(); 66 | 67 | // 构造恒真条件,从 entry 跳转到 original 68 | auto lhs = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1); 69 | auto rhs = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1); 70 | auto condition = new ICmpInst(*entryBB, ICmpInst::ICMP_EQ, lhs, rhs, Twine("condition")); 71 | BranchInst::Create(originalBB, alteredBB, (Value *)condition, entryBB); 72 | 73 | // 构造恒真条件,从 original 跳转到 terminator 74 | auto lhs2 = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1); 75 | auto rhs2 = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1); 76 | auto condition2 = new ICmpInst(*originalBB, ICmpInst::ICMP_EQ, lhs, rhs, Twine("condition2")); 77 | BranchInst::Create(terminatorBB, alteredBB, (Value *)condition, originalBB); 78 | } 79 | 80 | return false; 81 | } 82 | 83 | bool isObfuscateable(const Function &fn) { 84 | if (fn.isDeclaration()) { 85 | return false; 86 | } 87 | 88 | if (fn.hasAvailableExternallyLinkage()) { 89 | return false; 90 | } 91 | 92 | if (!isInvoke(fn)) { 93 | return false; 94 | } 95 | 96 | return true; 97 | } 98 | 99 | bool isInvoke(const Function &fn) { 100 | for (const BasicBlock &bb : fn) { 101 | if (isa(bb.getTerminator())) { 102 | return false; 103 | } 104 | } 105 | return true; 106 | } 107 | 108 | BasicBlock *createAlteredBB(BasicBlock *original, Function &F) { 109 | // 构造伪造块 110 | ValueToValueMapTy VMap; 111 | auto altered = CloneBasicBlock(original, VMap, Twine("altered"), &F); 112 | 113 | // 修复伪造块的调试信息和元数据 114 | // https://bbs.pediy.com/thread-266201.htm 115 | auto originalInstIt = original->begin(); 116 | for (auto &inst : *altered) { 117 | // NOTE: 118 | // 参考链接: https://bbs.pediy.com/thread-266201.htm 119 | // 120 | // ... 但是CloneBasicBlock函数进行的克隆并不是完全的克隆,第一他不会对指令的操作数进行替换,比如: 121 | // 122 | // ``` 123 | // orig: 124 | // %a = ... 125 | // %b = fadd %a, ... 126 | // 127 | // clone: 128 | // %a.clone = ... 129 | // %b.clone = fadd %a, ... ; Note that this references the old %a and 130 | // not %a.clone! 131 | // ``` 132 | // 133 | // 在clone出来的基本块中,fadd指令的操作数不是%a.clone,而是%a。 134 | // 所以之后要通过VMap对所有操作数进行映射,使其恢复正常: 135 | // 136 | for (auto opi = inst.op_begin(); opi != inst.op_end(); opi++) { 137 | Value *v = MapValue(*opi, VMap, RF_None, 0); 138 | if (v != 0) { 139 | *opi = v; 140 | } 141 | } 142 | 143 | // 第二,它不会对PHI Node进行任何处理,PHI Node的前驱块仍然是原始基本块的前驱块, 144 | // 但是新克隆出来的基本块并没有任何前驱块,所以我们要对PHI Node的前驱块进行remap: 145 | if (auto pn = dyn_cast(&inst)) { 146 | for (unsigned j = 0, e = pn->getNumIncomingValues(); j != e; ++j) { 147 | Value *v = MapValue(pn->getIncomingBlock(j), VMap, RF_None, 0); 148 | if (v != 0) { 149 | pn->setIncomingBlock(j, cast(v)); 150 | } 151 | } 152 | } 153 | 154 | // 元数据 155 | SmallVector, 4> MDs; 156 | inst.getAllMetadata(MDs); 157 | 158 | // 修复调试 159 | inst.setDebugLoc(originalInstIt->getDebugLoc()); 160 | ++originalInstIt; 161 | } 162 | 163 | // 清理原来的 terminator,无条件从 altered 跳转到 original 164 | altered->getTerminator()->eraseFromParent(); 165 | BranchInst::Create(original, altered); 166 | 167 | return altered; 168 | } 169 | }; 170 | } // namespace 171 | 172 | char BCFPass::ID = 0; 173 | 174 | static RegisterPass X("bcf", "simple bogus control flow obfuscation", false, false); 175 | 176 | static void loadPass(const PassManagerBuilder &builder, legacy::PassManagerBase &pm) { pm.add(new BCFPass()); } 177 | static RegisterStandardPasses Ox(PassManagerBuilder::EP_OptimizerLast, loadPass); 178 | static RegisterStandardPasses O0(PassManagerBuilder::EP_EnabledOnOptLevel0, loadPass); 179 | -------------------------------------------------------------------------------- /packer9/packer.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from subprocess import STDOUT, CalledProcessError, check_call, check_output 3 | from typing import List 4 | import zlib 5 | from copy import copy 6 | from hashlib import sha256 7 | from io import BytesIO 8 | 9 | import png 10 | from pyDes import CBC, PAD_PKCS5, des 11 | 12 | 13 | def compress(filename: str) -> bytes: 14 | """compress file and return compressed data with original size (4byte) in front 15 | """ 16 | with open(filename, 'rb') as f: 17 | content = f.read() 18 | return struct.pack(' bytes: 22 | """encrypt data with des algorithm, return encrypted data 23 | """ 24 | cypher = des('ilF9eKRC', CBC, IV=b'zgOr6NtQ', padmode=PAD_PKCS5) 25 | return cypher.encrypt(data) 26 | 27 | 28 | def align(x, al): 29 | """ return aligned to """ 30 | return ((x+(al-1))//al)*al 31 | 32 | 33 | def padding(x, al): 34 | """ return padded to """ 35 | x += b'\0'*(align(len(x), al)-len(x)) 36 | return x 37 | 38 | 39 | def encode(data: bytes, width_in_pixels: int, depth: int) -> List[bytes]: 40 | width_in_byte = width_in_pixels*depth 41 | padded = padding(data, width_in_byte) 42 | rows: List[bytes] = [] 43 | 44 | for r in range(len(padded)//width_in_byte): 45 | row = bytearray() 46 | row_begin = r*width_in_byte 47 | row_end = row_begin+width_in_byte 48 | for c in range(len(padded[row_begin:row_end])//depth): 49 | pixel_begin = row_begin+c*depth 50 | pixel_end = pixel_begin+depth 51 | pixel: bytes = padded[pixel_begin:pixel_end] 52 | row.extend(copy(pixel)) 53 | 54 | rows.append(copy(bytes(row))) 55 | row.clear() 56 | 57 | return rows 58 | 59 | 60 | def hide_in_png_1(original_filename: str, output_filename: str, data: bytes): 61 | """hide data in png file method 1 62 | """ 63 | with open(output_filename, 'wb+') as f: 64 | with open(original_filename, 'rb') as src: 65 | f.write(src.read()) 66 | f.write(b'----HIDDEN----') 67 | f.write(data) 68 | 69 | 70 | def hide_in_png_2(original_filename: str, output_filename: str, data: bytes): 71 | """hide data in png file method 2 72 | """ 73 | width, height, rows, info = png.Reader(filename=original_filename).read() 74 | rows = [row for row in rows] 75 | 76 | # original size before padding so we can restore data from pixels 77 | hidden = struct.pack('I', img_content[IHDR_offset-4:IHDR_offset]) 96 | img_content[IHDR_offset+8:IHDR_offset+12] = struct.pack('>I', height) 97 | 98 | # recalculating CRC32 99 | chunk = img_content[IHDR_offset:IHDR_offset+4+chunk_size] 100 | checksum = zlib.crc32(chunk) 101 | img_content[IHDR_offset+4+chunk_size:IHDR_offset+4+chunk_size+4] = struct.pack('>I', checksum) 102 | 103 | # write final image 104 | with open(output_filename, 'wb+') as o: 105 | o.write(img_content) 106 | 107 | 108 | if __name__ == '__main__': 109 | try: 110 | check_call(['gcc', 'sample.c', '-Wall', '-O3', '-o', 'sample.exe'], stderr=STDOUT) 111 | except CalledProcessError as err: 112 | print('compilation fail,', err.output) 113 | exit(1) 114 | 115 | compressed = compress('sample.exe') 116 | encrypted = encrypt(compressed) 117 | hide_in_png_2('anime-girl.png', 'hide.png', encrypted) 118 | 119 | try: 120 | check_call(['windres', 'rsrc.rc', '-o', 'rsrc.o'], stderr=STDOUT) 121 | except CalledProcessError as err: 122 | print('compilation fail,', err.output) 123 | exit(1) 124 | -------------------------------------------------------------------------------- /packer9/rsrc.rc: -------------------------------------------------------------------------------- 1 | // windres rsrc.rc -o rsrc.o 2 | anime-girl.png RCDATA "hide.png" -------------------------------------------------------------------------------- /packer9/sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(void) { 7 | MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /packer9/verify.py: -------------------------------------------------------------------------------- 1 | import locale 2 | import struct 3 | import zlib 4 | from binascii import hexlify 5 | from copy import copy 6 | from subprocess import STDOUT, CalledProcessError, check_output 7 | from PIL import Image 8 | from hashlib import sha256 9 | 10 | import png 11 | from pyDes import CBC, PAD_PKCS5, des 12 | 13 | width, height, pixels, info = png.Reader(filename='hide.png').read_flat() 14 | hidden = pixels[width*height*4:] 15 | length, = struct.unpack('