├── .gitignore ├── CMakeLists.txt ├── Images ├── After.png └── Execution.png ├── LLVMBuild.txt ├── Noctilucence.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LLVM_LINK_COMPONENTS 2 | ${LLVM_TARGETS_TO_BUILD} 3 | BitReader 4 | IRReader 5 | Core 6 | Support 7 | Object 8 | Linker 9 | AsmPrinter 10 | CodeGen 11 | MC 12 | Passes 13 | AggressiveInstCombine 14 | Coroutines 15 | IPO 16 | InstCombine 17 | Instrumentation 18 | ScalarOpts 19 | TransformUtils 20 | Vectorize 21 | ObjCARCOpts 22 | Obfuscation 23 | SelectionDAG 24 | Target 25 | ) 26 | add_llvm_tool(Noctilucence 27 | Noctilucence.cpp 28 | DEPENDS 29 | intrinsics_gen 30 | ) 31 | target_link_libraries(Noctilucence PRIVATE xar) 32 | -------------------------------------------------------------------------------- /Images/After.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HikariObfuscator/Noctilucence/071386a7ecb26b86c4bc06d9f6027c5e81da8514/Images/After.png -------------------------------------------------------------------------------- /Images/Execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HikariObfuscator/Noctilucence/071386a7ecb26b86c4bc06d9f6027c5e81da8514/Images/Execution.png -------------------------------------------------------------------------------- /LLVMBuild.txt: -------------------------------------------------------------------------------- 1 | ;===- ./tools/Noctilucence/LLVMBuild.txt ---------------------------*- Conf -*--===; 2 | ; 3 | ; The LLVM Compiler Infrastructure 4 | ; 5 | ; This file is distributed under the University of Illinois Open Source 6 | ; License. See LICENSE.TXT for details. 7 | ; 8 | ;===------------------------------------------------------------------------===; 9 | ; 10 | ; This is an LLVMBuild description file for the components in this subdirectory. 11 | ; 12 | ; For more information on the LLVMBuild system, please see: 13 | ; 14 | ; http://llvm.org/docs/LLVMBuild.html 15 | ; 16 | ;===------------------------------------------------------------------------===; 17 | 18 | [component_0] 19 | type = Tool 20 | name = Noctilucence 21 | parent = Tools 22 | required_libraries = Analysis BitReader BinaryFormat Support Passes AggressiveInstCombine Coroutines IPO InstCombine Instrumentation Scalar Utils Vectorize ObjCARC Obfuscation all-targets 23 | -------------------------------------------------------------------------------- /Noctilucence.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LLVM Bitcode Recompiler 3 | Copyright (C) 2017 Zhang(https://github.com/Naville/) 4 | Exceptions: 5 | Anyone who has associated with ByteDance in anyway at any past, current, 6 | or future time point is prohibited from direct using this piece of 7 | software or create any derivative from it. 8 | 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU Affero General Public License as published 12 | by the Free Software Foundation, either version 3 of the License, or 13 | any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU Affero General Public License for more details. 19 | You should have received a copy of the GNU Affero General Public License 20 | along with this program. If not, see . 21 | */ 22 | #include "llvm/ADT/Triple.h" 23 | #include "llvm/Analysis/TargetLibraryInfo.h" 24 | //#include "llvm/CodeGen/CommandFlags.inc" 25 | #include "llvm/CodeGen/MachineModuleInfo.h" 26 | #include "llvm/CodeGen/TargetPassConfig.h" 27 | #include "llvm/IR/DataLayout.h" 28 | #include "llvm/IR/IRPrintingPasses.h" 29 | #include "llvm/IR/LLVMContext.h" 30 | #include "llvm/IR/LegacyPassManager.h" 31 | #include "llvm/IR/Module.h" 32 | #include "llvm/IR/Verifier.h" 33 | #include "llvm/IRReader/IRReader.h" 34 | #include "llvm/Linker/Linker.h" 35 | #include "llvm/Object/Archive.h" 36 | #include "llvm/Object/COFFImportFile.h" 37 | #include "llvm/Object/ELFObjectFile.h" 38 | #include "llvm/Object/MachOUniversal.h" 39 | #include "llvm/Object/ObjectFile.h" 40 | #include "llvm/Support/CommandLine.h" 41 | #include "llvm/Support/FileSystem.h" 42 | #include "llvm/Support/Format.h" 43 | #include "llvm/Support/Path.h" 44 | #include "llvm/Support/PrettyStackTrace.h" 45 | #include "llvm/Support/Program.h" 46 | #include "llvm/Support/ScopedPrinter.h" 47 | #include "llvm/Support/Signals.h" 48 | #include "llvm/Support/SourceMgr.h" 49 | #include "llvm/Support/TargetRegistry.h" 50 | #include "llvm/Support/TargetSelect.h" 51 | #include "llvm/Support/raw_os_ostream.h" 52 | #include "llvm/Target/TargetOptions.h" 53 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | using namespace llvm; 63 | using namespace llvm::sys; 64 | using namespace llvm::sys::fs; 65 | using namespace llvm::object; 66 | using namespace std; 67 | static cl::opt DumpIR("dump-ir", cl::init(false), cl::NotHidden, 68 | cl::desc("Dump Obfuscated IR.")); 69 | static cl::opt OutputFilename("o", cl::desc(""), 70 | cl::Required); 71 | static cl::opt InputFilename("i", cl::desc(""), 72 | cl::Required); 73 | 74 | static cl::opt LDPath("ldpath", cl::init("/usr/bin/ld"), 75 | cl::desc("Path to LD")); 76 | static cl::opt 77 | SDKROOTPATH("sdkroot", 78 | cl::init("/Applications/Xcode.app/Contents/Developer/Platforms/" 79 | "iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk"), 80 | cl::desc("SDKROOT")); 81 | 82 | #define VM(Name) \ 83 | errs() << "Verifying " << Name << "...\n"; \ 84 | bool dbgInfo; \ 85 | if (verifyModule(module, &errs(), &dbgInfo)) { \ 86 | SmallString<128> TmpModel; \ 87 | path::system_temp_directory(true, TmpModel); \ 88 | path::append(TmpModel, "HikariCrashTemporaryIR%%%%%.ll"); \ 89 | int fd = -1; \ 90 | SmallString<128> ResultPath; \ 91 | std::error_code EC = fs::createUniqueFile(TmpModel, fd, ResultPath); \ 92 | if (!EC && fd != -1) { \ 93 | raw_fd_ostream OS(fd, true); \ 94 | ModulePass *PMP = createPrintModulePass(OS); \ 95 | PMP->runOnModule(module); \ 96 | delete PMP; \ 97 | OS.flush(); \ 98 | errs() << "Written Crashed LLVM IR to:" << ResultPath.str() << "\n"; \ 99 | } else { \ 100 | errs() << "Creating Temporary File Failed, ErroCode:" << EC.message() \ 101 | << "\n"; \ 102 | } \ 103 | abort(); \ 104 | } 105 | 106 | string HandleMachOObjFile(MachOObjectFile *MachO, const char **argv) { 107 | if (MachO == nullptr) { 108 | report_fatal_error(make_error("MachO is NULL!"), false); 109 | } 110 | string ret = ""; 111 | SMDiagnostic diag; 112 | LLVMContext ctx; 113 | bool foundBC = false; 114 | bool foundCMD = false; 115 | Module module("Noctilucence", ctx); 116 | vector ldargs; 117 | ldargs.push_back(argv[0]); 118 | for (section_iterator iter = MachO->section_begin(), E = MachO->section_end(); 119 | iter != E; ++iter) { 120 | SectionRef section = *iter; 121 | StringRef sectionName; 122 | StringRef segmentName = 123 | MachO->getSectionFinalSegmentName(section.getRawDataRefImpl()); 124 | MachO->getSectionName(section.getRawDataRefImpl(), sectionName); 125 | if (segmentName.equals("__LLVM") && sectionName.equals("__bundle")) { 126 | foundBC = true; 127 | foundCMD = true; 128 | StringRef contents; 129 | section.getContents(contents); 130 | SmallString<128> TmpModel; 131 | path::system_temp_directory(true, TmpModel); 132 | path::append(TmpModel, "NoctilucenceTemporaryXAR%%%%%.xar"); 133 | Expected tfOrError = TempFile::create(TmpModel); 134 | if (tfOrError) { 135 | TempFile &tf = tfOrError.get(); 136 | raw_fd_ostream rf(tf.FD, false); 137 | rf << contents; 138 | rf.flush(); 139 | errs() << "Created xar temporary file at:" << tf.TmpName << "\n"; 140 | xar_t xar = xar_open(tf.TmpName.c_str(), READ); 141 | xar_iter_t xi = xar_iter_new(); 142 | for (xar_file_t xf = xar_file_first(xar, xi); xf; 143 | xf = xar_file_next(xi)) { 144 | char *buffer = nullptr; 145 | xar_extract_tobuffer(xar, xf, &buffer); 146 | assert(buffer != nullptr && "xar extraction failed"); 147 | char *sizeStr = xar_get_size(xar, xf); 148 | long int total = atol(sizeStr); 149 | free(sizeStr); 150 | 151 | MemoryBuffer *MB = 152 | MemoryBuffer::getMemBuffer(StringRef(buffer, total), "", false) 153 | .get(); 154 | MemoryBufferRef MBR(*MB); 155 | std::unique_ptr tmpModule = parseIR(MBR, diag, ctx); 156 | assert(tmpModule.get() != nullptr && "Bitcode Parsing Failed!"); 157 | Linker::linkModules(module, std::move(tmpModule)); 158 | free(buffer); 159 | } 160 | 161 | for (xar_subdoc_t doc = xar_subdoc_first(xar); doc; 162 | doc = xar_subdoc_next(doc)) { 163 | if (strcmp("Ld", xar_subdoc_name(doc)) == 0) { 164 | unsigned char *val = nullptr; 165 | unsigned int size = 0; 166 | xar_subdoc_copyout(doc, &val, &size); 167 | string text((char *)val); 168 | std::stringstream ss(text); 169 | std::string to; 170 | vector dylibs; 171 | while (std::getline(ss, to, '\n')) { 172 | if (to.find("") != string::npos) { 174 | size_t s1 = to.find(""); 177 | to.erase(s2, strlen("")); 178 | to.erase(std::remove(to.begin(), to.end(), ' '), to.end()); 179 | char *newT = (char *)calloc(to.size() + 1, sizeof(char)); 180 | to.copy(newT, to.size()); 181 | ldargs.push_back(newT); 182 | } else if (to.find("") != string::npos && 183 | to.find("") != string::npos) { 184 | size_t s1 = to.find(""); 185 | to.erase(s1, strlen("")); 186 | size_t s2 = to.find(""); 187 | to.erase(s2, strlen("")); 188 | to.erase(std::remove(to.begin(), to.end(), ' '), to.end()); 189 | ldargs.push_back("-arch"); 190 | char *newT = (char *)calloc(to.size() + 1, sizeof(char)); 191 | to.copy(newT, to.size()); 192 | ldargs.push_back(newT); 193 | } else if (to.find("") != string::npos && 194 | to.find("") != string::npos) { 195 | size_t s1 = to.find(""); 196 | to.erase(s1, strlen("")); 197 | size_t s2 = to.find(""); 198 | to.erase(s2, strlen("")); 199 | to.erase(std::remove(to.begin(), to.end(), ' '), to.end()); 200 | to.replace(to.find("{SDKPATH}"), strlen("{SDKPATH}"), 201 | SDKROOTPATH); 202 | char *newT = (char *)calloc(to.size() + 1, sizeof(char)); 203 | to.copy(newT, to.size()); 204 | dylibs.push_back(newT); 205 | } 206 | } 207 | for (char *dy : dylibs) { 208 | ldargs.push_back(dy); 209 | } 210 | break; 211 | } 212 | } 213 | 214 | xar_close(xar); 215 | Error err = tf.discard(); 216 | if (err) { 217 | errs() << err << "\n"; 218 | abort(); 219 | } 220 | } else { 221 | errs() << "Creating xar temporary file failed:" << tfOrError.takeError() 222 | << "\n"; 223 | abort(); 224 | } 225 | break; 226 | } else if (sectionName.equals("__bitcode")) { 227 | foundBC = true; 228 | StringRef contents; 229 | section.getContents(contents); 230 | MemoryBuffer *MB = MemoryBuffer::getMemBuffer(contents, "", false).get(); 231 | MemoryBufferRef MBR(*MB); 232 | std::unique_ptr tmpModule = parseIR(MBR, diag, ctx); 233 | assert(tmpModule.get() != nullptr && "Bitcode Parsing Failed!"); 234 | Linker::linkModules(module, std::move(tmpModule)); 235 | } 236 | } 237 | if (foundBC == false) { 238 | errs() << "Bitcode not Found!\n"; 239 | abort(); 240 | } 241 | errs() << "Extracted Linker Flags:\n"; 242 | for (decltype(ldargs.size()) i = 1; i < ldargs.size() - 1; i++) { 243 | errs() << ldargs[i] << " "; 244 | } 245 | errs() << "\n"; 246 | // TargetOptions's setup is by no mean complete, though 247 | SmallString<128> TmpModel; 248 | path::system_temp_directory(true, TmpModel); 249 | path::append(TmpModel, "NoctilucenceTemporaryObject%%%%%%%.o"); 250 | Expected tfOrError = TempFile::create(TmpModel); 251 | if (tfOrError) { 252 | TempFile &tf = tfOrError.get(); 253 | errs() << "Created temporary object file placeholder at:" << tf.TmpName 254 | << " with FD:" << tf.FD << "\n"; 255 | raw_fd_ostream rf(tf.FD, false); 256 | Triple tri(module.getTargetTriple()); 257 | DataLayout dl(&module); 258 | PassManagerBuilder PMB; 259 | legacy::FunctionPassManager FPM(&module); 260 | legacy::PassManager MPM; 261 | PMB.populateFunctionPassManager(FPM); 262 | PMB.populateModulePassManager(MPM); 263 | for (auto &F : module) { 264 | FPM.run(F); 265 | } 266 | MPM.run(module); 267 | VM("Noctilucence") 268 | if (DumpIR) { 269 | std::ofstream std_file_stream(OutputFilename + ".ll"); 270 | raw_os_ostream file_stream(std_file_stream); 271 | module.print(file_stream, nullptr); 272 | } 273 | std::string err; 274 | string arch = ""; 275 | const Target *target = TargetRegistry::lookupTarget(tri.getTriple(), err); 276 | if (target == nullptr) { 277 | errs() << err << "\n"; 278 | exit(-1); 279 | } 280 | TargetOptions Options = InitTargetOptionsFromCodeGenFlags(); 281 | Options.DisableIntegratedAS = false; 282 | Options.MCOptions.ShowMCEncoding = false; 283 | Options.MCOptions.MCUseDwarfDirectory = false; 284 | Options.MCOptions.AsmVerbose = false; 285 | Options.MCOptions.PreserveAsmComments = false; 286 | Options.MCOptions.SplitDwarfFile = false; 287 | std::unique_ptr Target(target->createTargetMachine( 288 | tri.getTriple(), "", "", Options, getRelocModel(), getCodeModel(), 289 | CodeGenOpt::Default)); 290 | assert(Target && "Could not allocate target machine!"); 291 | legacy::PassManager PM; 292 | TargetLibraryInfoImpl TLII(tri); 293 | PM.add(new TargetLibraryInfoWrapperPass(TLII)); 294 | LLVMTargetMachine &LLVMTM = static_cast(*Target); 295 | MachineModuleInfo *MMI = new MachineModuleInfo(&LLVMTM); 296 | TargetPassConfig &TPC = *LLVMTM.createPassConfig(PM); 297 | PM.add(&TPC); 298 | PM.add(MMI); 299 | Target->addPassesToEmitFile(PM, rf, nullptr, 300 | TargetMachine::CodeGenFileType::CGFT_ObjectFile, 301 | false, nullptr); 302 | PM.run(module); 303 | rf.flush(); 304 | if (foundCMD) { 305 | // Prepare linking final obj 306 | ldargs.push_back(tf.TmpName); 307 | SmallString<128> TmpModel2; 308 | path::system_temp_directory(true, TmpModel2); 309 | path::append(TmpModel2, "NoctilucenceFinalObject%%%%%%%"); 310 | Expected tfOrError2 = TempFile::create(TmpModel2); 311 | if (tfOrError2) { 312 | TempFile &tf2 = tfOrError2.get(); 313 | errs() << "Emitting Final Linked Product at temporary path:" 314 | << tf2.TmpName << "\n"; 315 | ret = tf2.TmpName; 316 | ldargs.push_back("-o"); 317 | ldargs.push_back(tf2.TmpName); 318 | ldargs.push_back("-syslibroot"); 319 | ldargs.push_back(SDKROOTPATH); 320 | std::string ErrMsg; 321 | bool failed = false; 322 | sys::ExecuteAndWait(LDPath, ArrayRef(ldargs), llvm::None, {}, 323 | 0, 0, &ErrMsg, &failed); 324 | if (failed || ErrMsg != "") { 325 | errs() << "Linking Failed:" << ErrMsg << "\n"; 326 | abort(); 327 | } 328 | Error errc = tf2.keep(); 329 | if (errc) { 330 | errs() << errc << "\n"; 331 | abort(); 332 | } 333 | } else { 334 | errs() << "Creating Linking Product Failed:" << tfOrError2.takeError() 335 | << "\n"; 336 | abort(); 337 | } 338 | Error errc = tf.discard(); 339 | if (errc) { 340 | errs() << errc << "\n"; 341 | abort(); 342 | } 343 | } else { 344 | ret = tf.TmpName; 345 | Error errc = tf.keep(); 346 | if (errc) { 347 | errs() << errc << "\n"; 348 | abort(); 349 | } 350 | } 351 | 352 | } else { 353 | errs() << "Emit Object File Failed:" << tfOrError.takeError() << "\n"; 354 | } 355 | return ret; 356 | } 357 | void HandleUniversalMachO(MachOUniversalBinary *MachO, const char **argv) { 358 | vector lipoargs; 359 | lipoargs.push_back(argv[0]); 360 | lipoargs.push_back("-create"); 361 | for (auto obj : MachO->objects()) { 362 | auto Bin = &obj; 363 | if (auto BinaryOrErr = Bin->getAsObjectFile()) { 364 | MachOObjectFile &MachO = *BinaryOrErr.get(); 365 | string ret = HandleMachOObjFile(&MachO, argv); 366 | char *result = new char[ret.length()]; 367 | strcpy(result, ret.c_str()); 368 | if (ret == "") { 369 | errs() << "Slide Handling Failed\n"; 370 | abort(); 371 | } 372 | lipoargs.push_back(result); 373 | } 374 | } 375 | lipoargs.push_back("-output"); 376 | lipoargs.push_back(OutputFilename); 377 | std::string ErrMsg; 378 | bool failed = false; 379 | sys::ExecuteAndWait("/usr/bin/lipo", ArrayRef(lipoargs), 380 | llvm::None, {}, 0, 0, &ErrMsg, &failed); 381 | if (failed || ErrMsg != "") { 382 | errs() << "Linking Failed:" << ErrMsg << "\n"; 383 | abort(); 384 | } 385 | } 386 | 387 | int main(int argc, char const *argv[]) { 388 | StringRef ToolName = argv[0]; 389 | sys::PrintStackTraceOnErrorSignal(ToolName); 390 | PrettyStackTraceProgram X(argc, argv); 391 | llvm_shutdown_obj Y; 392 | cl::ParseCommandLineOptions(argc, argv); 393 | cl::AddExtraVersionPrinter(TargetRegistry::printRegisteredTargetsForVersion); 394 | InitializeAllTargets(); 395 | InitializeAllTargetMCs(); 396 | InitializeAllAsmPrinters(); 397 | InitializeAllAsmParsers(); 398 | auto BinaryOrErr = createBinary(InputFilename); 399 | if (BinaryOrErr) { 400 | auto &Binary = *BinaryOrErr; 401 | if (Binary.getBinary()->isMachOUniversalBinary()) { 402 | MachOUniversalBinary *MOUB = 403 | dyn_cast(Binary.getBinary()); 404 | errs() << "Found Universal MachO With" << MOUB->getNumberOfObjects() 405 | << " Objects\n"; 406 | HandleUniversalMachO(MOUB, argv); 407 | } else if (Binary.getBinary()->isMachO()) { 408 | errs() << "Found Thin MachO\n"; 409 | string ret = HandleMachOObjFile( 410 | dyn_cast(Binary.getBinary()), argv); 411 | if (ret != "") { 412 | std::error_code err = sys::fs::rename(ret, OutputFilename); 413 | if (err) { 414 | errs() << "Moving failed with std::error_code :" << err.message() 415 | << "\n"; 416 | } 417 | } else { 418 | errs() << "Thin MachO Handling Failed\n"; 419 | } 420 | 421 | } else { 422 | errs() << "Unsupported ObjectFile Format\n"; 423 | return -1; 424 | } 425 | return 0; 426 | } else { 427 | errs() << "LLVM BinaryParsing Failed:" << BinaryOrErr.takeError() << "\n"; 428 | return -1; 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Noctilucence 2 | LLVM-Based tool that statically extract the Bitcode section from an object file, run passes on it and recompile/link it again. 3 | Note that currently only support for MachOs built by Apple Clang is implemented 4 | 5 | # License 6 | Please refer to [License](https://github.com/HikariObfuscator/Hikari/wiki/License). 7 | 8 | Note that this linked version of license text overrides any artifact left in source code 9 | 10 | # Why 11 | Existing implementations suck because they do all the following which is plain retarded design in my opinion: 12 | - Invoke a ton of processes through ``posix_spawn``, Noctilucence only invokes system's linker due to the lack of MachO support in LLD 13 | - Instead of correctly handling linker flags, they tend to hard-encode linker flags 14 | 15 | # Usage 16 | The following arguments are required: 17 | - ``-i=`` Path to input executable 18 | - ``-o=`` Path to output executable 19 | - ``-dump-ir`` Dump Obfuscated IR in text representation to ``Path to output executable.ll`` for other more advanced usage 20 | 21 | The following arguments are automatically detected 22 | - ``-sdkroot=`` Path to SDKROOT, default to ``/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk`` 23 | - ``-ldpath=`` Path to LD executable, default to ``/usr/bin/ld`` 24 | 25 | # Limitations 26 | Noctilucence directly uses LLVM's CodeGen without invoking a ton of processes and it's doing so by implementing a minimum implementation stripped down from ``llc``, which means it could be less stable in cases. But then again this is just a toy project and serves as PoC purpose only. Furthermore it lacks the following features that might be useful but not critical 27 | 28 | - Automatically extract binary from IPAs/APKs 29 | - Extracting object files from static libraries. Note this is hard to implement due to LLVM's broken ``llvm::object::Archive`` on Darwin. 30 | - For whatever reason the embedded BitCode has been stripped, which removes many symbols and metadatas that the open-source version of Hikari depends on, essentially disabling non-CFG obfuscation passes like ``AntiClassDump`` and ``FunctionCallObfuscate``, which means you probably shouldn't enable those passes. Maybe instead figure out how to tell Xcode/Clang not to strip them? Meh I couldn't care less 31 | 32 | # Compiling 33 | ``git clone https://github.com/HikariObfuscator/Noctilucence.git LLVM_SOURCE_ROOT/tools/`` and compile the whole LLVM suite with it 34 | 35 | # Demonstration 36 | ![Run](https://github.com/Naville/Noctilucence/blob/master/Images/Execution.png?raw=true) 37 | ![Result](https://github.com/Naville/Noctilucence/blob/master/Images/After.png?raw=true) 38 | --------------------------------------------------------------------------------