PatchSolution2.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #include "PatchSolutions.hpp"
  2. #include "Misc.hpp"
  3. #include <string.h>
  4. namespace nkg {
  5. const char PatchSolution2::Keyword[1114] =
  6. "BIjWyoeRR0NBgkqnDZWxCgKCEAw1dqF3DTvOB91ZHwecJYFrdM1KEh"
  7. "1yVeRoGqSdLLGZGUlngig3OD5mMzs889IqWqqfHSeHMvzyg1p6UPCY"
  8. "nesxa9M2dDUrXHomRHOFHSfsbSXRFwt5GivtnJG9lLJHZ7XWeIQABi"
  9. "dKionYD3O6c9tvUAoDosUJAdQ1RaSXTzyETbHTRtnTPeLpO3EedGMs"
  10. "v3jG9yPcmmdYkddSeJRwn2raPJmnvdHScHUACw0sUNuosAqPaQbTQN"
  11. "PATDzcrnd1Sf8RIbUp4MQJFVJugPLVZbP53Gjtyyniqe5q75kva8Qm"
  12. "Hr1uOuXkVppe3cwECaGamupG43L1XfcpRjCMrxRep3s2VlbL01xmfz"
  13. "5cIhrj34iVmgZSAmIb8ZxiHPdp1oDMFkbNetZyWegqjAHQQ9eoSOTD"
  14. "bERbKEwZ5FLeLsbNAxfqsapB1XBvCavFHualx6bxVxuRQceh4z8kaZ"
  15. "iv2pOKbZQSJ2Dx5HEq0bYZ6y6b7sN9IaeDFNQwjzQn1K7k3XlYAPWC"
  16. "IvDe8Ln0FUe4yMNmuUhu5RTjxE05hUqtz1HjJvYQ9Es1VA6LflKQ87"
  17. "TwIXBNvfrcHaZ72QM4dQtDUyEMrLgMDkJBDM9wqIDps65gSlAz6eHD"
  18. "8tYWUttrWose0cH0yykVnqFzPtdRiZyZRfio6lGyK48mIC9z7T6MN3"
  19. "a7OaLZHZSwzcpQLcGi7M9q1wXLq4Ms1UvlwntB9FLHc63tHPpG8rhn"
  20. "XhZIk4QrSm4GYuEKQVHwku6ulw6wfggVL8FZPhoPCGsrb2rQGurBUL"
  21. "3lkVJ6RO9VGHcczDYomXqAJqlt4y9pkQIj9kgwTrxTzEZgMGdYZqsV"
  22. "4Bd5JjtrL7u3LA0N2Hq9Xvmmis2jDVhSQoUoGukNIoqng3SBsf0E7b"
  23. "4W0S1aZSSOJ90nQHQkQShE9YIMDBbNwIg2ncthwADYqibYUgIvJcK9"
  24. "89XHnYmZsdMWtt53lICsXE1vztR5WrQjSw4WXDiB31LXTrvudCB6vw"
  25. "kCQa4leutETpKLJ2bYaOYBdoiBFOwvf36YaSuRoY4SP2x1pWOwGFTg"
  26. "d90J2uYyCqUa3Q3iX52iigT4EKL2vJKdJ";
  27. PatchSolution2::PatchSolution2(const X64ImageInterpreter& Image) :
  28. m_Image(Image),
  29. m_DisassemblerEngine(CS_ARCH_X86, CS_MODE_64),
  30. m_AssemblerEngine(KS_ARCH_X86, KS_MODE_64)
  31. {
  32. m_DisassemblerEngine.Option(CS_OPT_DETAIL, CS_OPT_ON);
  33. }
  34. [[nodiscard]]
  35. const char* PatchSolution2::TryResolveStubHelper(const void* lpStubHelperProc) const {
  36. if (auto dyld_info = m_Image.SpecialLoadCommand<LC_DYLD_INFO_ONLY>(); dyld_info) {
  37. auto Disassembler = m_DisassemblerEngine.CreateDisassembler();
  38. // A stub-helper proc must look like:
  39. // push xxxxh;
  40. // jmp loc_xxxxxxxx
  41. // which should be 10 bytes long.
  42. Disassembler.SetContext({ lpStubHelperProc, 10, 0 });
  43. if (Disassembler.Next()) {
  44. auto lpInsn = Disassembler.GetInstruction();
  45. if (strcasecmp(lpInsn->mnemonic, "push") == 0 && lpInsn->detail->x86.operands[0].type == X86_OP_IMM) {
  46. auto pbBindOpcode =
  47. m_Image.ImageOffset<const uint8_t*>(dyld_info->lazy_bind_off) +
  48. lpInsn->detail->x86.operands[0].imm;
  49. while ((*pbBindOpcode & BIND_OPCODE_MASK) != BIND_OPCODE_DONE) {
  50. switch (*pbBindOpcode & BIND_OPCODE_MASK) {
  51. case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: // 0x10
  52. case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: // 0x30
  53. case BIND_OPCODE_SET_TYPE_IMM: // 0x50
  54. case BIND_OPCODE_DO_BIND: // 0x90
  55. case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: // 0xB0
  56. ++pbBindOpcode;
  57. break;
  58. case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: // 0x20
  59. case BIND_OPCODE_SET_ADDEND_SLEB: // 0x60
  60. case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: // 0x70
  61. case BIND_OPCODE_ADD_ADDR_ULEB: // 0x80
  62. case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: // 0xA0
  63. while (*(++pbBindOpcode) & 0x80u) {}
  64. ++pbBindOpcode;
  65. break;
  66. case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: // 0x40
  67. return reinterpret_cast<const char *>(pbBindOpcode + 1);
  68. case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: // 0xC0
  69. //
  70. // This opcode is too rare to appear,
  71. // It is okay to dismiss this opcode
  72. //
  73. return nullptr;
  74. default:
  75. return nullptr;
  76. }
  77. }
  78. }
  79. }
  80. }
  81. return nullptr;
  82. }
  83. [[nodiscard]]
  84. bool PatchSolution2::FindPatchOffset() noexcept {
  85. try {
  86. std::optional<X64ImageOffset> KeywordOffset;
  87. std::optional<X64ImageOffset> FunctionOffset;
  88. std::optional<X64ImageAddress> StdStringAppendStubRva;
  89. auto section__text = m_Image.ImageSection("__TEXT", "__text");
  90. auto section__stubs = m_Image.ImageSection("__TEXT", "__stubs");
  91. auto sectionview__text = m_Image.ImageSectionView(section__text);
  92. auto sectionview__stubs = m_Image.ImageSectionView(section__stubs);
  93. auto Disassembler = m_DisassemblerEngine.CreateDisassembler();
  94. auto p = m_Image.SearchSection("__TEXT", "__const", [](const void* base, size_t i, size_t size) {
  95. if (i + sizeof(Keyword) <= size) {
  96. return memcmp(ARL::AddressOffset(base, i), Keyword, sizeof(Keyword)) == 0;
  97. } else {
  98. return false;
  99. }
  100. });
  101. if (p) {
  102. KeywordOffset = m_Image.ConvertPtrToOffset(p);
  103. } else {
  104. throw ARL::Exception(__FILE__, __LINE__, "Keyword is not found.");
  105. }
  106. p = m_Image.SearchSection(section__text, [section__text, KeywordRva = m_Image.ConvertOffsetToRva(KeywordOffset.value())](const void* base, size_t i, size_t size) {
  107. if (i + sizeof(uint32_t) <= size) {
  108. auto rip = i + section__text->addr + sizeof(uint32_t);
  109. auto off = ARL::AddressRead<uint32_t>(ARL::AddressOffset(base, i));
  110. return rip + off == KeywordRva;
  111. } else {
  112. return false;
  113. }
  114. });
  115. if (p) {
  116. p = m_Image.SearchSection(
  117. section__text,
  118. ARL::AddressDelta(p, sectionview__text) >= 0xc0 ? ARL::AddressDelta(p, sectionview__text) - 0xc0 : 0,
  119. [p](const void* base, size_t i, size_t size) {
  120. static const uint8_t FunctionHeader[9] = {
  121. 0x55, // push rbp
  122. 0x48, 0x89, 0xe5, // mov rbp, rsp
  123. 0x41, 0x57, // push r15
  124. 0x41, 0x56, // push r14
  125. 0x53, // push rbx
  126. };
  127. if (ARL::AddressOffset(base, i + sizeof(FunctionHeader)) <= p) {
  128. return memcmp(ARL::AddressOffset(base, i), FunctionHeader, sizeof(FunctionHeader)) == 0;
  129. } else {
  130. return false;
  131. }
  132. }
  133. );
  134. if (p) {
  135. FunctionOffset = m_Image.ConvertPtrToOffset(p);
  136. } else {
  137. throw ARL::Exception(__FILE__, __LINE__, "Function header is not found.");
  138. }
  139. } else {
  140. throw ARL::Exception(__FILE__, __LINE__, "Xref of Keyword is not found.");
  141. }
  142. Disassembler.SetContext({ sectionview__stubs, section__stubs->size, section__stubs->addr });
  143. while (Disassembler.Next()) {
  144. auto lpInsn = Disassembler.GetInstruction();
  145. //
  146. // As far as I know, all stub functions have a pattern looking like:
  147. // jmp qword ptr [RIP + xxxx]
  148. //
  149. if (strcasecmp(lpInsn->mnemonic, "jmp") == 0 && lpInsn->detail->x86.operands[0].type == X86_OP_MEM && lpInsn->detail->x86.operands[0].mem.base == X86_REG_RIP) {
  150. try {
  151. X64ImageAddress la_symbol_ptr_rva = Disassembler.GetContext().Address + lpInsn->detail->x86.operands[0].mem.disp;
  152. X64ImageOffset la_symbol_ptr_offset = m_Image.ConvertRvaToOffset(la_symbol_ptr_rva);
  153. X64ImageAddress stub_helper_rva = ARL::AddressRead<uint64_t>(m_Image.ImageBase(), la_symbol_ptr_offset);
  154. X64ImageOffset stub_helper_offset = m_Image.ConvertRvaToOffset(stub_helper_rva);
  155. //
  156. // __ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKc
  157. // is the mangled name of "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::append(char const*)",
  158. // which is, as known as, "std::string::append(const char*)"
  159. // You can demangle it by c++flit
  160. // e.g.
  161. // c++filt -_ '__ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKc'
  162. //
  163. auto lpszSymbolName = TryResolveStubHelper(m_Image.ImageOffset(stub_helper_offset));
  164. if (lpszSymbolName && strcmp(lpszSymbolName, "__ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKc") == 0) {
  165. StdStringAppendStubRva = Disassembler.GetInstructionContext().Address;
  166. break;
  167. }
  168. } catch (...) {
  169. continue;
  170. }
  171. }
  172. }
  173. if (StdStringAppendStubRva.has_value()) {
  174. m_KeywordOffset = KeywordOffset.value();
  175. m_FunctionOffset = FunctionOffset.value();
  176. m_StdStringAppendStubRva = StdStringAppendStubRva.value();
  177. } else {
  178. throw ARL::Exception(__FILE__, __LINE__, "std::string::append(const char*) is not found.");
  179. }
  180. printf("[+] PatchSolution2 ...... Ready to apply.\n");
  181. printf(" Keyword offset = +0x%.8x\n", m_KeywordOffset.value());
  182. printf(" CSRegistrationCenter::obtainPublicKey RVA = 0x%.16llx\n", m_Image.ConvertOffsetToRva(m_FunctionOffset.value()));
  183. printf(" std::string::append(const char*) RVA = 0x%.16llx\n", m_StdStringAppendStubRva.value());
  184. return true;
  185. } catch (...) {
  186. printf("[-] PatchSolution2 ...... Omitted.\n");
  187. return false;
  188. }
  189. }
  190. [[nodiscard]]
  191. bool PatchSolution2::CheckKey(const RSACipher& Cipher) const noexcept {
  192. return Cipher.Bits() == 2048;
  193. }
  194. void PatchSolution2::MakePatch(const RSACipher& Cipher) const {
  195. if (m_KeywordOffset.has_value() && m_FunctionOffset.has_value() && m_StdStringAppendStubRva.has_value()) {
  196. //
  197. // Prepare public key string
  198. //
  199. auto szPublicKeyPEM = Cipher.ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();
  200. for (auto pos = szPublicKeyPEM.find("-----BEGIN PUBLIC KEY-----"); pos != std::string::npos; pos = szPublicKeyPEM.find("-----BEGIN PUBLIC KEY-----", pos)) {
  201. szPublicKeyPEM.erase(pos, strlen("-----BEGIN PUBLIC KEY-----"));
  202. }
  203. for (auto pos = szPublicKeyPEM.find("-----END PUBLIC KEY-----"); pos != std::string::npos; pos = szPublicKeyPEM.find("-----END PUBLIC KEY-----", pos)) {
  204. szPublicKeyPEM.erase(pos, strlen("-----END PUBLIC KEY-----"));
  205. }
  206. for (auto pos = szPublicKeyPEM.find('\n'); pos != std::string::npos; pos = szPublicKeyPEM.find('\n', pos)) {
  207. szPublicKeyPEM.erase(pos, 1);
  208. }
  209. //
  210. // Prepare new function opcodes
  211. //
  212. auto Assembler = m_AssemblerEngine.CreateAssembler();
  213. auto MachineCode = Assembler.GenerateMachineCode(
  214. [KeywordRva = m_Image.ConvertOffsetToRva(m_KeywordOffset.value()), StdStringAppendStubRva = m_StdStringAppendStubRva.value()]() -> std::string {
  215. const char asm_template[] =
  216. "push rbp;"
  217. "mov rbp, rsp;"
  218. "xor rax, rax;" // initialize std::string with null
  219. "mov qword ptr[rdi], rax;"
  220. "mov qword ptr[rdi + 0x8], rax;"
  221. "mov qword ptr[rdi + 0x10], rax;"
  222. "lea rsi, qword ptr[0x%.16llx];" // filled with address to Keyword
  223. "call 0x%.16llx;" // filled with address to std::string::append(const char*)
  224. "leave;"
  225. "ret;";
  226. std::string asm_string;
  227. int l = snprintf(nullptr, 0, asm_template, KeywordRva, StdStringAppendStubRva);
  228. if (l < 0) {
  229. std::terminate();
  230. }
  231. asm_string.resize(l + 1);
  232. l = snprintf(asm_string.data(), asm_string.length(), asm_template, KeywordRva, StdStringAppendStubRva);
  233. if (l < 0) {
  234. std::terminate();
  235. }
  236. while (asm_string.back() == '\x00') {
  237. asm_string.pop_back();
  238. }
  239. return asm_string;
  240. }().c_str(),
  241. m_Image.ConvertOffsetToRva(m_FunctionOffset.value())
  242. );
  243. puts("**************************************************************");
  244. puts("* PatchSolution2 *");
  245. puts("**************************************************************");
  246. auto lpKeyword = m_Image.ImageOffset(m_KeywordOffset.value());
  247. auto lpFunction = m_Image.ImageOffset(m_FunctionOffset.value());
  248. printf("[*] Previous:\n");
  249. Misc::PrintMemory(lpKeyword, szPublicKeyPEM.length() + 1, m_Image.ImageBase());
  250. memcpy(lpKeyword, szPublicKeyPEM.c_str(), szPublicKeyPEM.length() + 1); // with a null-terminator
  251. printf("[*] After:\n");
  252. Misc::PrintMemory(lpKeyword, szPublicKeyPEM.length() + 1, m_Image.ImageBase());
  253. printf("\n");
  254. printf("[*] Previous:\n");
  255. Misc::PrintMemory(lpFunction, MachineCode.size(), m_Image.ImageBase());
  256. memcpy(lpFunction, MachineCode.data(), MachineCode.size());
  257. printf("[*] After:\n");
  258. Misc::PrintMemory(lpFunction, MachineCode.size(), m_Image.ImageBase());
  259. printf("\n");
  260. } else {
  261. throw ARL::Exception(__FILE__, __LINE__, "PatchSolution2: not ready yet.");
  262. }
  263. }
  264. }