├── .gitignore ├── AesScan.py ├── README.md ├── TeaScan.py └── quickScan.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /AesScan.py: -------------------------------------------------------------------------------- 1 | from quickScan import * 2 | 3 | pe = pefile.PE("F:\\wrapper\\wrapper.28418.node") 4 | pe_image_base = pe.OPTIONAL_HEADER.ImageBase 5 | # 加载段区 6 | section_rdata_range = get_section_range_rva(pe, '.rdata') 7 | rdata_start, rdata_end = section_rdata_range 8 | 9 | section_text_range = get_section_range_rva(pe, '.text') 10 | text_start, text_end = section_text_range 11 | # 分析函数 12 | list_func_ranges = get_function_ranges(pe) 13 | 14 | 15 | # 分析AES Encrypt Function 16 | offset_text_aes_encrypt = search_bytes(pe, rdata_start, rdata_end, b'AES_gcm_256_encrypt') 17 | print('[debug] Maybe AES Encrypt Text: ',hex(offset_text_aes_encrypt+pe_image_base)) 18 | 19 | list_offset_aes_encrypt_xref = search_data_maybe_xref_all(pe, offset_text_aes_encrypt ,text_start,text_end) 20 | list_offset_aes_encrypt_func = [] 21 | for offset_aes_encrypt in list_offset_aes_encrypt_xref: 22 | rva = find_function_containing_address(list_func_ranges, offset_aes_encrypt) 23 | if rva: 24 | print('[debug] AES Encrypt Function: ',hex(rva['start']+pe_image_base)) 25 | if rva['start'] in list_offset_aes_encrypt_func: 26 | continue 27 | list_offset_aes_encrypt_func.append(rva['start']) 28 | print('[result] AES Encrypt Function: ',hex(list_offset_aes_encrypt_func[len(list_offset_aes_encrypt_func)-1]+pe_image_base)) 29 | 30 | # 分析AES Decrypt Function 31 | offset_text_aes_decrypt = search_bytes(pe, rdata_start, rdata_end, b'AES_gcm_256_decrypt') 32 | print('[debug] Maybe AES Decrypt Text: ',hex(offset_text_aes_decrypt+pe_image_base)) 33 | 34 | list_offset_aes_decrypt_xref = search_data_maybe_xref_all(pe, offset_text_aes_decrypt ,text_start,text_end) 35 | list_offset_aes_decrypt_func = [] 36 | for offset_aes_decrypt in list_offset_aes_decrypt_xref: 37 | rva = find_function_containing_address(list_func_ranges, offset_aes_decrypt) 38 | if rva: 39 | print('[debug] AES Decrypt Function: ',hex(rva['start']+pe_image_base)) 40 | if rva['start'] in list_offset_aes_decrypt_func: 41 | continue 42 | list_offset_aes_decrypt_func.append(rva['start']) 43 | print('[result] AES Decrypt Function: ',hex(list_offset_aes_decrypt_func[len(list_offset_aes_decrypt_func)-1]+pe_image_base)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NTQQScanQuick 2 | 快速对NTQQ进行Scan分析Offset 3 | 4 | ## 特点 5 | - [x] 几乎不受版本约束? 6 | - [x] 速度遥遥领先IDA! 7 | - [x] 暴力的方法就是豪. 8 | 9 | ## 进度 10 | - [x] Aes Scan 11 | - [x] 业务Tea Scan 12 | - [x] 登录Tea Scan 13 | 14 | ## 运行 15 | 修改 pe 文件地址即刻分析 16 | 17 | ## 存在的小问题 18 | - [x] 已修复: 由于get_function_ranges实现 导致部分函数定位的是push rbp位置 距离目标函数还有几字节 19 | -------------------------------------------------------------------------------- /TeaScan.py: -------------------------------------------------------------------------------- 1 | from quickScan import * 2 | 3 | pe = pefile.PE("F:\\wrapper\\wrapper.28418.node") 4 | pe_image_base = pe.OPTIONAL_HEADER.ImageBase 5 | # 加载段区 6 | section_rdata_range = get_section_range_rva(pe, '.rdata') 7 | rdata_start, rdata_end = section_rdata_range 8 | 9 | section_text_range = get_section_range_rva(pe, '.text') 10 | text_start, text_end = section_text_range 11 | # 分析函数 12 | list_func_ranges = get_function_ranges(pe) 13 | 14 | print('[]----------[]') 15 | # delta = 0x9E3779B9 小端序 16 | list_tea_detal = search_data_pattern_all(pe, 'B9 79 37 9E', text_start, text_end) 17 | list_inner_crypt = [] 18 | list_tea_crypt_datal_outer = [] 19 | for tea_detal in list_tea_detal: 20 | tea_crypt_inner = find_function_containing_address(list_func_ranges, tea_detal) 21 | if tea_crypt_inner: 22 | if tea_crypt_inner['start'] in list_inner_crypt: 23 | continue 24 | list_inner_crypt.append(tea_crypt_inner['start']) 25 | print('[debug] Maybe TEA Crypt Inner: ', hex(tea_crypt_inner['start']+pe_image_base)) 26 | tea_crypt_outer = search_call_maybe_xref(pe, tea_crypt_inner['start'], text_start, text_end) 27 | if tea_crypt_outer: 28 | print('[debug] Maybe TEA Crypt Inner Xref: ', hex(tea_crypt_outer+pe_image_base)) 29 | tea_crypt_outer_func = find_function_containing_address(list_func_ranges, tea_crypt_outer) 30 | if tea_crypt_outer_func: 31 | print('[result] TEA Crypt Outer: ', hex(tea_crypt_outer_func['start']+pe_image_base)) 32 | list_tea_crypt_datal_outer.append(tea_crypt_outer_func['start']) 33 | print('[]----------[]') 34 | 35 | # sum = 0xE3779B90 小端序 36 | list_tea_sum = search_data_pattern_all(pe, '90 9B 77 E3', text_start, text_end) 37 | list_inner_decrypt = [] 38 | list_tea_decrypt_datal_outer = [] 39 | for tea_sum in list_tea_sum: 40 | tea_decrypt_inner = find_function_containing_address(list_func_ranges, tea_sum) 41 | if tea_decrypt_inner: 42 | if tea_decrypt_inner['start'] in list_inner_decrypt: 43 | continue 44 | list_inner_decrypt.append(tea_decrypt_inner['start']) 45 | print('[debug] Maybe TEA Crypt Inner: ', hex(tea_decrypt_inner['start']+pe_image_base)) 46 | tea_decrypt_outer = search_call_maybe_xref(pe, tea_decrypt_inner['start'], text_start, text_end) 47 | if tea_decrypt_outer: 48 | print('[debug] Maybe TEA Crypt Inner Xref: ', hex(tea_decrypt_outer+pe_image_base)) 49 | tea_decrypt_outer_func = find_function_containing_address(list_func_ranges, tea_decrypt_outer) 50 | if tea_decrypt_outer_func: 51 | print('[result] TEA Crypt Outer: ', hex(tea_decrypt_outer_func['start']+pe_image_base)) 52 | list_tea_decrypt_datal_outer.append(tea_decrypt_outer_func['start']) 53 | 54 | print('[]----------[]') -------------------------------------------------------------------------------- /quickScan.py: -------------------------------------------------------------------------------- 1 | import pefile 2 | from capstone import * 3 | 4 | def get_function_ranges(pe): 5 | md = Cs(CS_ARCH_X86, CS_MODE_64) 6 | md.skipdata = True 7 | text_section = next(s for s in pe.sections if s.Name.startswith(b'.text')) 8 | code = text_section.get_data() 9 | start_addr = text_section.VirtualAddress 10 | end_addr = start_addr + text_section.Misc_VirtualSize 11 | 12 | functions = [] 13 | current_func = None 14 | 15 | insn_list = list(md.disasm(code, start_addr)) 16 | 17 | i = 0 18 | while i < len(insn_list): 19 | insn = insn_list[i] 20 | if insn.mnemonic == 'push' and insn.op_str == 'rbp': 21 | # 向上搜索连续的push指令 22 | j = i - 1 23 | while j >= 0 and insn_list[j].mnemonic == 'push': 24 | j -= 1 25 | if current_func: 26 | functions.append(current_func) 27 | current_func = {'start': insn_list[j + 1].address, 'end': None} 28 | elif insn.mnemonic == 'mov' and insn.op_str == 'rbp, rsp': 29 | if current_func: 30 | functions.append(current_func) 31 | current_func = {'start': insn.address, 'end': None} 32 | if current_func: 33 | current_func['end'] = insn.address + insn.size 34 | i += 1 35 | 36 | if current_func: 37 | functions.append(current_func) 38 | 39 | return functions 40 | 41 | def find_function_containing_address(functions, address): 42 | for func in functions: 43 | if func['start'] <= address < func['end']: 44 | return func 45 | return None 46 | 47 | # 限定范围 48 | def get_section_range_rva(pe, section_name): 49 | for section in pe.sections: 50 | if section.Name.decode().strip('\x00') == section_name: 51 | return section.VirtualAddress, section.VirtualAddress + section.SizeOfRawData 52 | return None, None 53 | 54 | def get_section_range_real(pe, section_name): 55 | (start,end) = get_section_range_rva(pe, section_name) 56 | if start and end: 57 | start += pe.OPTIONAL_HEADER.ImageBase 58 | end += pe.OPTIONAL_HEADER.ImageBase 59 | return start, end 60 | return None, None 61 | 62 | def search_bytes(pe, start, end, bytes): 63 | for i in range(start, end+1): 64 | if pe.get_data(i, len(bytes)) == bytes: 65 | return i 66 | return None 67 | 68 | # 暴力引用查找 汇编分析?不存在的 69 | 70 | def search_data_maybe_xref_all(pe, string_rva, start, end): 71 | rva_list = [] 72 | for i in range(start, end+1): 73 | if int.from_bytes(pe.get_data(i, 4), 'little',signed=True) + i + 4 == string_rva: 74 | rva_list.append(i) 75 | return rva_list 76 | 77 | def search_data_maybe_xref(pe, string_rva, start, end): 78 | for i in range(start, end+1): 79 | if int.from_bytes(pe.get_data(i, 4), 'little',signed=True) + i + 4 == string_rva: 80 | return i 81 | return None 82 | 83 | # search_data_maybe_xref和search_data_maybe_xref_all 忘记兼容上跳的情况了 已兼容 84 | def search_call_maybe_xref(pe, string_rva, start, end): 85 | for i in range(start, end+1): 86 | if int.from_bytes(pe.get_data(i, 4), 'little',signed=True) + i + 4 == string_rva: 87 | return i 88 | return None 89 | 90 | def search_data_pattern_all(pe, pattern, start, end): 91 | rva_list = [] 92 | pattern = pattern.replace(' ', '').replace('0x', '').lower() 93 | for i in range(start, end+1): 94 | hex_data = ''.join(['%02x' % x for x in pe.get_data(i, len(pattern) // 2)]) 95 | for j in range(0, len(pattern)): 96 | if pattern[j] != '?' and pattern[j] != hex_data[j]: 97 | break 98 | else: 99 | rva_list.append(i) 100 | return rva_list 101 | 102 | def search_data_pattern(pe, pattern, start, end): 103 | pattern = pattern.replace(' ', '').replace('0x', '').lower() 104 | for i in range(start, end+1): 105 | hex_data = ''.join(['%02x' % x for x in pe.get_data(i, len(pattern) // 2)]) 106 | for j in range(0, len(pattern)): 107 | if pattern[j] != '?' and pattern[j] != hex_data[j]: 108 | break 109 | else: 110 | return i 111 | return None 112 | --------------------------------------------------------------------------------