ida-batch-decompile 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ida_batch_decompile-0.1.0/PKG-INFO +163 -0
- ida_batch_decompile-0.1.0/README.md +138 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile/__init__.py +4 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile/cli.py +7 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile/idb_batch_decompile.py +731 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile.egg-info/PKG-INFO +163 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile.egg-info/SOURCES.txt +11 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile.egg-info/dependency_links.txt +1 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile.egg-info/entry_points.txt +2 -0
- ida_batch_decompile-0.1.0/ida_batch_decompile.egg-info/top_level.txt +1 -0
- ida_batch_decompile-0.1.0/pyproject.toml +42 -0
- ida_batch_decompile-0.1.0/setup.cfg +4 -0
- ida_batch_decompile-0.1.0/setup.py +5 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ida-batch-decompile
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: IDA Pro batch decompiler tool - use idat to batch decompile binary files to C code
|
|
5
|
+
Author: IDA2C Auto
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourorg/ida-batch-decompile
|
|
8
|
+
Project-URL: Documentation, https://github.com/yourorg/ida-batch-decompile#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/yourorg/ida-batch-decompile.git
|
|
10
|
+
Project-URL: Issues, https://github.com/yourorg/ida-batch-decompile/issues
|
|
11
|
+
Keywords: ida,ida-pro,decompiler,reverse-engineering,idaapi,hex-rays
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Requires-Python: >=3.7
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# IDA Pro 批量反编译工具
|
|
27
|
+
|
|
28
|
+
使用 IDA Pro 的命令行工具 `idat` 配合 IDAPython 批量反编译二进制文件,生成干净的 C 代码。
|
|
29
|
+
|
|
30
|
+
## 功能特性
|
|
31
|
+
|
|
32
|
+
- 扫描目录并自动识别可执行文件和共享库
|
|
33
|
+
- 使用 Hex-Rays `decompile_many` API 生成高质量的 C 代码
|
|
34
|
+
- 支持并行处理
|
|
35
|
+
- 自动等待分析完成
|
|
36
|
+
- 支持 Mach-O、ELF、PE 等格式
|
|
37
|
+
|
|
38
|
+
## 依赖
|
|
39
|
+
|
|
40
|
+
- IDA Pro 6.9+(推荐 9.0+)
|
|
41
|
+
- Python 3.x
|
|
42
|
+
|
|
43
|
+
## 安装
|
|
44
|
+
|
|
45
|
+
将工具放入你的 IDA Pro 目录,确保 `idat` 可执行文件可访问。
|
|
46
|
+
|
|
47
|
+
## 使用方法
|
|
48
|
+
|
|
49
|
+
### 基本用法
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 扫描目录并批量反编译
|
|
53
|
+
python3 idb_batch_decompile.py -i <输入目录> -o <输出目录>
|
|
54
|
+
|
|
55
|
+
# 指定 idat 路径
|
|
56
|
+
python3 idb_batch_decompile.py \
|
|
57
|
+
-i /path/to/binaries \
|
|
58
|
+
-o ./output \
|
|
59
|
+
-p "/Applications/IDA Professional 9.1.app/Contents/MacOS/idat"
|
|
60
|
+
|
|
61
|
+
# 详细输出
|
|
62
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -v
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 完整参数
|
|
66
|
+
|
|
67
|
+
| 参数 | 说明 |
|
|
68
|
+
|------|------|
|
|
69
|
+
| `-i, --input-dir` | 要扫描的输入目录或单个二进制文件 |
|
|
70
|
+
| `-o, --output-dir` | C 文件输出目录 |
|
|
71
|
+
| `-p, --idat-path` | idat 可执行文件路径 |
|
|
72
|
+
| `-j, --jobs` | 并行处理的进程数 (默认: 4) |
|
|
73
|
+
| `-t, --timeout` | 每个文件的超时时间,单位秒 (默认: 300) |
|
|
74
|
+
| `-v, --verbose` | 显示详细输出 |
|
|
75
|
+
| `-h, --help` | 显示帮助信息 |
|
|
76
|
+
|
|
77
|
+
### 示例
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 处理单个文件
|
|
81
|
+
python3 idb_batch_decompile.py -i ./hello -o ./output
|
|
82
|
+
|
|
83
|
+
# 处理整个目录
|
|
84
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./decompiled
|
|
85
|
+
|
|
86
|
+
# 使用 8 个并行进程
|
|
87
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -j 8
|
|
88
|
+
|
|
89
|
+
# 增加超时时间(处理大型文件)
|
|
90
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -t 600
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 输出格式
|
|
94
|
+
|
|
95
|
+
生成的 C 文件格式如下:
|
|
96
|
+
|
|
97
|
+
```c
|
|
98
|
+
/* This file was generated by the Hex-Rays decompiler version 9.1.0.250226.
|
|
99
|
+
Copyright (c) 2007-2021 Hex-Rays <info@hex-rays.com>
|
|
100
|
+
|
|
101
|
+
Detected compiler: GNU C++
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
#include <defs.h>
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
//-------------------------------------------------------------------------
|
|
108
|
+
// Function declarations
|
|
109
|
+
|
|
110
|
+
int __fastcall main(int argc, const char **argv, const char **envp);
|
|
111
|
+
int printf(const char *, ...);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
//----- (0000000100000460) ----------------------------------------------------
|
|
115
|
+
int __fastcall main(int argc, const char **argv, const char **envp)
|
|
116
|
+
{
|
|
117
|
+
printf("Hello, World!\n");
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
输出文件以原二进制文件名命名,例如 `hello` → `hello.c`。
|
|
123
|
+
|
|
124
|
+
## 目录结构
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
ida2c_auto/
|
|
128
|
+
├── idb_batch_decompile.py # 主工具脚本
|
|
129
|
+
├── idapython_export_c.py # IDAPython 导出脚本
|
|
130
|
+
├── README.md # 本文件
|
|
131
|
+
├── test_code/ # 测试文件目录
|
|
132
|
+
│ ├── hello.c
|
|
133
|
+
│ ├── math_lib.c
|
|
134
|
+
│ ├── string_utils.c
|
|
135
|
+
│ └── ...
|
|
136
|
+
└── test_output/ # 测试输出目录
|
|
137
|
+
├── hello.c
|
|
138
|
+
├── math_lib.c
|
|
139
|
+
├── string_utils.c
|
|
140
|
+
└── ...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 实现原理
|
|
144
|
+
|
|
145
|
+
工具使用 `ida_hexrays.decompile_many()` API:
|
|
146
|
+
|
|
147
|
+
1. 使用 `idat -A` 以自动模式加载二进制文件
|
|
148
|
+
2. 等待 IDA 自动分析完成
|
|
149
|
+
3. 调用 `decompile_many()` 导出所有函数的 C 代码
|
|
150
|
+
4. 保存为 `.c` 文件
|
|
151
|
+
|
|
152
|
+
这与 IDA GUI 的"导出 C 代码"功能使用相同的底层 API,因此输出质量相同。
|
|
153
|
+
|
|
154
|
+
## 注意事项
|
|
155
|
+
|
|
156
|
+
- 确保 IDA Pro 已正确安装且 `idat` 可执行文件存在
|
|
157
|
+
- 某些保护/混淆的二进制文件可能无法完整反编译
|
|
158
|
+
- 处理大型文件时可能需要增加超时时间
|
|
159
|
+
- 并行处理时请注意系统资源限制
|
|
160
|
+
|
|
161
|
+
## 许可证
|
|
162
|
+
|
|
163
|
+
本工具仅供学习和研究使用。请遵守 IDA Pro 的使用协议。
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# IDA Pro 批量反编译工具
|
|
2
|
+
|
|
3
|
+
使用 IDA Pro 的命令行工具 `idat` 配合 IDAPython 批量反编译二进制文件,生成干净的 C 代码。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 扫描目录并自动识别可执行文件和共享库
|
|
8
|
+
- 使用 Hex-Rays `decompile_many` API 生成高质量的 C 代码
|
|
9
|
+
- 支持并行处理
|
|
10
|
+
- 自动等待分析完成
|
|
11
|
+
- 支持 Mach-O、ELF、PE 等格式
|
|
12
|
+
|
|
13
|
+
## 依赖
|
|
14
|
+
|
|
15
|
+
- IDA Pro 6.9+(推荐 9.0+)
|
|
16
|
+
- Python 3.x
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
将工具放入你的 IDA Pro 目录,确保 `idat` 可执行文件可访问。
|
|
21
|
+
|
|
22
|
+
## 使用方法
|
|
23
|
+
|
|
24
|
+
### 基本用法
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 扫描目录并批量反编译
|
|
28
|
+
python3 idb_batch_decompile.py -i <输入目录> -o <输出目录>
|
|
29
|
+
|
|
30
|
+
# 指定 idat 路径
|
|
31
|
+
python3 idb_batch_decompile.py \
|
|
32
|
+
-i /path/to/binaries \
|
|
33
|
+
-o ./output \
|
|
34
|
+
-p "/Applications/IDA Professional 9.1.app/Contents/MacOS/idat"
|
|
35
|
+
|
|
36
|
+
# 详细输出
|
|
37
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -v
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 完整参数
|
|
41
|
+
|
|
42
|
+
| 参数 | 说明 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| `-i, --input-dir` | 要扫描的输入目录或单个二进制文件 |
|
|
45
|
+
| `-o, --output-dir` | C 文件输出目录 |
|
|
46
|
+
| `-p, --idat-path` | idat 可执行文件路径 |
|
|
47
|
+
| `-j, --jobs` | 并行处理的进程数 (默认: 4) |
|
|
48
|
+
| `-t, --timeout` | 每个文件的超时时间,单位秒 (默认: 300) |
|
|
49
|
+
| `-v, --verbose` | 显示详细输出 |
|
|
50
|
+
| `-h, --help` | 显示帮助信息 |
|
|
51
|
+
|
|
52
|
+
### 示例
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 处理单个文件
|
|
56
|
+
python3 idb_batch_decompile.py -i ./hello -o ./output
|
|
57
|
+
|
|
58
|
+
# 处理整个目录
|
|
59
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./decompiled
|
|
60
|
+
|
|
61
|
+
# 使用 8 个并行进程
|
|
62
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -j 8
|
|
63
|
+
|
|
64
|
+
# 增加超时时间(处理大型文件)
|
|
65
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -t 600
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 输出格式
|
|
69
|
+
|
|
70
|
+
生成的 C 文件格式如下:
|
|
71
|
+
|
|
72
|
+
```c
|
|
73
|
+
/* This file was generated by the Hex-Rays decompiler version 9.1.0.250226.
|
|
74
|
+
Copyright (c) 2007-2021 Hex-Rays <info@hex-rays.com>
|
|
75
|
+
|
|
76
|
+
Detected compiler: GNU C++
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
#include <defs.h>
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
//-------------------------------------------------------------------------
|
|
83
|
+
// Function declarations
|
|
84
|
+
|
|
85
|
+
int __fastcall main(int argc, const char **argv, const char **envp);
|
|
86
|
+
int printf(const char *, ...);
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
//----- (0000000100000460) ----------------------------------------------------
|
|
90
|
+
int __fastcall main(int argc, const char **argv, const char **envp)
|
|
91
|
+
{
|
|
92
|
+
printf("Hello, World!\n");
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
输出文件以原二进制文件名命名,例如 `hello` → `hello.c`。
|
|
98
|
+
|
|
99
|
+
## 目录结构
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
ida2c_auto/
|
|
103
|
+
├── idb_batch_decompile.py # 主工具脚本
|
|
104
|
+
├── idapython_export_c.py # IDAPython 导出脚本
|
|
105
|
+
├── README.md # 本文件
|
|
106
|
+
├── test_code/ # 测试文件目录
|
|
107
|
+
│ ├── hello.c
|
|
108
|
+
│ ├── math_lib.c
|
|
109
|
+
│ ├── string_utils.c
|
|
110
|
+
│ └── ...
|
|
111
|
+
└── test_output/ # 测试输出目录
|
|
112
|
+
├── hello.c
|
|
113
|
+
├── math_lib.c
|
|
114
|
+
├── string_utils.c
|
|
115
|
+
└── ...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 实现原理
|
|
119
|
+
|
|
120
|
+
工具使用 `ida_hexrays.decompile_many()` API:
|
|
121
|
+
|
|
122
|
+
1. 使用 `idat -A` 以自动模式加载二进制文件
|
|
123
|
+
2. 等待 IDA 自动分析完成
|
|
124
|
+
3. 调用 `decompile_many()` 导出所有函数的 C 代码
|
|
125
|
+
4. 保存为 `.c` 文件
|
|
126
|
+
|
|
127
|
+
这与 IDA GUI 的"导出 C 代码"功能使用相同的底层 API,因此输出质量相同。
|
|
128
|
+
|
|
129
|
+
## 注意事项
|
|
130
|
+
|
|
131
|
+
- 确保 IDA Pro 已正确安装且 `idat` 可执行文件存在
|
|
132
|
+
- 某些保护/混淆的二进制文件可能无法完整反编译
|
|
133
|
+
- 处理大型文件时可能需要增加超时时间
|
|
134
|
+
- 并行处理时请注意系统资源限制
|
|
135
|
+
|
|
136
|
+
## 许可证
|
|
137
|
+
|
|
138
|
+
本工具仅供学习和研究使用。请遵守 IDA Pro 的使用协议。
|
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Batch decompiler module - CLI entry point is in cli.py."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import argparse
|
|
7
|
+
import subprocess
|
|
8
|
+
import multiprocessing
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
# IDA Pro 要处理的文件类型
|
|
13
|
+
FILE_TYPES = {
|
|
14
|
+
'executable': {
|
|
15
|
+
'description': '可执行文件 (Mach-O, ELF, PE)',
|
|
16
|
+
'extensions': ['', '.out', '.exe', '.elf', '.bin'],
|
|
17
|
+
'magic_bytes': {
|
|
18
|
+
b'\x7fELF': 'ELF',
|
|
19
|
+
b'\xCF\xFA\xED\xFE': 'Mach-O 64-bit', # 0xFEEDFACE reversed
|
|
20
|
+
b'\xCE\xFA\xED\xFE': 'Mach-O 32-bit',
|
|
21
|
+
b'\x4D\x5A': 'PE/DOS executable',
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
'sharedlib': {
|
|
25
|
+
'description': '共享库/动态库 (Mach-O dylib, ELF so, DLL)',
|
|
26
|
+
'extensions': ['.so', '.dylib', '.dll'],
|
|
27
|
+
'magic_bytes': {
|
|
28
|
+
b'\x7fELF': 'ELF shared library',
|
|
29
|
+
b'\xCF\xFA\xED\xFE': 'Mach-O 64-bit dylib',
|
|
30
|
+
b'\xCE\xFA\xED\xFE': 'Mach-O 32-bit dylib',
|
|
31
|
+
b'\x4D\x5A': 'PE DLL',
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def detect_file_type(file_path: Path) -> Optional[Tuple[str, str]]:
|
|
38
|
+
"""
|
|
39
|
+
检测文件类型
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
file_path: 文件路径
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
(file_category, file_description) 或 None
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
with open(file_path, 'rb') as f:
|
|
49
|
+
# 读取前 4 字节用于 magic number 检测
|
|
50
|
+
magic = f.read(4)
|
|
51
|
+
|
|
52
|
+
for file_type, config in FILE_TYPES.items():
|
|
53
|
+
for magic_bytes, desc in config['magic_bytes'].items():
|
|
54
|
+
if magic.startswith(magic_bytes):
|
|
55
|
+
return (file_type, desc)
|
|
56
|
+
|
|
57
|
+
# 对于 Mach-O,还需要检查 32/64 位
|
|
58
|
+
if len(magic) >= 4:
|
|
59
|
+
# Mach-O 字节序检查
|
|
60
|
+
if magic[:2] in (b'\xfe\xed', b'\xce\xfa'):
|
|
61
|
+
# 读取更多字节判断
|
|
62
|
+
f.seek(0)
|
|
63
|
+
macho_header = f.read(20)
|
|
64
|
+
if len(macho_header) >= 20:
|
|
65
|
+
# 检查 cpu type
|
|
66
|
+
import struct
|
|
67
|
+
if len(magic) >= 4:
|
|
68
|
+
# 简单检查 - 64 位 Mach-O 的 cputype 位 0x01000007
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# 扩展名检查
|
|
72
|
+
for file_type, config in FILE_TYPES.items():
|
|
73
|
+
if any(file_path.suffix.lower() == ext for ext in config['extensions']):
|
|
74
|
+
# 只有当扩展名匹配但 magic 不确定时才返回
|
|
75
|
+
# 如果已经通过 magic 检测到,就不需要这里
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
except (IOError, PermissionError):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def is_binary_file(file_path: Path) -> bool:
|
|
85
|
+
"""判断文件是否为可执行文件或共享库"""
|
|
86
|
+
try:
|
|
87
|
+
# 排除已知的非二进制文件
|
|
88
|
+
excluded_extensions = {'.py', '.pyc', '.txt', '.md', '.json', '.xml', '.yaml',
|
|
89
|
+
'.yml', '.log', '.cfg', '.ini', '.conf', '.html', '.css',
|
|
90
|
+
'.js', '.ts', '.java', '.c', '.cpp', '.h', '.hpp'}
|
|
91
|
+
|
|
92
|
+
if file_path.suffix.lower() in excluded_extensions:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
# 检查文件大小
|
|
96
|
+
if file_path.stat().st_size < 64: # 太小的文件不是二进制
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# 检查是否是文本文件
|
|
100
|
+
try:
|
|
101
|
+
with open(file_path, 'rb') as f:
|
|
102
|
+
chunk = f.read(8192)
|
|
103
|
+
# 检查空字节 - 二进制文件通常包含
|
|
104
|
+
if b'\x00' in chunk:
|
|
105
|
+
return True
|
|
106
|
+
# 检查是否是纯文本
|
|
107
|
+
try:
|
|
108
|
+
chunk.decode('utf-8')
|
|
109
|
+
# 纯文本,可能是脚本,检查 shebang
|
|
110
|
+
if chunk.startswith(b'#!'):
|
|
111
|
+
return True # 脚本也可能需要处理
|
|
112
|
+
except UnicodeDecodeError:
|
|
113
|
+
return True
|
|
114
|
+
except (IOError, PermissionError):
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
# 检测 magic number
|
|
118
|
+
with open(file_path, 'rb') as f:
|
|
119
|
+
magic = f.read(4)
|
|
120
|
+
|
|
121
|
+
for config in FILE_TYPES.values():
|
|
122
|
+
if magic[:2] in (b'\x7fE', b'\xCF\xFA', b'\xCE\xFA', b'\x4D\x5A'):
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
# 检查扩展名
|
|
126
|
+
for config in FILE_TYPES.values():
|
|
127
|
+
if any(file_path.suffix.lower() == ext for ext in config['extensions']):
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
except (IOError, OSError, PermissionError):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def find_binaries(directory: Path) -> List[Path]:
|
|
137
|
+
"""在目录中递归查找所有二进制文件"""
|
|
138
|
+
binaries = []
|
|
139
|
+
|
|
140
|
+
for root, dirs, files in os.walk(directory):
|
|
141
|
+
# 跳过常见非二进制目录
|
|
142
|
+
dirs[:] = [d for d in dirs if d not in {'.git', '.svn', '.idea', '.vscode',
|
|
143
|
+
'__pycache__', 'node_modules',
|
|
144
|
+
'.cache', 'build', 'dist'}]
|
|
145
|
+
|
|
146
|
+
for filename in files:
|
|
147
|
+
file_path = Path(root) / filename
|
|
148
|
+
|
|
149
|
+
# 快速初筛
|
|
150
|
+
if not is_binary_file(file_path):
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# 详细检测
|
|
154
|
+
file_type = detect_file_type(file_path)
|
|
155
|
+
if file_type:
|
|
156
|
+
binaries.append(file_path)
|
|
157
|
+
print(f" Found: {file_type[0]:15} -> {file_path}")
|
|
158
|
+
|
|
159
|
+
return binaries
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def create_idapython_script(output_dir: Path) -> Path:
|
|
163
|
+
"""
|
|
164
|
+
创建 IDAPython 脚本用于导出反编译的 C 代码
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
output_dir: 输出目录
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
脚本文件路径
|
|
171
|
+
"""
|
|
172
|
+
script_content = """
|
|
173
|
+
import os
|
|
174
|
+
import idaapi
|
|
175
|
+
import idc
|
|
176
|
+
import idautils
|
|
177
|
+
|
|
178
|
+
# 获取输出目录
|
|
179
|
+
output_dir = r\"{output_dir}\"
|
|
180
|
+
|
|
181
|
+
# 获取二进制文件名(不带扩展名)
|
|
182
|
+
input_file = idc.get_input_file_path()
|
|
183
|
+
bin_name = os.path.splitext(os.path.basename(input_file))[0]
|
|
184
|
+
output_file = os.path.join(output_dir, bin_name + ".c")
|
|
185
|
+
|
|
186
|
+
print(f"[IDAPython] 开始导出反编译代码: {output_file}")
|
|
187
|
+
|
|
188
|
+
# 确保输出目录存在
|
|
189
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
190
|
+
|
|
191
|
+
# 获取所有函数
|
|
192
|
+
functions = list(idautils.Functions())
|
|
193
|
+
print(f"[IDAPython] 找到 {len(functions)} 个函数")
|
|
194
|
+
|
|
195
|
+
# 生成 C 代码
|
|
196
|
+
try:
|
|
197
|
+
# 使用 IDAPython 的 decompile 接口
|
|
198
|
+
import ida_hexrays
|
|
199
|
+
|
|
200
|
+
if ida_hexrays.init_hexrays_plugin(0):
|
|
201
|
+
print("[IDAPython] Hex-Rays 反编译器可用")
|
|
202
|
+
else:
|
|
203
|
+
print("[IDAPython] Hex-Rays 反编译器不可用,尝试使用内置反编译")
|
|
204
|
+
|
|
205
|
+
except ImportError:
|
|
206
|
+
print("[IDAPython] Hex-Rays 模块不可用")
|
|
207
|
+
|
|
208
|
+
# 导出 C 代码
|
|
209
|
+
# 方法1: 使用 idc.Export() 如果可用
|
|
210
|
+
try:
|
|
211
|
+
if idaapi.export_to_c(output_file, 0, idc.BADADDR, 0) == 0:
|
|
212
|
+
print(f"[IDAPython] 成功导出 C 代码: {output_file}")
|
|
213
|
+
else:
|
|
214
|
+
print(f"[IDAPython] 导出失败")
|
|
215
|
+
except Exception as e:
|
|
216
|
+
print(f"[IDAPython] 导出异常: {e}")
|
|
217
|
+
# 方法2: 尝试使用 idc.Save Base
|
|
218
|
+
try:
|
|
219
|
+
# 生成伪代码
|
|
220
|
+
idc.generate_file(idc.GFL_CDECOMP, output_file, 0, idc.BADADDR, 0)
|
|
221
|
+
print(f"[IDAPython] 使用 GFL_CDECOMP 导出: {output_file}")
|
|
222
|
+
except Exception as e2:
|
|
223
|
+
print(f"[IDAPython] 两个方法都失败: {e2}")
|
|
224
|
+
|
|
225
|
+
# 退出 IDA
|
|
226
|
+
print("[IDAPython] 完成,退出 IDA...")
|
|
227
|
+
idaapi.qexit(0)
|
|
228
|
+
""".format(output_dir=output_dir)
|
|
229
|
+
|
|
230
|
+
script_path = Path(__file__).parent / "idapython_export_c.py"
|
|
231
|
+
script_path.write_text(script_content)
|
|
232
|
+
return script_path
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def create_idapython_script_v2(output_dir: Path) -> Path:
|
|
236
|
+
"""
|
|
237
|
+
创建改进版 IDAPython 脚本,使用 IDC 的 generate_file
|
|
238
|
+
"""
|
|
239
|
+
script_content = f"""import os
|
|
240
|
+
import idaapi
|
|
241
|
+
import idc
|
|
242
|
+
|
|
243
|
+
# 输出目录
|
|
244
|
+
output_dir = r"{output_dir}"
|
|
245
|
+
|
|
246
|
+
# 获取输入文件信息
|
|
247
|
+
input_file = idc.get_input_file_path()
|
|
248
|
+
bin_name = os.path.splitext(os.path.basename(input_file))[0]
|
|
249
|
+
output_file = os.path.join(output_dir, bin_name + ".c")
|
|
250
|
+
|
|
251
|
+
print(f"Processing: {input_file}")
|
|
252
|
+
print(f"Output: {output_file}")
|
|
253
|
+
|
|
254
|
+
# 确保输出目录存在
|
|
255
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
256
|
+
|
|
257
|
+
# 使用 IDC 的 generate_file 函数导出 C 代码
|
|
258
|
+
# GFL_CDECOMP = 0x0004 (C source file)
|
|
259
|
+
try:
|
|
260
|
+
result = idc.generate_file(idc.GFL_CDECOMP, output_file, 0, idc.BADADDR, 0)
|
|
261
|
+
if result == 0:
|
|
262
|
+
print(f"SUCCESS: Generated {output_file}")
|
|
263
|
+
else:
|
|
264
|
+
print(f"FAILED: generate_file returned {result}")
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print(f"ERROR: {e}")
|
|
267
|
+
|
|
268
|
+
# 保存数据库(可选)
|
|
269
|
+
# idc.save_database(output_file.replace('.c', '.i64'))
|
|
270
|
+
|
|
271
|
+
# 退出
|
|
272
|
+
idaapi.qexit(0)
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
script_path = Path(__file__).parent / "idapython_export_c.py"
|
|
276
|
+
script_path.write_text(script_content)
|
|
277
|
+
return script_path
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def decompile_binary(
|
|
281
|
+
binary_path: Path,
|
|
282
|
+
output_dir: Path,
|
|
283
|
+
idat_path: Path,
|
|
284
|
+
timeout: int = 300,
|
|
285
|
+
verbose: bool = False
|
|
286
|
+
) -> Tuple[bool, str]:
|
|
287
|
+
"""
|
|
288
|
+
使用 IDA Pro decompile 一个二进制文件
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
binary_path: 二进制文件路径
|
|
292
|
+
output_dir: 输出目录
|
|
293
|
+
idat_path: idat 可执行文件路径
|
|
294
|
+
timeout: 超时时间(秒)
|
|
295
|
+
verbose: 是否显示详细输出
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
(success, message)
|
|
299
|
+
"""
|
|
300
|
+
bin_name = binary_path.stem
|
|
301
|
+
output_file = output_dir / f"{bin_name}.c"
|
|
302
|
+
|
|
303
|
+
# 创建临时脚本
|
|
304
|
+
script_content = f"""import os
|
|
305
|
+
import idaapi
|
|
306
|
+
import idc
|
|
307
|
+
|
|
308
|
+
# 输出目录
|
|
309
|
+
output_dir = r"{output_dir}"
|
|
310
|
+
|
|
311
|
+
# 获取输入文件信息
|
|
312
|
+
input_file = idc.get_input_file_path()
|
|
313
|
+
bin_name = os.path.splitext(os.path.basename(input_file))[0]
|
|
314
|
+
output_file = os.path.join(output_dir, bin_name + ".c")
|
|
315
|
+
|
|
316
|
+
print(f"Processing: {{input_file}}")
|
|
317
|
+
print(f"Output: {{output_file}}")
|
|
318
|
+
|
|
319
|
+
# 确保输出目录存在
|
|
320
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
321
|
+
|
|
322
|
+
# 使用 IDC 的 generate_file 函数导出 C 代码
|
|
323
|
+
# GFL_CDECOMP = 0x0004 (C source file)
|
|
324
|
+
try:
|
|
325
|
+
result = idc.generate_file(idc.GFL_CDECOMP, output_file, 0, idc.BADADDR, 0)
|
|
326
|
+
if result == 0:
|
|
327
|
+
print(f"SUCCESS: Generated {{output_file}}")
|
|
328
|
+
else:
|
|
329
|
+
print(f"FAILED: generate_file returned {{result}}")
|
|
330
|
+
except Exception as e:
|
|
331
|
+
print(f"ERROR: {{e}}")
|
|
332
|
+
|
|
333
|
+
# 退出
|
|
334
|
+
idaapi.qexit(0)
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
# 写入脚本文件
|
|
338
|
+
script_path = Path(tempfile.gettempdir()) / f"idapython_EXPORT_C_{bin_name}.py"
|
|
339
|
+
script_path.write_text(script_content)
|
|
340
|
+
|
|
341
|
+
# 构建 idat 命令
|
|
342
|
+
# -A: 自动模式
|
|
343
|
+
# -S: 执行脚本
|
|
344
|
+
# -L: 日志文件
|
|
345
|
+
idat_cmd = [
|
|
346
|
+
str(idat_path),
|
|
347
|
+
"-A",
|
|
348
|
+
f"-S{idapython_SCRIPT_PATH}",
|
|
349
|
+
f"-L{output_dir / f'{bin_name}.log'}",
|
|
350
|
+
str(binary_path)
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
if verbose:
|
|
354
|
+
print(f" Command: {' '.join(idat_cmd)}")
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
result = subprocess.run(
|
|
358
|
+
idat_cmd,
|
|
359
|
+
timeout=timeout,
|
|
360
|
+
capture_output=True,
|
|
361
|
+
text=True
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
if result.returncode == 0 and output_file.exists():
|
|
365
|
+
return True, f"Successfully generated {output_file}"
|
|
366
|
+
else:
|
|
367
|
+
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
|
368
|
+
return False, f"Failed: {error_msg}"
|
|
369
|
+
|
|
370
|
+
except subprocess.TimeoutExpired:
|
|
371
|
+
return False, f"Timeout after {timeout}s"
|
|
372
|
+
except Exception as e:
|
|
373
|
+
return False, f"Exception: {e}"
|
|
374
|
+
finally:
|
|
375
|
+
# 清理临时脚本
|
|
376
|
+
try:
|
|
377
|
+
script_path.unlink()
|
|
378
|
+
except:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def decompile_binary_simple(
|
|
383
|
+
binary_path: Path,
|
|
384
|
+
output_dir: Path,
|
|
385
|
+
idat_path: Path,
|
|
386
|
+
timeout: int = 300,
|
|
387
|
+
verbose: bool = False
|
|
388
|
+
) -> Tuple[bool, str]:
|
|
389
|
+
"""
|
|
390
|
+
使用 IDA Pro decompile 一个二进制文件 (简化版)
|
|
391
|
+
|
|
392
|
+
使用 -Cm 参数让 IDA 自动加载并处理
|
|
393
|
+
"""
|
|
394
|
+
bin_name = binary_path.stem
|
|
395
|
+
output_file = output_dir / f"{bin_name}.c"
|
|
396
|
+
|
|
397
|
+
# 创建脚本文件
|
|
398
|
+
script_content = f"""import idc
|
|
399
|
+
import idaapi
|
|
400
|
+
import os
|
|
401
|
+
|
|
402
|
+
output_dir = r"{output_dir}"
|
|
403
|
+
bin_name = os.path.splitext(idc.get_input_file_path())[0]
|
|
404
|
+
output_file = os.path.join(output_dir, bin_name + ".c")
|
|
405
|
+
|
|
406
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
407
|
+
|
|
408
|
+
# 导出 C 代码
|
|
409
|
+
result = idc.generate_file(idc.GFL_CDECOMP, output_file, 0, idc.BADADDR, 0)
|
|
410
|
+
if result == 0:
|
|
411
|
+
print(f"SUCCESS: {{output_file}}")
|
|
412
|
+
else:
|
|
413
|
+
print(f"FAILED: {{result}}")
|
|
414
|
+
|
|
415
|
+
idaapi.qexit(0)
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
# 生成唯一的脚本文件名
|
|
419
|
+
import time
|
|
420
|
+
script_name = f"idapython_export_{int(time.time() * 1000)}.py"
|
|
421
|
+
script_path = Path(tempfile.gettempdir()) / script_name
|
|
422
|
+
script_path.write_text(script_content)
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
# 构建命令
|
|
426
|
+
# Linux/macOS: idat
|
|
427
|
+
# Windows: idat64 或 idat32
|
|
428
|
+
cmd = [
|
|
429
|
+
str(idat_path),
|
|
430
|
+
"-A", # Batch mode
|
|
431
|
+
f"-S{script_path}", # Run script
|
|
432
|
+
str(binary_path)
|
|
433
|
+
]
|
|
434
|
+
|
|
435
|
+
if verbose:
|
|
436
|
+
print(f" Running: {' '.join(cmd)}")
|
|
437
|
+
|
|
438
|
+
result = subprocess.run(
|
|
439
|
+
cmd,
|
|
440
|
+
timeout=timeout,
|
|
441
|
+
capture_output=True,
|
|
442
|
+
text=True,
|
|
443
|
+
env={**os.environ, "IDA_LOG_FILE": str(output_dir / f"{bin_name}.log")}
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if output_file.exists():
|
|
447
|
+
return True, f"Generated {output_file}"
|
|
448
|
+
else:
|
|
449
|
+
stderr = result.stderr.strip() if result.stderr else "No error output"
|
|
450
|
+
return False, f"Output file not created: {stderr}"
|
|
451
|
+
|
|
452
|
+
except subprocess.TimeoutExpired:
|
|
453
|
+
return False, f"Timeout after {timeout}s"
|
|
454
|
+
except Exception as e:
|
|
455
|
+
return False, f"Exception: {e}"
|
|
456
|
+
finally:
|
|
457
|
+
try:
|
|
458
|
+
script_path.unlink(missing_ok=True)
|
|
459
|
+
except:
|
|
460
|
+
pass
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def decompile_binary_v3(
|
|
464
|
+
binary_path: Path,
|
|
465
|
+
output_dir: Path,
|
|
466
|
+
idat_path: Path,
|
|
467
|
+
timeout: int = 300,
|
|
468
|
+
verbose: bool = False
|
|
469
|
+
) -> Tuple[bool, str]:
|
|
470
|
+
"""
|
|
471
|
+
使用 IDA Pro decompile 一个二进制文件
|
|
472
|
+
直接将脚本内容作为参数 passing
|
|
473
|
+
"""
|
|
474
|
+
bin_name = binary_path.stem
|
|
475
|
+
output_file = output_dir / f"{bin_name}.c"
|
|
476
|
+
|
|
477
|
+
# 创建 log 目录
|
|
478
|
+
log_dir = output_dir / "logs"
|
|
479
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
480
|
+
|
|
481
|
+
# 脚本内容 (内联)
|
|
482
|
+
script_lines = [
|
|
483
|
+
"import idc",
|
|
484
|
+
"import idaapi",
|
|
485
|
+
"import os",
|
|
486
|
+
"",
|
|
487
|
+
"bin_name = os.path.splitext(idc.get_input_file_path())[0]",
|
|
488
|
+
f"output_dir = r\"{output_dir}\"",
|
|
489
|
+
"output_file = os.path.join(output_dir, bin_name + \".c\")",
|
|
490
|
+
"",
|
|
491
|
+
"os.makedirs(output_dir, exist_ok=True)",
|
|
492
|
+
"",
|
|
493
|
+
"# Export C code",
|
|
494
|
+
"result = idc.generate_file(idc.GFL_CDECOMP, output_file, 0, idc.BADADDR, 0)",
|
|
495
|
+
"if result == 0:",
|
|
496
|
+
" print(f\"SUCCESS: {output_file}\")",
|
|
497
|
+
"else:",
|
|
498
|
+
" print(f\"FAILED: {result}\")",
|
|
499
|
+
"",
|
|
500
|
+
"idaapi.qexit(0)"
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
# 使用 -c 参数自动处理并退出
|
|
504
|
+
# 使用 -A 静默模式
|
|
505
|
+
# 使用 -S 执行脚本
|
|
506
|
+
|
|
507
|
+
# 将脚本写入临时文件
|
|
508
|
+
import tempfile
|
|
509
|
+
script_fd, script_path = tempfile.mkstemp(suffix='.py', prefix='ida_export_')
|
|
510
|
+
os.close(script_fd)
|
|
511
|
+
Path(script_path).write_text('\\n'.join(script_lines))
|
|
512
|
+
|
|
513
|
+
try:
|
|
514
|
+
cmd = [
|
|
515
|
+
str(idat_path),
|
|
516
|
+
"-A",
|
|
517
|
+
"-B", # Batch mode, no UI
|
|
518
|
+
f"-S{script_path}",
|
|
519
|
+
str(binary_path)
|
|
520
|
+
]
|
|
521
|
+
|
|
522
|
+
if verbose:
|
|
523
|
+
print(f" Processing: {bin_name}")
|
|
524
|
+
|
|
525
|
+
result = subprocess.run(
|
|
526
|
+
cmd,
|
|
527
|
+
timeout=timeout,
|
|
528
|
+
capture_output=True,
|
|
529
|
+
text=True
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
if output_file.exists():
|
|
533
|
+
return True, f"Generated {output_file}"
|
|
534
|
+
else:
|
|
535
|
+
stderr = result.stderr.strip() if result.stderr else result.stdout.strip()
|
|
536
|
+
return False, f"Output file not created. {stderr}"
|
|
537
|
+
|
|
538
|
+
except subprocess.TimeoutExpired:
|
|
539
|
+
return False, f"Timeout after {timeout}s"
|
|
540
|
+
except Exception as e:
|
|
541
|
+
return False, f"Exception: {e}"
|
|
542
|
+
finally:
|
|
543
|
+
try:
|
|
544
|
+
os.unlink(script_path)
|
|
545
|
+
except:
|
|
546
|
+
pass
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# 完整版 - step by step
|
|
550
|
+
def decompile_binary_full(
|
|
551
|
+
binary_path: Path,
|
|
552
|
+
output_dir: Path,
|
|
553
|
+
idat_path: Path,
|
|
554
|
+
timeout: int = 300,
|
|
555
|
+
verbose: bool = False
|
|
556
|
+
) -> Tuple[bool, str]:
|
|
557
|
+
"""
|
|
558
|
+
完整的反编译流程:
|
|
559
|
+
1. 使用 idat -A 批量处理
|
|
560
|
+
2. IDAPython 脚本负责导出 C 代码
|
|
561
|
+
"""
|
|
562
|
+
bin_name = binary_path.stem
|
|
563
|
+
output_file = output_dir / f"{bin_name}.c"
|
|
564
|
+
|
|
565
|
+
# 使用外部脚本文件
|
|
566
|
+
script_path = Path(__file__).parent / "idapython_export_c.py"
|
|
567
|
+
if not script_path.exists():
|
|
568
|
+
return False, f"Script not found: {script_path}"
|
|
569
|
+
|
|
570
|
+
# 设置环境变量传递输出目录
|
|
571
|
+
env = os.environ.copy()
|
|
572
|
+
env['IDA_OUTPUT_DIR'] = str(output_dir)
|
|
573
|
+
|
|
574
|
+
try:
|
|
575
|
+
# 使用 idat 批量模式
|
|
576
|
+
cmd = [
|
|
577
|
+
str(idat_path),
|
|
578
|
+
"-A", # Automatic mode
|
|
579
|
+
f"-S{script_path}",
|
|
580
|
+
str(binary_path)
|
|
581
|
+
]
|
|
582
|
+
|
|
583
|
+
if verbose:
|
|
584
|
+
print(f" Processing: {bin_name}")
|
|
585
|
+
|
|
586
|
+
result = subprocess.run(
|
|
587
|
+
cmd,
|
|
588
|
+
timeout=timeout,
|
|
589
|
+
capture_output=True,
|
|
590
|
+
text=True,
|
|
591
|
+
env=env
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
if output_file.exists():
|
|
595
|
+
return True, f"Generated {output_file}"
|
|
596
|
+
else:
|
|
597
|
+
stderr = result.stderr.strip() if result.stderr else result.stdout.strip()
|
|
598
|
+
return False, f"Output not created: {stderr}"
|
|
599
|
+
|
|
600
|
+
except subprocess.TimeoutExpired:
|
|
601
|
+
return False, f"Timeout after {timeout}s"
|
|
602
|
+
except Exception as e:
|
|
603
|
+
return False, f"Exception: {e}"
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def process_single_binary(args):
|
|
607
|
+
"""处理单个二进制文件的包装函数(用于 map)"""
|
|
608
|
+
binary_path, output_dir, idat_path, timeout, verbose = args
|
|
609
|
+
return decompile_binary_full(
|
|
610
|
+
binary_path, output_dir, idat_path, timeout, verbose
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def main():
|
|
615
|
+
parser = argparse.ArgumentParser(
|
|
616
|
+
description="IDA Pro 批量反编译工具 - 使用 idat 导出二进制文件的 C 代码",
|
|
617
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
618
|
+
epilog="""
|
|
619
|
+
Examples:
|
|
620
|
+
%(prog)s -i ./binaries -o ./output
|
|
621
|
+
%(prog)s -i /path/to/binary -o ./decompiled -p /Applications/IDA\\ Professional\\ 9.1.app/Contents/MacOS/idat
|
|
622
|
+
%(prog)s -i ./binaries -o ./output -j 8 -t 600
|
|
623
|
+
"""
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
parser.add_argument('-i', '--input-dir', required=True,
|
|
627
|
+
help='要扫描的输入目录或二进制文件')
|
|
628
|
+
parser.add_argument('-o', '--output-dir', required=True,
|
|
629
|
+
help='C 文件输出目录')
|
|
630
|
+
parser.add_argument('-p', '--idat-path',
|
|
631
|
+
default='/Applications/IDA Professional 9.1.app/Contents/MacOS/idat',
|
|
632
|
+
help='idat 可执行文件路径')
|
|
633
|
+
parser.add_argument('-j', '--jobs', type=int, default=4,
|
|
634
|
+
help='并行处理的进程数 (默认: 4)')
|
|
635
|
+
parser.add_argument('-t', '--timeout', type=int, default=300,
|
|
636
|
+
help='每个文件的处理超时时间(秒) (默认: 300)')
|
|
637
|
+
parser.add_argument('-v', '--verbose', action='store_true',
|
|
638
|
+
help='显示详细输出')
|
|
639
|
+
|
|
640
|
+
args = parser.parse_args()
|
|
641
|
+
|
|
642
|
+
# 验证 idat 路径
|
|
643
|
+
idat_path = Path(args.idat_path)
|
|
644
|
+
if not idat_path.exists():
|
|
645
|
+
print(f"ERROR: idat not found at {idat_path}")
|
|
646
|
+
sys.exit(1)
|
|
647
|
+
|
|
648
|
+
# 验证输入
|
|
649
|
+
input_path = Path(args.input_dir)
|
|
650
|
+
if not input_path.exists():
|
|
651
|
+
print(f"ERROR: Input path not found: {input_path}")
|
|
652
|
+
sys.exit(1)
|
|
653
|
+
|
|
654
|
+
# 创建输出目录
|
|
655
|
+
output_dir = Path(args.output_dir)
|
|
656
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
657
|
+
|
|
658
|
+
# 查找二进制文件
|
|
659
|
+
print(f"\n{'='*60}")
|
|
660
|
+
print("IDA Pro 批量反编译工具")
|
|
661
|
+
print(f"{'='*60}")
|
|
662
|
+
print(f"\n输入目录: {input_path}")
|
|
663
|
+
print(f"输出目录: {output_dir}")
|
|
664
|
+
print(f"IDAT 路径: {idat_path}")
|
|
665
|
+
print(f"并行进程: {args.jobs}")
|
|
666
|
+
print(f"超时时间: {args.timeout}s")
|
|
667
|
+
print()
|
|
668
|
+
|
|
669
|
+
# 收集文件
|
|
670
|
+
if input_path.is_file():
|
|
671
|
+
binaries = [input_path]
|
|
672
|
+
print(f"处理单个文件: {input_path}")
|
|
673
|
+
else:
|
|
674
|
+
print(f"扫描目录: {input_path}")
|
|
675
|
+
binaries = find_binaries(input_path)
|
|
676
|
+
|
|
677
|
+
if not binaries:
|
|
678
|
+
print("\n未找到任何二进制文件")
|
|
679
|
+
sys.exit(0)
|
|
680
|
+
|
|
681
|
+
print(f"\n找到 {len(binaries)} 个二进制文件")
|
|
682
|
+
|
|
683
|
+
# 处理文件
|
|
684
|
+
print(f"\n{'='*60}")
|
|
685
|
+
print("开始反编译...")
|
|
686
|
+
print(f"{'='*60}\n")
|
|
687
|
+
|
|
688
|
+
success_count = 0
|
|
689
|
+
fail_count = 0
|
|
690
|
+
|
|
691
|
+
if args.jobs > 1 and len(binaries) > 1:
|
|
692
|
+
# 多进程并行处理
|
|
693
|
+
import multiprocessing
|
|
694
|
+
with multiprocessing.Pool(args.jobs) as pool:
|
|
695
|
+
results = pool.map(process_single_binary, [
|
|
696
|
+
(b, output_dir, idat_path, args.timeout, args.verbose)
|
|
697
|
+
for b in binaries
|
|
698
|
+
])
|
|
699
|
+
|
|
700
|
+
for binary, (success, msg) in zip(binaries, results):
|
|
701
|
+
status = "OK" if success else "FAIL"
|
|
702
|
+
print(f"[{status}] {binary.name}: {msg}")
|
|
703
|
+
if success:
|
|
704
|
+
success_count += 1
|
|
705
|
+
else:
|
|
706
|
+
fail_count += 1
|
|
707
|
+
else:
|
|
708
|
+
# 单进程处理
|
|
709
|
+
for binary in binaries:
|
|
710
|
+
success, msg = decompile_binary_full(
|
|
711
|
+
binary, output_dir, idat_path, args.timeout, args.verbose
|
|
712
|
+
)
|
|
713
|
+
status = "OK" if success else "FAIL"
|
|
714
|
+
print(f"[{status}] {binary.name}: {msg}")
|
|
715
|
+
if success:
|
|
716
|
+
success_count += 1
|
|
717
|
+
else:
|
|
718
|
+
fail_count += 1
|
|
719
|
+
|
|
720
|
+
# 汇总
|
|
721
|
+
print(f"\n{'='*60}")
|
|
722
|
+
print("处理完成!")
|
|
723
|
+
print(f"{'='*60}")
|
|
724
|
+
print(f"成功: {success_count}")
|
|
725
|
+
print(f"失败: {fail_count}")
|
|
726
|
+
print(f"总计: {len(binaries)}")
|
|
727
|
+
print(f"\n输出目录: {output_dir}")
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
if __name__ == "__main__":
|
|
731
|
+
main()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ida-batch-decompile
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: IDA Pro batch decompiler tool - use idat to batch decompile binary files to C code
|
|
5
|
+
Author: IDA2C Auto
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourorg/ida-batch-decompile
|
|
8
|
+
Project-URL: Documentation, https://github.com/yourorg/ida-batch-decompile#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/yourorg/ida-batch-decompile.git
|
|
10
|
+
Project-URL: Issues, https://github.com/yourorg/ida-batch-decompile/issues
|
|
11
|
+
Keywords: ida,ida-pro,decompiler,reverse-engineering,idaapi,hex-rays
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Requires-Python: >=3.7
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# IDA Pro 批量反编译工具
|
|
27
|
+
|
|
28
|
+
使用 IDA Pro 的命令行工具 `idat` 配合 IDAPython 批量反编译二进制文件,生成干净的 C 代码。
|
|
29
|
+
|
|
30
|
+
## 功能特性
|
|
31
|
+
|
|
32
|
+
- 扫描目录并自动识别可执行文件和共享库
|
|
33
|
+
- 使用 Hex-Rays `decompile_many` API 生成高质量的 C 代码
|
|
34
|
+
- 支持并行处理
|
|
35
|
+
- 自动等待分析完成
|
|
36
|
+
- 支持 Mach-O、ELF、PE 等格式
|
|
37
|
+
|
|
38
|
+
## 依赖
|
|
39
|
+
|
|
40
|
+
- IDA Pro 6.9+(推荐 9.0+)
|
|
41
|
+
- Python 3.x
|
|
42
|
+
|
|
43
|
+
## 安装
|
|
44
|
+
|
|
45
|
+
将工具放入你的 IDA Pro 目录,确保 `idat` 可执行文件可访问。
|
|
46
|
+
|
|
47
|
+
## 使用方法
|
|
48
|
+
|
|
49
|
+
### 基本用法
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 扫描目录并批量反编译
|
|
53
|
+
python3 idb_batch_decompile.py -i <输入目录> -o <输出目录>
|
|
54
|
+
|
|
55
|
+
# 指定 idat 路径
|
|
56
|
+
python3 idb_batch_decompile.py \
|
|
57
|
+
-i /path/to/binaries \
|
|
58
|
+
-o ./output \
|
|
59
|
+
-p "/Applications/IDA Professional 9.1.app/Contents/MacOS/idat"
|
|
60
|
+
|
|
61
|
+
# 详细输出
|
|
62
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -v
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 完整参数
|
|
66
|
+
|
|
67
|
+
| 参数 | 说明 |
|
|
68
|
+
|------|------|
|
|
69
|
+
| `-i, --input-dir` | 要扫描的输入目录或单个二进制文件 |
|
|
70
|
+
| `-o, --output-dir` | C 文件输出目录 |
|
|
71
|
+
| `-p, --idat-path` | idat 可执行文件路径 |
|
|
72
|
+
| `-j, --jobs` | 并行处理的进程数 (默认: 4) |
|
|
73
|
+
| `-t, --timeout` | 每个文件的超时时间,单位秒 (默认: 300) |
|
|
74
|
+
| `-v, --verbose` | 显示详细输出 |
|
|
75
|
+
| `-h, --help` | 显示帮助信息 |
|
|
76
|
+
|
|
77
|
+
### 示例
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 处理单个文件
|
|
81
|
+
python3 idb_batch_decompile.py -i ./hello -o ./output
|
|
82
|
+
|
|
83
|
+
# 处理整个目录
|
|
84
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./decompiled
|
|
85
|
+
|
|
86
|
+
# 使用 8 个并行进程
|
|
87
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -j 8
|
|
88
|
+
|
|
89
|
+
# 增加超时时间(处理大型文件)
|
|
90
|
+
python3 idb_batch_decompile.py -i ./binaries -o ./output -t 600
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 输出格式
|
|
94
|
+
|
|
95
|
+
生成的 C 文件格式如下:
|
|
96
|
+
|
|
97
|
+
```c
|
|
98
|
+
/* This file was generated by the Hex-Rays decompiler version 9.1.0.250226.
|
|
99
|
+
Copyright (c) 2007-2021 Hex-Rays <info@hex-rays.com>
|
|
100
|
+
|
|
101
|
+
Detected compiler: GNU C++
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
#include <defs.h>
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
//-------------------------------------------------------------------------
|
|
108
|
+
// Function declarations
|
|
109
|
+
|
|
110
|
+
int __fastcall main(int argc, const char **argv, const char **envp);
|
|
111
|
+
int printf(const char *, ...);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
//----- (0000000100000460) ----------------------------------------------------
|
|
115
|
+
int __fastcall main(int argc, const char **argv, const char **envp)
|
|
116
|
+
{
|
|
117
|
+
printf("Hello, World!\n");
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
输出文件以原二进制文件名命名,例如 `hello` → `hello.c`。
|
|
123
|
+
|
|
124
|
+
## 目录结构
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
ida2c_auto/
|
|
128
|
+
├── idb_batch_decompile.py # 主工具脚本
|
|
129
|
+
├── idapython_export_c.py # IDAPython 导出脚本
|
|
130
|
+
├── README.md # 本文件
|
|
131
|
+
├── test_code/ # 测试文件目录
|
|
132
|
+
│ ├── hello.c
|
|
133
|
+
│ ├── math_lib.c
|
|
134
|
+
│ ├── string_utils.c
|
|
135
|
+
│ └── ...
|
|
136
|
+
└── test_output/ # 测试输出目录
|
|
137
|
+
├── hello.c
|
|
138
|
+
├── math_lib.c
|
|
139
|
+
├── string_utils.c
|
|
140
|
+
└── ...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 实现原理
|
|
144
|
+
|
|
145
|
+
工具使用 `ida_hexrays.decompile_many()` API:
|
|
146
|
+
|
|
147
|
+
1. 使用 `idat -A` 以自动模式加载二进制文件
|
|
148
|
+
2. 等待 IDA 自动分析完成
|
|
149
|
+
3. 调用 `decompile_many()` 导出所有函数的 C 代码
|
|
150
|
+
4. 保存为 `.c` 文件
|
|
151
|
+
|
|
152
|
+
这与 IDA GUI 的"导出 C 代码"功能使用相同的底层 API,因此输出质量相同。
|
|
153
|
+
|
|
154
|
+
## 注意事项
|
|
155
|
+
|
|
156
|
+
- 确保 IDA Pro 已正确安装且 `idat` 可执行文件存在
|
|
157
|
+
- 某些保护/混淆的二进制文件可能无法完整反编译
|
|
158
|
+
- 处理大型文件时可能需要增加超时时间
|
|
159
|
+
- 并行处理时请注意系统资源限制
|
|
160
|
+
|
|
161
|
+
## 许可证
|
|
162
|
+
|
|
163
|
+
本工具仅供学习和研究使用。请遵守 IDA Pro 的使用协议。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
ida_batch_decompile/__init__.py
|
|
5
|
+
ida_batch_decompile/cli.py
|
|
6
|
+
ida_batch_decompile/idb_batch_decompile.py
|
|
7
|
+
ida_batch_decompile.egg-info/PKG-INFO
|
|
8
|
+
ida_batch_decompile.egg-info/SOURCES.txt
|
|
9
|
+
ida_batch_decompile.egg-info/dependency_links.txt
|
|
10
|
+
ida_batch_decompile.egg-info/entry_points.txt
|
|
11
|
+
ida_batch_decompile.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ida_batch_decompile
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ida-batch-decompile"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "IDA Pro batch decompiler tool - use idat to batch decompile binary files to C code"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "IDA2C Auto"}
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Information Technology",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Topic :: Software Development :: Disassemblers",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
]
|
|
27
|
+
keywords = ["ida", "ida-pro", "decompiler", "reverse-engineering", "idaapi", "hex-rays"]
|
|
28
|
+
requires-python = ">=3.7"
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/yourorg/ida-batch-decompile"
|
|
33
|
+
Documentation = "https://github.com/yourorg/ida-batch-decompile#readme"
|
|
34
|
+
Repository = "https://github.com/yourorg/ida-batch-decompile.git"
|
|
35
|
+
Issues = "https://github.com/yourorg/ida-batch-decompile/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
ida-decompile = "ida_batch_decompile.cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["."]
|
|
42
|
+
include = ["ida_batch_decompile*"]
|