├── .gitignore ├── .idea ├── encodings.xml ├── misc.xml ├── modules.xml ├── re_scripts.iml └── vcs.xml ├── LICENSE ├── README.md ├── ida ├── Armariris_flow_flattening_bypass.py ├── Armariris_string_obfuscation_bypass.md ├── Armariris_string_obfuscation_bypass.py ├── Simulator.py ├── img │ ├── after1.png │ ├── after2.png │ ├── after3.png │ ├── after4.png │ ├── before.png │ ├── data_orig.png │ └── xref.png └── sample │ ├── test.c │ ├── test_linux_x86_64 │ ├── test_linux_x86_64_fla │ └── test_macos_x86_64 └── jeb └── FridaCodeGenerator.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Sensitive or high-churn files 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | .idea/**/dbnavigator.xml 20 | 21 | # Gradle 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # CMake 26 | cmake-build-debug/ 27 | cmake-build-release/ 28 | 29 | # Mongo Explorer plugin 30 | .idea/**/mongoSettings.xml 31 | 32 | # File-based project format 33 | *.iws 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | 53 | # Editor-based Rest Client 54 | .idea/httpRequests 55 | ### VisualStudioCode template 56 | .vscode/* 57 | !.vscode/settings.json 58 | !.vscode/tasks.json 59 | !.vscode/launch.json 60 | !.vscode/extensions.json 61 | ### SublimeText template 62 | # Cache files for Sublime Text 63 | *.tmlanguage.cache 64 | *.tmPreferences.cache 65 | *.stTheme.cache 66 | 67 | # Workspace files are user-specific 68 | *.sublime-workspace 69 | 70 | # Project files should be checked into the repository, unless a significant 71 | # proportion of contributors will probably not be using Sublime Text 72 | # *.sublime-project 73 | 74 | # SFTP configuration file 75 | sftp-config.json 76 | 77 | # Package control specific files 78 | Package Control.last-run 79 | Package Control.ca-list 80 | Package Control.ca-bundle 81 | Package Control.system-ca-bundle 82 | Package Control.cache/ 83 | Package Control.ca-certs/ 84 | Package Control.merged-ca-bundle 85 | Package Control.user-ca-bundle 86 | oscrypto-ca-bundle.crt 87 | bh_unicode_properties.cache 88 | 89 | # Sublime-github package stores a github token in this file 90 | # https://packagecontrol.io/packages/sublime-github 91 | GitHub.sublime-settings 92 | ### Windows template 93 | # Windows thumbnail cache files 94 | Thumbs.db 95 | ehthumbs.db 96 | ehthumbs_vista.db 97 | 98 | # Dump file 99 | *.stackdump 100 | 101 | # Folder config file 102 | [Dd]esktop.ini 103 | 104 | # Recycle Bin used on file shares 105 | $RECYCLE.BIN/ 106 | 107 | # Windows Installer files 108 | *.cab 109 | *.msi 110 | *.msix 111 | *.msm 112 | *.msp 113 | 114 | # Windows shortcuts 115 | *.lnk 116 | ### Linux template 117 | *~ 118 | 119 | # temporary files which can be created if a process still has a handle open of a deleted file 120 | .fuse_hidden* 121 | 122 | # KDE directory preferences 123 | .directory 124 | 125 | # Linux trash folder which might appear on any partition or disk 126 | .Trash-* 127 | 128 | # .nfs files are created when an open file is removed but is still being accessed 129 | .nfs* 130 | ### Java template 131 | # Compiled class file 132 | *.class 133 | 134 | # Log file 135 | *.log 136 | 137 | # BlueJ files 138 | *.ctxt 139 | 140 | # Mobile Tools for Java (J2ME) 141 | .mtj.tmp/ 142 | 143 | # Package Files # 144 | *.jar 145 | *.war 146 | *.nar 147 | *.ear 148 | *.zip 149 | *.tar.gz 150 | *.rar 151 | 152 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 153 | hs_err_pid* 154 | ### Python template 155 | # Byte-compiled / optimized / DLL files 156 | __pycache__/ 157 | *.py[cod] 158 | *$py.class 159 | 160 | # C extensions 161 | *.so 162 | 163 | # Distribution / packaging 164 | .Python 165 | build/ 166 | develop-eggs/ 167 | dist/ 168 | downloads/ 169 | eggs/ 170 | .eggs/ 171 | lib/ 172 | lib64/ 173 | parts/ 174 | sdist/ 175 | var/ 176 | wheels/ 177 | *.egg-info/ 178 | .installed.cfg 179 | *.egg 180 | MANIFEST 181 | 182 | # PyInstaller 183 | # Usually these files are written by a python script from a template 184 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 185 | *.manifest 186 | *.spec 187 | 188 | # Installer logs 189 | pip-log.txt 190 | pip-delete-this-directory.txt 191 | 192 | # Unit test_linux_elf / coverage reports 193 | htmlcov/ 194 | .tox/ 195 | .coverage 196 | .coverage.* 197 | .cache 198 | nosetests.xml 199 | coverage.xml 200 | *.cover 201 | .hypothesis/ 202 | .pytest_cache/ 203 | 204 | # Translations 205 | *.mo 206 | *.pot 207 | 208 | # Django stuff: 209 | *.log 210 | local_settings.py 211 | db.sqlite3 212 | 213 | # Flask stuff: 214 | instance/ 215 | .webassets-cache 216 | 217 | # Scrapy stuff: 218 | .scrapy 219 | 220 | # Sphinx documentation 221 | docs/_build/ 222 | 223 | # PyBuilder 224 | target/ 225 | 226 | # Jupyter Notebook 227 | .ipynb_checkpoints 228 | 229 | # pyenv 230 | .python-version 231 | 232 | # celery beat schedule file 233 | celerybeat-schedule 234 | 235 | # SageMath parsed files 236 | *.sage.py 237 | 238 | # Environments 239 | .env 240 | .venv 241 | env/ 242 | venv/ 243 | ENV/ 244 | env.bak/ 245 | venv.bak/ 246 | 247 | # Spyder project settings 248 | .spyderproject 249 | .spyproject 250 | 251 | # Rope project settings 252 | .ropeproject 253 | 254 | # mkdocs documentation 255 | /site 256 | 257 | # mypy 258 | .mypy_cache/ 259 | ### macOS template 260 | # General 261 | .DS_Store 262 | .AppleDouble 263 | .LSOverride 264 | 265 | # Icon must end with two \r 266 | Icon 267 | 268 | # Thumbnails 269 | ._* 270 | 271 | # Files that might appear in the root of a volume 272 | .DocumentRevisions-V100 273 | .fseventsd 274 | .Spotlight-V100 275 | .TemporaryItems 276 | .Trashes 277 | .VolumeIcon.icns 278 | .com.apple.timemachine.donotpresent 279 | 280 | # Directories potentially created on remote AFP share 281 | .AppleDB 282 | .AppleDesktop 283 | Network Trash Folder 284 | Temporary Items 285 | .apdisk 286 | 287 | *.i64 288 | *.idb 289 | *.id1 290 | *.id2 291 | *.id0 292 | *.nam 293 | *.til 294 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/re_scripts.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 MSWorkers 2 | 3 | Anti 996 License Version 1.0 (Draft) 4 | 5 | Permission is hereby granted to any individual or legal entity obtaining a copy 6 | of this licensed work (including the source code, documentation and/or related 7 | items, hereinafter collectively referred to as the "licensed work"), free of 8 | charge, to deal with the licensed work for any purpose, including without 9 | limitation, the rights to use, reproduce, modify, prepare derivative works of, 10 | publish, distribute and sublicense the licensed work, subject to the following 11 | conditions: 12 | 13 | 1. The individual or the legal entity must conspicuously display, without 14 | modification, this License on each redistributed or derivative copy of the 15 | Licensed Work. 16 | 17 | 2. The individual or the legal entity must strictly comply with all applicable 18 | laws, regulations, rules and standards of the jurisdiction relating to 19 | labor and employment where the individual is physically located or where 20 | the individual was born or naturalized; or where the legal entity is 21 | registered or is operating (whichever is stricter). In case that the 22 | jurisdiction has no such laws, regulations, rules and standards or its 23 | laws, regulations, rules and standards are unenforceable, the individual 24 | or the legal entity are required to comply with Core International Labor 25 | Standards. 26 | 27 | 3. The individual or the legal entity shall not induce or force its 28 | employee(s), whether full-time or part-time, or its independent 29 | contractor(s), in any methods, to agree in oral or written form, 30 | to directly or indirectly restrict, weaken or relinquish his or 31 | her rights or remedies under such laws, regulations, rules and 32 | standards relating to labor and employment as mentioned above, 33 | no matter whether such written or oral agreement are enforceable 34 | under the laws of the said jurisdiction, nor shall such individual 35 | or the legal entity limit, in any methods, the rights of its employee(s) 36 | or independent contractor(s) from reporting or complaining to the copyright 37 | holder or relevant authorities monitoring the compliance of the license 38 | about its violation(s) of the said license. 39 | 40 | THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 42 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT 43 | HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 44 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION 45 | WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # re_scripts 2 | Some reverse-engineering scripts 3 | 4 | ## jeb 5 | 6 | 1. [FridaCodeGenerator.py](./jeb/FridaCodeGenerator.py) A jeb plugin generate frida hook code 7 | 8 | ## ida 9 | 10 | 1. [Armariris_string_obfuscation_bypass.py](./ida/Armariris_string_obfuscation_bypass.py) An ida plugin bypass Armariris string obfuscation 11 | -------------------------------------------------------------------------------- /ida/Armariris_flow_flattening_bypass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # @Author: smartdone 5 | # @Date: 2019-07-01 11:00 6 | 7 | from Simulator import * 8 | from capstone import * 9 | 10 | ret_addr = None 11 | start_addr = 0 12 | 13 | 14 | def hook_mem_access(uc, type, address, size, value, userdata): 15 | pc = uc.reg_read(UC_ARM64_REG_PC) 16 | print 'pc:%x type:%d addr:%x size:%x' % (pc, type, address, size) 17 | # uc.emu_stop() 18 | return False 19 | 20 | md = Cs(CS_ARCH_ARM64,CS_MODE_ARM) 21 | md.detail = True #enable detail analyise 22 | 23 | def reg_ctou(regname):# 24 | # This function covert capstone reg name to unicorn reg const. 25 | type1 = regname[0] 26 | if type1 == 'w' or type1 =='x': 27 | idx = int(regname[1:]) 28 | if type1 == 'w': 29 | return idx + UC_ARM64_REG_W0 30 | else: 31 | if idx == 29: 32 | return 1 33 | elif idx == 30: 34 | return 2 35 | else: 36 | return idx + UC_ARM64_REG_X0 37 | elif regname=='sp': 38 | return 4 39 | return None 40 | 41 | branch_control = 1 42 | 43 | def hook_code(uc, address, size, user_data): 44 | global ret_addr 45 | global start_addr 46 | global branch_control 47 | # print user_data 48 | instruction = uc.mem_read(address, size) 49 | # print ["0x%x" % item for item in block_starts] 50 | idc.set_color(address, idc.CIC_ITEM, 0xFFB6C1) 51 | # print ["0x%x" % item for item in instruction] 52 | _code = idc.GetDisasm(address) 53 | 54 | # print(type(_code), _code.startswith("CMP"), _code) 55 | # print("0x%016x \t%s" % (address, _code)) 56 | 57 | if address in user_data and address != start_addr: 58 | print("address 0x%x" % address) 59 | uc.emu_stop() 60 | ret_addr = address 61 | return 62 | 63 | if "RET" == _code: 64 | ret_addr = -1 65 | uc.emu_stop() 66 | 67 | for ins in md.disasm(instruction, address): 68 | if ins.mnemonic == 'csel': 69 | print("csel 0x%x:\t%s\t%s" % (ins.address, ins.mnemonic, ins.op_str)) 70 | regs = [reg_ctou(x) for x in ins.op_str.split(', ')] 71 | assert len(regs) == 4 72 | v1 = uc.reg_read(regs[1]) 73 | v2 = uc.reg_read(regs[2]) 74 | if branch_control == 1: 75 | uc.reg_write(regs[0], v1) 76 | else: 77 | uc.reg_write(regs[0], v2) 78 | uc.reg_write(UC_ARM64_REG_PC, address + size) 79 | 80 | 81 | class FLASimulator(Simulator): 82 | def __init__(self, basic_blocks): 83 | super(FLASimulator, self).__init__() 84 | self.context = None 85 | self.basic_blocks = basic_blocks 86 | 87 | def emu_start(self, func_start, func_end): 88 | global ret_addr 89 | if self.arch == UC_ARCH_ARM: 90 | if self.is_thumb_ea(func_start): 91 | print("thumb mode") 92 | self.mode = UC_MODE_THUMB 93 | mu = Uc(self.arch, self.mode) 94 | 95 | for item in self.mem_map: 96 | Simulator.map_memory(mu, item['start'], item['length']) 97 | 98 | # 给栈分配内存 99 | Simulator.map_memory(mu, self.stack_base, self.stack_length) 100 | 101 | # 写入数据 102 | for item in self.segments: 103 | Simulator.write_memory(mu, item['start'], item['data']) 104 | 105 | # 配置寄存器 106 | mu.reg_write(self.sp, self.stack_base + 1024 * 1024) 107 | 108 | mu.hook_add(htype=UC_HOOK_CODE, callback=hook_code, user_data=self.basic_blocks) 109 | mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_access) 110 | mu.hook_add(UC_ERR_WRITE_UNMAPPED, hook_mem_access) 111 | 112 | self.set_context(mu, self.context) 113 | 114 | try: 115 | # 开始执行 116 | if self.mode == UC_MODE_THUMB: 117 | mu.emu_start(func_start + 1, func_end) 118 | else: 119 | mu.emu_start(func_start, func_end) 120 | except Exception as e: 121 | print("Err: %s. Execution function failed.(The function address is 0x%x)" % (e, func_start)) 122 | 123 | self.get_context(mu) 124 | 125 | # 读取数据 126 | for item in self.segments: 127 | _data = Simulator.read_memory(mu, item['start'], item['end']) 128 | self.replace_data(item['start'], _data) 129 | 130 | # unmap memory 131 | for item in self.mem_map: 132 | Simulator.unmap_memory(mu, item['start'], item['length']) 133 | 134 | Simulator.unmap_memory(mu, self.stack_base, self.stack_length) 135 | 136 | if ret_addr != 0: 137 | tmp_addr = ret_addr 138 | ret_addr = None 139 | return tmp_addr 140 | 141 | return None 142 | 143 | def get_context(self, mu=None): 144 | if not mu: 145 | return self.context 146 | else: 147 | regs = [] 148 | if self.arch == UC_ARCH_ARM: 149 | print("arm") 150 | elif self.arch == UC_ARCH_ARM64: 151 | print("arm64") 152 | for idx in range(UC_ARM64_REG_X0, UC_ARM64_REG_X28 + 1): 153 | regs.append((idx, mu.reg_read(idx))) 154 | elif self.arch == UC_ARCH_X86: 155 | print("x86") 156 | 157 | self.context = regs 158 | # print "regs", regs 159 | 160 | return self.context 161 | 162 | def set_context(self, mu=None, _context=None): 163 | if not mu: 164 | self.context = _context 165 | else: 166 | if _context: 167 | self.context = _context 168 | for item in self.context: 169 | mu.reg_write(item[0], item[1]) 170 | 171 | 172 | for func in idautils.Functions(): 173 | global start_addr 174 | func_name = idc.GetFunctionName(func) 175 | func_data = idaapi.get_func(func) 176 | start = func_data.start_ea 177 | end = func_data.end_ea 178 | # print func_name, hex(start), hex(end) 179 | if func_name == "JNI_OnLoad": 180 | print func_name, hex(start), hex(end) 181 | f = idaapi.FlowChart(idaapi.get_func(start)) 182 | basic_blocs = [] 183 | for block in f: 184 | print("%x - %x [%d]:" % (block.start_ea, block.end_ea, block.id)) 185 | block_start = block.start_ea 186 | block_end = block.end_ea 187 | basic_blocs.append(block_start) 188 | sim = FLASimulator(basic_blocs) 189 | sim.sp = UC_ARM64_REG_SP 190 | sim.arch = UC_ARCH_ARM64 191 | sim.mode = UC_MODE_ARM 192 | 193 | flow = {} 194 | start_addr = start 195 | queue = [(start, None)] 196 | 197 | while len(queue) > 0: 198 | env = queue.pop() 199 | pc = env[0] 200 | context = env[1] 201 | 202 | sim.set_context(context) 203 | 204 | if pc in flow.keys(): 205 | print("0x%x in flow" % pc) 206 | continue 207 | 208 | flow[pc] = [] 209 | start_addr = pc 210 | 211 | p = sim.emu_start(pc, end) 212 | 213 | if p: 214 | print("0x%x --> 0x%x" % (pc, p)) 215 | flow[pc].append(p) 216 | queue.append((p, sim.get_context())) 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /ida/Armariris_string_obfuscation_bypass.md: -------------------------------------------------------------------------------- 1 | ## 编译Armariris 2 | 3 | ``` 4 | git clone git@github.com:gossip-sjtu/Armariris.git 5 | ``` 6 | 7 | 编译 8 | 9 | ```shell 10 | cd Armariris 11 | mkdir build 12 | cd build 13 | cmake ../ -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="ARM;X86;AArch64" 14 | make -j8 15 | ``` 16 | 17 | 测试文件内容如下: 18 | 19 | ```c 20 | #include 21 | 22 | void fun(){ 23 | printf("test 3333\n"); 24 | } 25 | 26 | int main(int argc, char *argv[]) { 27 | printf("test 1111\n"); 28 | printf("test 2222\n"); 29 | fun(); 30 | return 0; 31 | } 32 | 33 | ``` 34 | 35 | 使用编译好的llvm编译这个测试的文件 36 | 37 | ```shell 38 | clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -mllvm -sobf test.c -o test 39 | ``` 40 | 41 | 使用isysroot指定sdk,然后使用`-mllvm -sobf`开启字符串混淆 42 | 43 | ## Armariris是如何进行字符串混淆的 44 | 45 | 我们直接看使用ida反汇编出来的代码 46 | 47 | ```c++ 48 | int __cdecl main(int argc, const char **argv, const char **envp) 49 | { 50 | printf(aRcur7777, argv, envp); 51 | printf(&byte_100001036); 52 | fun(); 53 | return 0; 54 | } 55 | ``` 56 | 57 | 可以看到有两个printf函数打印了一些数据出来,我们点第一个打印的字符串,双击`aRcur7777`跳转到 58 | 字符串定义位置,这个字符串在data段 59 | 60 | ![](./img/data_orig.png) 61 | 62 | 这个字符串我们本来输出的是`test 1111`这里显然不是,我们查看`aRcur7777`的交叉引用,发现两处, 63 | 其中一处是main函数中的printf,另一处应该就是还原这个字符串的位置了 64 | 65 | ![](./img/xref.png) 66 | 67 | 所以`__datadiv_decode14953400483976599729`这个函数就是还原这个字符的函数,我们看他是如何做的还原 68 | 。跳转过去按F5反编译,得到的结果如下: 69 | 70 | ```cpp 71 | __int64 datadiv_decode14953400483976599729() 72 | { 73 | bool v0; // ST23_1 74 | bool v1; // ST17_1 75 | __int64 result; // rax 76 | bool v3; // ST0B_1 77 | unsigned int v4; // [rsp+8h] [rbp-1Ch] 78 | unsigned int v5; // [rsp+14h] [rbp-10h] 79 | unsigned int v6; // [rsp+20h] [rbp-4h] 80 | 81 | v6 = 0; 82 | do 83 | { 84 | aLKl[v6] ^= 0x38u; 85 | v0 = v6++ < 0xA; 86 | } 87 | while ( v0 ); 88 | v5 = 0; 89 | do 90 | { 91 | aRcur7777[v5] ^= 6u; 92 | v1 = v5++ < 0xA; 93 | } 94 | while ( v1 ); 95 | v4 = 0; 96 | do 97 | { 98 | byte_100001036[v4] ^= 0x71u; 99 | result = v4 - 10; 100 | v3 = v4++ < 0xA; 101 | } 102 | while ( v3 ); 103 | return result; 104 | } 105 | ``` 106 | 107 | 我们可以看到`aRcur7777`的还原是和6做了异或操作,那我们来验证一下是否是我们看到的这样。 108 | 109 | `aRcur7777`的原始数据是`[0x72, 0x63, 0x75, 0x72, 0x26, 0x37, 0x37, 0x37, 0x37]` 110 | 111 | 每一位和6异或之后的结果是`[0x74, 0x65, 0x73, 0x74, 0x20, 0x31, 0x31, 0x31, 0x31]` 112 | 113 | 对于的ascii字符串就是`test 1111` 114 | 115 | 他这里做字符串混淆用的是一个很简单的原理,一个数字两次异或同一个值,得到的结果是本身的值。也就是 116 | 第一次异或就给字符串混淆了,再异或一次就把数据还原了。 117 | 118 | ## 源码分析 119 | 120 | 字符串混淆的源文件在`lib/Transforms/Obfuscation/StringObfuscation.cpp`这个位置, 121 | 实现字符串混淆的是一个`ModulePass`,关于`ModulePass`可以参考[http://llvm.org/doxygen/classllvm_1_1ModulePass.html#details](http://llvm.org/doxygen/classllvm_1_1ModulePass.html#details) 122 | 。在这个pass里面会遍历字符串,然后把字符串和生成的key异或,并替换原始的值,关键代码如下: 123 | 124 | ```cpp 125 | 126 | // Duplicate global variable 127 | GlobalVariable *dynGV = new GlobalVariable(M, 128 | gv->getType()->getElementType(), 129 | !(gv->isConstant()), gv->getLinkage(), 130 | (Constant*) 0, gv->getName(), 131 | (GlobalVariable*) 0, 132 | gv->getThreadLocalMode(), 133 | gv->getType()->getAddressSpace()); 134 | // dynGV->copyAttributesFrom(gv); 135 | dynGV->setInitializer(gv->getInitializer()); 136 | 137 | std::string tmp=gv->getName().str(); 138 | // errs()<<"GV: "<<*gv<<"\n"; 139 | 140 | Constant *initializer = gv->getInitializer(); 141 | ConstantDataSequential *cdata = dyn_cast(initializer); 142 | if (cdata) { 143 | const char *orig = cdata->getRawDataValues().data(); 144 | unsigned len = cdata->getNumElements()*cdata->getElementByteSize(); 145 | 146 | encVar *cur = new encVar(); 147 | cur->var = dynGV; 148 | cur->key = llvm::cryptoutils->get_uint8_t(); 149 | // casting away const is undef. behavior in C++ 150 | // TODO a clean implementation would retrieve the data, generate a new constant 151 | // set the correct type, and copy the data over. 152 | //char *encr = new char[len]; 153 | //Constant *initnew = ConstantDataArray::getString(M.getContext(), encr, true); 154 | char *encr = const_cast(orig); 155 | // Simple xor encoding 156 | for (unsigned i = 0; i != len; ++i) { 157 | encr[i] = orig[i]^cur->key; 158 | } 159 | 160 | // FIXME Second part of the unclean hack. 161 | dynGV->setInitializer(initializer); 162 | 163 | // Prepare to add decode function for this variable 164 | encGlob.push_back(cur); 165 | } else { 166 | // just copying default initializer for now 167 | dynGV->setInitializer(initializer); 168 | } 169 | 170 | // redirect references to new GV and remove old one 171 | gv->replaceAllUsesWith(dynGV); 172 | toDelConstGlob.push_back(gv); 173 | 174 | ``` 175 | 176 | 在替换了之后为了保证程序可以正常运行,还得加一个函数输还原字符串,还原字符串的 177 | 函数生成代码在`addDecodeFunction`中。在这里添加了`.datadiv_decode`开始的函数 178 | 加上一串随机字符串,里面进行了异或操作,将数据还原。然后将这个函数加入到了`entry`,这个在 179 | elf文件的话,就会被加入到`.init_array`,在mach-o文件中就会被加入到`__mod_init_func`。 180 | 代码也比较简单,可以参照源码看一下。 181 | 182 | 183 | ## 还原字符串 184 | 185 | 前面讲了原理其实很简单,那么怎么还原字符串呢,其实也有很多方式,第一种是内存dump,因为他会在 186 | 初始化程序的时候就把原始字符串还原回去。但是有时候我就行静态分析,不想执行之后去dump。如果只 187 | 静态分析,也可以去人工还原字符串。但是如果字符串很多,人工还原工作量很大。其实我们还可以使用 188 | unicorn之类的工具,模拟去执行他的指令,把字符串进行还原。 189 | 190 | ### 还原混淆字符串的思路 191 | 192 | 1. 找到所有`.datadiv_decode`开始的函数 193 | 2. unicorn分配内存,将程序的.text段和.data段映射到unicorn分配的内存中 194 | 3. 模拟执行所有`.datadiv_decode`开始的函数 195 | 4. 最后将unicorn中分配的data读出来,patch到程序中 196 | 197 | ### 使用的工具 198 | 199 | 因为不同操作系统可执行文件格式不一样。为了简单点,我们直接写一个ida插件。所以需要以下工具: 200 | 201 | 1. ida 202 | 2. python2 (因为ida里面内置的python是python2) 203 | 3. python2安装unicorn和keystone库 204 | 205 | ### 找到所有的`.datadiv_decode`开始的函数 206 | 207 | `idautils.Functions()`可以遍历函数,遍历匹配含有`datadiv_decode`的函数,保存他们 208 | 的起始地址,代码很简单,如下: 209 | 210 | ```python 211 | import idaapi 212 | import idc 213 | import idautils 214 | 215 | for func in idautils.Functions(): 216 | func_name = idc.GetFunctionName(func) 217 | if "datadiv_decode" in func_name: 218 | func_data = idaapi.get_func(func) 219 | start = func_data.start_ea 220 | end = func_data.end_ea 221 | ``` 222 | 223 | ### unicorn分配内存 224 | 225 | 我这里分配内存的想法是直接用ida的api获取data段和text段的内容,以及起始地址,然后在 226 | unicorn里面对于起始分配内存,将data段和text段写进去。 227 | 228 | unicorn分配内存还是有些坑,不能直接在任意地址分配,必须得整除1024的才可以,所以需 229 | 要稍微计算一下分配的地址。这里对基地址减去对(1024 * 1024)求余的结果作为新的基地址, 230 | 然后分配内存的长度增加(1024 * 1024),实现的代码如下 231 | 232 | ```python 233 | 234 | def get_base_and_len(base, length): 235 | _base = base - (base % (1024 * 1024)) 236 | _length = (length / (1024 * 1024) + 1) * 1024 * 1024 237 | return _base, _length 238 | 239 | ``` 240 | 241 | 算出起始地址之后使用unicorn的`mem_map`方法分配内存即可 242 | 243 | ### 模拟执行,patch程序 244 | 245 | 模拟执行这里也比较简单,直接调用unicorn的`emu_start`方法,然后传入函数的起始地址即可开始 246 | 模拟执行,模拟执行完成之后将data段读出来,模拟执行下一个函数的时候使用这个data加载到内存中。 247 | 这样所有的`.datadiv_decode`函数执行之后data段就被还原了。将还原的data段用ida的patch去 248 | 修改掉原始的data,这个时候你看到的字符串就是原始的字符串了。 249 | 250 | 运行脚本前效果如下: 251 | 252 | ![before](./img/before.png) 253 | 254 | 运行脚本之后效果如下: 255 | 256 | ![after](./img/after1.png) 257 | 258 | ![after](./img/after2.png) 259 | 260 | 现在脚本已经可以自动重新解析data段,并且会给string的所有引用处加上注释,效果大概可以参考[http://iosre.com/t/armariris-ida/15071](http://iosre.com/t/armariris-ida/15071) 261 | 262 | ![after](./img/after3.png) 263 | 264 | ![after](./img/after4.png) 265 | 266 | 267 | ### 完整代码以及示例二进制文件 268 | 269 | 代码以及二进制文件存放在[https://github.com/smartdone/re_scripts/tree/master/ida](https://github.com/smartdone/re_scripts/tree/master/ida) 270 | 其中Armariris_string_obfuscation_bypass.py是ida用来还原Armariris混淆过的字符串的插件。sample里面的test_linux_x86_64和test_macos_x86_64是示例二进制文件。 271 | 272 | > 如果本文说的有错误的地方,请及时指正,谢谢。 273 | 274 | -------------------------------------------------------------------------------- /ida/Armariris_string_obfuscation_bypass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | # @Author: smartdone 5 | # @Date: 2019-07-01 11:00 6 | 7 | import idaapi 8 | import idautils 9 | import idc 10 | 11 | from Simulator import Simulator 12 | 13 | sim = Simulator() 14 | for func in idautils.Functions(): 15 | func_name = idc.GetFunctionName(func) 16 | func_data = idaapi.get_func(func) 17 | start = func_data.start_ea 18 | end = func_data.end_ea 19 | # print(func_name, hex(start), hex(end)) 20 | if "datadiv_decode" in func_name and not ("j_.datadiv_decode" in func_name): 21 | sim.emu_start(start, end) 22 | 23 | sim.patch_segment('data') 24 | 25 | for seg in sim.segments: 26 | if "data" in seg['name']: 27 | # 把data段全部undefined 28 | print("MakeUnknown %s" % seg['name']) 29 | idc.MakeUnknown(seg['start'], seg['end'] - seg['start'], idaapi.DELIT_DELNAMES) 30 | # 调用ida重新解析data段 31 | print("analyze area: 0x%x - 0x%x" % (seg['start'], seg['end'])) 32 | idaapi.analyze_area(seg['start'], seg['end']) 33 | # idaapi.clear_strlist() 34 | # idaapi.build_strlist() 35 | 36 | # 查询string的交叉引用,在引用位置添加备注 37 | s = idautils.Strings(False) 38 | s.setup() 39 | for i, str_info in enumerate(s): 40 | if str_info: 41 | # print("%x: len=%d index=%d-> '%s'" % (str_info.ea, str_info.length, i, str(str_info))) 42 | str_cont = str(str_info) 43 | refs = idautils.DataRefsTo(str_info.ea) 44 | for ref in refs: 45 | idc.MakeComm(ref, str_cont) 46 | -------------------------------------------------------------------------------- /ida/Simulator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | # @Author: smartdone 5 | # @Date: 2019-07-01 11:00 6 | 7 | import idaapi 8 | import idc 9 | import idautils 10 | import sys 11 | 12 | sys.path.append('/usr/local/lib/python2.7/site-packages/') 13 | 14 | from unicorn import * 15 | from unicorn.x86_const import * 16 | from unicorn.arm_const import * 17 | from unicorn.arm64_const import * 18 | 19 | IMAGE_BASE = idaapi.get_imagebase() 20 | DEBUG = True 21 | 22 | 23 | def hook_code(uc, address, size, user_data): 24 | instruction = uc.mem_read(address, size) 25 | if instruction == b'\xc3': 26 | uc.emu_stop() 27 | 28 | if address == 0: 29 | uc.emu_stop() 30 | 31 | if address != 0 and address != IMAGE_BASE: 32 | idc.set_color(address, idc.CIC_ITEM, 0xFFB6C1) 33 | 34 | if DEBUG: 35 | _code = idc.GetDisasm(address) 36 | print("0x%016x \t%s" % (address, _code)) 37 | 38 | 39 | class Simulator(object): 40 | def __init__(self): 41 | self.segments = [] 42 | self.mem_map = [] 43 | 44 | self.ph_flag = None 45 | self.ph_id = None 46 | 47 | self.arch = None 48 | self.mode = None 49 | 50 | self.sp = None 51 | 52 | self.stack_base = 0 53 | self.stack_length = 1024 * 1024 * 2 54 | 55 | self.get_segments() 56 | self.get_arch() 57 | self.get_unicorn_mem_pages() 58 | 59 | def get_segments(self): 60 | if len(self.segments) == 0: 61 | for seg in idautils.Segments(): 62 | name = idc.SegName(seg) 63 | start = idc.SegStart(seg) 64 | end = idc.SegEnd(seg) 65 | d = idc.GetManyBytes(start, end - start) 66 | d = [ord(item) for item in list(d)] 67 | seg_data = {"name": name, "start": start, "end": end, "data": d} 68 | self.segments.append(seg_data) 69 | return self.segments 70 | 71 | def get_arch(self): 72 | self.ph_id = idaapi.ph.id 73 | self.ph_flag = idaapi.ph.flag 74 | 75 | if self.ph_id == idaapi.PLFM_386 and self.ph_flag & idaapi.PR_USE64: 76 | self.arch = UC_ARCH_X86 77 | self.mode = UC_MODE_64 78 | self.sp = UC_X86_REG_RSP 79 | elif self.ph_id == idaapi.PLFM_386 and self.ph_flag & idaapi.PR_USE32: 80 | self.arch = UC_ARCH_X86 81 | self.mode = UC_MODE_32 82 | self.sp = UC_X86_REG_RSP 83 | elif self.ph_id == idaapi.PLFM_ARM and self.ph_flag & idaapi.PR_USE32: 84 | self.arch = UC_ARCH_ARM 85 | self.mode = UC_MODE_ARM 86 | self.sp = UC_ARM_REG_SP 87 | elif self.ph_id == idaapi.PLFM_ARM and self.ph_flag & idaapi.PR_USE64: 88 | self.arch = UC_ARCH_ARM64 89 | self.mode = UC_MODE_ARM 90 | self.sp = UC_ARM64_REG_SP 91 | 92 | def is_thumb_ea(self, ea): 93 | if self.ph_id == idaapi.PLFM_ARM and not self.ph_flag & idaapi.PR_USE64: 94 | if idaapi.IDA_SDK_VERSION >= 700: 95 | try: 96 | t = idaapi.get_sreg(ea, 20) 97 | except: 98 | t = idaapi.get_sreg(ea, "T") 99 | else: 100 | t = idaapi.get_segreg(ea, 20) 101 | return t is not idaapi.BADSEL and t is not 0 102 | else: 103 | return False 104 | 105 | def emu_start(self, func_start, func_end): 106 | if self.arch == UC_ARCH_ARM: 107 | if self.is_thumb_ea(func_start): 108 | print("thumb mode") 109 | self.mode = UC_MODE_THUMB 110 | mu = Uc(self.arch, self.mode) 111 | 112 | for item in self.mem_map: 113 | Simulator.map_memory(mu, item['start'], item['length']) 114 | 115 | # 给栈分配内存 116 | Simulator.map_memory(mu, self.stack_base, self.stack_length) 117 | 118 | # 写入数据 119 | for item in self.segments: 120 | Simulator.write_memory(mu, item['start'], item['data']) 121 | 122 | # 配置寄存器 123 | mu.reg_write(self.sp, self.stack_base + 1024 * 1024) 124 | 125 | mu.hook_add(UC_HOOK_CODE, hook_code) 126 | 127 | try: 128 | # 开始执行 129 | if self.mode == UC_MODE_THUMB: 130 | mu.emu_start(func_start + 1, func_end) 131 | else: 132 | mu.emu_start(func_start, func_end) 133 | except Exception as e: 134 | print("Err: %s. Execution function failed.(The function address is 0x%x)" % (e, func_start)) 135 | 136 | # 读取数据 137 | for item in self.segments: 138 | _data = Simulator.read_memory(mu, item['start'], item['end']) 139 | self.replace_data(item['start'], _data) 140 | 141 | # unmap memory 142 | for item in self.mem_map: 143 | Simulator.unmap_memory(mu, item['start'], item['length']) 144 | 145 | Simulator.unmap_memory(mu, self.stack_base, self.stack_length) 146 | 147 | def replace_data(self, start, data): 148 | for i in range(len(self.segments)): 149 | if self.segments[i]['start'] == start: 150 | self.segments[i]['data'] = data 151 | 152 | @staticmethod 153 | def write_memory(mu, start, data): 154 | if isinstance(data, list): 155 | data = bytearray(data) 156 | mu.mem_write(start, bytes(data)) 157 | 158 | @staticmethod 159 | def read_memory(mu, start, end): 160 | _length = end - start 161 | _data = mu.mem_read(start, _length) 162 | return bytearray(_data) 163 | 164 | @staticmethod 165 | def map_memory(mu, start, _length): 166 | mu.mem_map(start, _length) 167 | print("map memory: offset 0x%x, size: 0x%x" % (start, _length)) 168 | 169 | @staticmethod 170 | def unmap_memory(mu, start, _length): 171 | mu.mem_unmap(start, _length) 172 | print("unmap memory: offset 0x%x, size: 0x%x" % (start, _length)) 173 | 174 | @staticmethod 175 | def get_base_and_len(base, length): 176 | _base = base - (base % (1024 * 1024)) 177 | _length = (length / (1024 * 1024) + 1) * 1024 * 1024 178 | return _base, _length 179 | 180 | def get_unicorn_mem_pages(self): 181 | if len(self.segments) == 0: 182 | return None 183 | 184 | if len(self.mem_map) == 0: 185 | seg = None 186 | pages = [] 187 | for item in self.segments: 188 | if not seg: 189 | seg = {'start': item['start'], 'end': item['end']} 190 | else: 191 | if item['start'] - seg['end'] > (1024 * 1024 * 2): 192 | pages.append(seg) 193 | seg = {'start': item['start'], 'end': item['end']} 194 | else: 195 | seg['end'] = item['end'] 196 | pages.append(seg) 197 | 198 | for item in pages: 199 | start, length = Simulator.get_base_and_len(item['start'], item['end'] - item['start']) 200 | self.mem_map.append({"start": start, "length": length}) 201 | 202 | for item in self.mem_map: 203 | if self.stack_base < item['start'] + item['length']: 204 | self.stack_base = item['start'] + item['length'] 205 | 206 | return self.mem_map 207 | 208 | def patch_segment(self, seg_name): 209 | for seg in self.segments: 210 | if seg_name in seg['name']: 211 | print("patch %s: 0x%x - 0x%x" %(seg['name'], seg['start'], seg['end'])) 212 | for i in range(len(seg['data'])): 213 | idc.patch_byte(seg['start'] + i, seg['data'][i]) 214 | -------------------------------------------------------------------------------- /ida/img/after1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/after1.png -------------------------------------------------------------------------------- /ida/img/after2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/after2.png -------------------------------------------------------------------------------- /ida/img/after3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/after3.png -------------------------------------------------------------------------------- /ida/img/after4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/after4.png -------------------------------------------------------------------------------- /ida/img/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/before.png -------------------------------------------------------------------------------- /ida/img/data_orig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/data_orig.png -------------------------------------------------------------------------------- /ida/img/xref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/img/xref.png -------------------------------------------------------------------------------- /ida/sample/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void fun(){ 4 | printf("test 3333\n"); 5 | } 6 | 7 | int main(int argc, char *argv[]) { 8 | printf("test 1111\n"); 9 | printf("test 2222\n"); 10 | fun(); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /ida/sample/test_linux_x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/sample/test_linux_x86_64 -------------------------------------------------------------------------------- /ida/sample/test_linux_x86_64_fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/sample/test_linux_x86_64_fla -------------------------------------------------------------------------------- /ida/sample/test_macos_x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartdone/re_scripts/275d21ea189cae5eac18a5f3b0c1875d4d0cf01b/ida/sample/test_macos_x86_64 -------------------------------------------------------------------------------- /jeb/FridaCodeGenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # @Author: smartdone 5 | # @Date: 2019-06-05 18:23 6 | 7 | # -*- coding: utf-8 -*- 8 | 9 | import sys 10 | 11 | reload(sys) 12 | sys.setdefaultencoding('utf8') 13 | 14 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 15 | from com.pnfsoftware.jeb.core import RuntimeProjectUtil 16 | from com.pnfsoftware.jeb.core.units.code.android import IDexUnit 17 | from com.pnfsoftware.jeb.core.util import DecompilerHelper 18 | from com.pnfsoftware.jeb.client.api import IconType, ButtonGroupType 19 | import os 20 | 21 | # hook_template = """ 22 | # if(Java.available) {{ 23 | # Java.perform(function(){{ 24 | # var application = Java.use("android.app.Application"); 25 | # 26 | # application.attach.overload('android.content.Context').implementation = function(context) {{ 27 | # var result = this.attach(context); // 先执行原来的attach方法 28 | # var classloader = context.getClassLoader(); // 获取classloader 29 | # Java.classFactory.loader = classloader; 30 | # 31 | # var {simple_class_name} = Java.classFactory.use("{full_class_name}"); 32 | # 33 | # {simple_class_name}.{method_name}.overload({types}).implementation = function({args}) {{ 34 | # {log_code} 35 | # }} 36 | # 37 | # return result; 38 | # }} 39 | # }}); 40 | # }} 41 | # """ 42 | 43 | hook_template = """ 44 | if(Java.available) {{ 45 | Java.perform(function(){{ 46 | 47 | var {simple_class_name} = Java.use("{full_class_name}"); 48 | 49 | {simple_class_name}.{method_name}.overload({types}).implementation = function({args}) {{ 50 | {log_code} 51 | }} 52 | 53 | }}); 54 | }} 55 | """ 56 | 57 | 58 | def generate_type_code(types): 59 | types = ["'{0}'".format(t) for t in types] 60 | return ", ".join(types) 61 | 62 | 63 | def generate_args_code(args): 64 | return ", ".join(args) 65 | 66 | 67 | def generate_log_code(types, retval, method_name, class_name, args): 68 | log_code = "" 69 | i = 0 70 | for _type in types: 71 | log_code += ' console.log("{class_name}->{method_name} (argType: {_type}): " + {arg});\n'.format( 72 | class_name=class_name, method_name=method_name, _type=_type, arg=args[i]) 73 | i += 1 74 | 75 | if retval != "void": 76 | log_code += ' var retval = this.{method_name}({args})\n'.format(method_name=method_name, 77 | args=", ".join(args)) 78 | log_code += ' console.log("{class_name}->{method_name} (retType: {_type}): " + retval)\n'.format( 79 | class_name=class_name, method_name=method_name, _type=retval) 80 | log_code += ' return retval;\n' 81 | else: 82 | log_code += ' this.{method_name}({args});\n'.format(method_name=method_name, args=", ".join(args)) 83 | 84 | return log_code 85 | 86 | 87 | class JavaMethod(object): 88 | def __init__(self): 89 | self.class_name = None 90 | self.name = None 91 | self.arg = [] 92 | self.retType = None 93 | 94 | def get_parameters(self): 95 | return self.arg 96 | 97 | def get_return_type(self): 98 | return self.retType 99 | 100 | def get_name(self): 101 | return self.name 102 | 103 | def get_class_name(self): 104 | return self.class_name 105 | 106 | def __str__(self): 107 | return "name: %s, args: %s, return type: %s" % (self.name, self.arg, self.retType) 108 | 109 | 110 | class FridaCodeGenerator(IScript): 111 | 112 | @staticmethod 113 | def to_canonical_name(dalvik_name): 114 | dalvik_name = dalvik_name.replace('/', '.') 115 | 116 | type_name = { 117 | 'C': "char", 118 | 'I': "int", 119 | 'B': "byte", 120 | 'Z': "boolean", 121 | 'F': "float", 122 | 'D': "double", 123 | 'S': "short", 124 | 'J': "long", 125 | 'V': "void", 126 | 'L': dalvik_name[1:-1], 127 | '[': dalvik_name 128 | } 129 | 130 | return type_name[dalvik_name[0]] 131 | 132 | def run(self, ctx): 133 | self.keys = {} 134 | 135 | engctx = ctx.getEnginesContext() 136 | if not engctx: 137 | print('Back-end engines not initialized') 138 | return 139 | 140 | projects = engctx.getProjects() 141 | if not projects: 142 | print('There is no opened project') 143 | return 144 | 145 | project = projects[0] # Get current project(IRuntimeProject) 146 | print('Decompiling code units of %s...' % project) 147 | 148 | self.dexunit = RuntimeProjectUtil.findUnitsByType(project, IDexUnit, False)[ 149 | 0] # Get dex context, needs >=V2.2.1 150 | try: 151 | self.current_unit = ctx.getFocusedView().getActiveFragment().getUnit() # Get current Source Tab in Focus 152 | # java_class = self.current_unit.getClassElement() # needs >V2.1.4 153 | current_addr = ctx.getFocusedView().getActiveFragment().getActiveAddress() # needs 2.1.4 154 | if "(" in current_addr: 155 | current_addr = current_addr.split("+")[0] 156 | print("current function: " + current_addr) 157 | m = FridaCodeGenerator.get_decompiled_method(self.dexunit, current_addr) 158 | 159 | method_name = m.get_name() 160 | class_name = FridaCodeGenerator.to_canonical_name(m.get_class_name()) 161 | 162 | return_type = FridaCodeGenerator.to_canonical_name(str(m.get_return_type())) 163 | 164 | if method_name == "": 165 | return 166 | 167 | args = [] 168 | for item in range(len(m.get_parameters())): 169 | # print(item.getIdentifier().getName()) 170 | args.append(str("arg_%d" % item)) 171 | 172 | types = [FridaCodeGenerator.to_canonical_name(param) for param in m.get_parameters()] 173 | 174 | simple_class_name = class_name.split('.')[-1].replace("$", "_") 175 | 176 | if method_name == "": method_name = "$init" 177 | 178 | type_code = generate_type_code(types) 179 | args_code = generate_args_code(args) 180 | log_code = generate_log_code(types, return_type, method_name, simple_class_name, 181 | args) 182 | 183 | hook_code = hook_template.format(simple_class_name=simple_class_name, 184 | full_class_name=class_name, 185 | method_name=method_name, 186 | types=type_code, 187 | args=args_code, 188 | log_code=log_code) 189 | print(hook_code) 190 | 191 | if not isinstance(ctx, IGraphicalClientContext): 192 | print('This script must be run within a graphical client') 193 | return 194 | file_name = 'hook_{class_name}.js'.format(class_name=simple_class_name) 195 | file_path = os.path.join(os.environ['HOME'], file_name) 196 | 197 | value = ctx.displayQuestionBox('Input', 198 | 'Enter the hook script save path(Save to directory {file_path} by default)' 199 | .format(file_path=file_path), file_path) 200 | # print(value) 201 | # with open(value) 202 | if value: 203 | file_path = value 204 | try: 205 | with open(file_path, "w+") as f: 206 | f.write(hook_code) 207 | f.flush() 208 | ctx.displayMessageBox('Information', 'Frida script save to \n{}'.format(file_path), 209 | IconType.INFORMATION, 210 | ButtonGroupType.OK) 211 | except Exception as e: 212 | print(e) 213 | 214 | else: 215 | print("Place the cursor in the function you want to generate the Frida code, then run the script") 216 | except Exception as e: 217 | print(e) 218 | print("Place the cursor in the function you want to generate the Frida code, then run the script") 219 | 220 | @staticmethod 221 | def get_decompiled_method(dex, msig): 222 | method_info = JavaMethod() 223 | infos = str(msig).split("->") 224 | if len(infos) == 2: 225 | method_info.class_name = infos[0] 226 | if len(infos[1].split("(")) == 2: 227 | method_info.name = infos[1].split("(")[0] 228 | if len(infos[1].split(")")) == 2: 229 | method_info.retType = infos[1].split(")")[1] 230 | if len(infos[1].split("(")) == 2 and len(infos[1].split(")")) == 2: 231 | args = infos[1].split("(")[-1].split(")")[0] 232 | while args: 233 | if args[0] in ['C', 'I', 'B', 'Z', 'F', 'D', 'S', 'J', 'V']: 234 | method_info.arg.append(str(args[0])) 235 | args = args[1:] 236 | elif args[0] == '[': 237 | if args[1] == 'L': 238 | offset = args.find(";") 239 | method_info.arg.append(str(args[0:offset + 1])) 240 | args = args[offset + 1:] 241 | else: 242 | method_info.arg.append(str(args[0:2])) 243 | args = args[2:] 244 | elif args[0] == 'L': 245 | offset = args.find(";") 246 | method_info.arg.append(str(args[0:offset + 1])) 247 | args = args[offset + 1:] 248 | print(method_info) 249 | 250 | return method_info 251 | --------------------------------------------------------------------------------