# Finding Antivirus Detection Virus Signatures (nuitka package)
最近在遇到 Nuitka 打包 會被eset 防毒軟體檢測的方式大概試了下面幾個方法
第一加殼,透過把 binary 壓縮 再套在另外一層的 c shell 來逃避 防毒軟體在 靜態分析掃描到相關特徵碼
第一種方法的話大部分防毒都會檢測到UPX 很早期的加殼,或著叫 chargpt 也可以產生一下簡單 aes 加殼 脫殼的方法 反正就是 將一些section 加密起來,因為是加密的,當 shell 啟動後會 decode 相關的 section 加載正確的 binary data 這些東西當然就不存在於 病毒的資料庫裡面所以當然找不到。
第二移除特徵碼,之前對這個蠻感興趣,剛好有實際案例可以試試看,看了一些查找病毒特徵碼的tool,發現大概可以這樣來實現,大概就是保留原本的 exe pe 結構,然後分別對各個 section 去做分割的動作,每次分割出來的 分塊的section 都有可能涵蓋到一小片段特徵碼被防毒軟體偵測到,也有複合的特徵碼片段的情況,所以那些tool 做的事情就是 拆個片段的特徵碼,然後再掃描看防毒砍掉哪些片段,逐一縮小範圍
目前猜想是這樣,
所以第一個步驟是尋找有可能產生這些片段section 的排列組合至少存在一組以上
那我要反向查找特徵碼最小集合是,例如我要在local 不斷的呼叫 ESET 裡面的ecls.exe(掃描分割出來的檔案來檢測那些塊組合會導致被檢測到病毒)
比如說掃描一個 nuitka 的 編譯pyhton pe結構 大概會得出下列幾個section,
首先你要用 nuitka 編譯一個 python 並印出hello ,這時候不管內容是什麼你會被 Eset 監測為
```
Log
a variant of Python/Packed.Nuitka.Y suspicious application - cleaned by deleting [1]
```
下面是窮舉nuitka編譯出來的 binary 所有section 並找到 最小集合的 section 會被eset 檢測到是 Python/Packed.Nuitka 組合得程式碼片段
```python=
import os
import pefile
import subprocess
from itertools import combinations
from concurrent.futures import ThreadPoolExecutor, as_completed
def scan_with_eset(file_path, eset_path="C:\\Program Files\\ESET\\ESET Security\\ecls.exe"):
"""
使用 ESET 的命令行工具扫描文件
"""
try:
command = [eset_path, file_path]
result = subprocess.run(command, capture_output=True, text=True)
if result.stdout.find("a variant of")>=0:
return True
return False
except FileNotFoundError:
print(f"ESET Command Line Scanner not found at {eset_path}.")
return False
def generate_section_combination(pe, binary_data, section_names, fill_byte=0x00):
"""
根据目标 sections,生成包含它们组合的新二进制文件
"""
new_binary = bytearray(binary_data)
for section in pe.sections:
name = section.Name.decode().strip('\x00')
start = section.PointerToRawData
size = section.SizeOfRawData
# 填充非目标 Sections
if name not in section_names:
new_binary[start:start + size] = bytes([fill_byte] * size)
return new_binary
def scan_section_combinations(pe, binary_data, output_dir, eset_path, max_combination_size=3, max_workers=4):
"""
扫描单独 sections 和它们的组合
"""
section_names = [section.Name.decode().strip('\x00') for section in pe.sections]
print(f"Available sections: {section_names}")
trigger_combinations = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {}
# 扫描所有单独的 sections 和它们的组合
for size in range(1, max_combination_size + 1):
for combination in combinations(section_names, size):
temp_file = os.path.join(output_dir, f"combination_{'_'.join(combination)}.bin")
modified_binary = generate_section_combination(pe, binary_data, combination)
# 保存临时文件
with open(temp_file, "wb") as f:
f.write(modified_binary)
# 提交扫描任务并存储 Future 和组合映射
futures[executor.submit(scan_with_eset, temp_file, eset_path)] = combination
print(f"Scanning combination: {combination}")
# 收集结果
for future in as_completed(futures):
combination = futures[future]
try:
if future.result():
print(f"Threat detected in combination: {combination}")
trigger_combinations.append(combination)
except Exception as e:
print(f"Error scanning combination {combination}: {e}")
return trigger_combinations
def main(input_file, output_dir, eset_path, max_workers=4):
"""
主函数
"""
# 读取二进制文件
with open(input_file, "rb") as f:
binary_data = f.read()
# 初始化 PE 文件解析器
pe = pefile.PE(data=binary_data)
# 初始化输出目录
os.makedirs(output_dir, exist_ok=True)
# 扫描单独 sections 和它们的组合
print("Starting section and combination scanning...")
trigger_combinations = scan_section_combinations(pe, binary_data, output_dir, eset_path, max_combination_size=4, max_workers=max_workers)
# 输出结果
if trigger_combinations:
print("\n=== Threat Combinations Found ===")
for combination in trigger_combinations:
print(f"Combination: {combination}")
else:
print("No threats detected in any combination.")
if __name__ == "__main__":
# 输入文件和输出目录
input_file = "test.exe" # 替换为你的实际文件路径
output_dir = "./output"
eset_path = "C:\\Program Files\\ESET\\ESET Security\\ecls.exe"
max_workers = 4 # 控制线程池最大线程数
# 启动分析
main(input_file, output_dir, eset_path, max_workers)
```
# output

```
Starting section and combination scanning...
Available sections: ['.text', '.data', '.rdata', '.eh_fram', '.pdata', '.xdata', '.bss', '.idata', '.CRT', '.tls', '.rsrc', '.reloc']
Scanning combination: ('.text',)
Scanning combination: ('.data',)
Scanning combination: ('.rdata',)
Scanning combination: ('.eh_fram',)
Scanning combination: ('.pdata',)
Scanning combination: ('.xdata',)
Scanning combination: ('.bss',)
Scanning combination: ('.idata',)
Scanning combination: ('.CRT',)
Scanning combination: ('.tls',)
Scanning combination: ('.rsrc',)
Scanning combination: ('.reloc',)
Scanning combination: ('.text', '.data')
Scanning combination: ('.text', '.rdata')
Scanning combination: ('.text', '.eh_fram')
Scanning combination: ('.text', '.pdata')
Scanning combination: ('.text', '.xdata')
Scanning combination: ('.text', '.bss')
Scanning combination: ('.text', '.idata')
Scanning combination: ('.text', '.CRT')
Scanning combination: ('.text', '.tls')
Scanning combination: ('.text', '.rsrc')
Scanning combination: ('.text', '.reloc')
Scanning combination: ('.data', '.rdata')
Scanning combination: ('.data', '.eh_fram')
Scanning combination: ('.data', '.pdata')
Scanning combination: ('.data', '.xdata')
Scanning combination: ('.data', '.bss')
```
這邊可以看到最終的得出這些section,可以看到這些組合裡面都有 .rdata
也就是我只要破壞其中一個 section 就可讓防毒軟體 識別不出來
```
=== Threat Combinations Found ===
Combination: ('.text', '.rdata', '.rsrc')
Combination: ('.text', '.data', '.rdata', '.rsrc')
Combination: ('.text', '.rdata', '.eh_fram', '.rsrc')
Combination: ('.text', '.rdata', '.pdata', '.rsrc')
Combination: ('.text', '.rdata', '.xdata', '.rsrc')
Combination: ('.text', '.rdata', '.bss', '.rsrc')
Combination: ('.text', '.rdata', '.idata', '.CRT')
Combination: ('.text', '.rdata', '.idata', '.rsrc')
Combination: ('.text', '.rdata', '.CRT', '.rsrc')
Combination: ('.text', '.rdata', '.tls', '.rsrc')
Combination: ('.text', '.rdata', '.rsrc', '.reloc')
```
所以透過這個方法,例如我接下來只要隨機對 .rdata填充 0x0 不斷的破壞數據在恢復,找到最小破壞的 byte 區間在對應到程式碼,假設沒影響就反向恢復數據,直到找到 eset 這個防毒軟體到底是針對哪一些 特徵來檢測到底是不是病毒,
```python=
import os
import subprocess
import pefile
def scan_with_eset(file_path, eset_path="C:\\Program Files\\ESET\\ESET Security\\ecls.exe"):
"""
使用 ESET 的命令行工具扫描文件
"""
try:
command = [eset_path, file_path]
result = subprocess.run(command, capture_output=True, text=True)
if "a variant of Python/Packed.Nuitka.Y suspicious application" in result.stdout:
return True
return False
except FileNotFoundError:
print(f"ESET Command Line Scanner not found at {eset_path}.")
return False
def mutate_rdata_exact(pe, binary_data, section_name, start_offset, end_offset, fill_byte=0x00):
"""
精确填充 .rdata 的指定范围内容为 fill_byte,并返回修改的差异
"""
differences = []
modified_binary = bytearray(binary_data)
for section in pe.sections:
name = section.Name.decode().strip('\x00')
if name == section_name:
start = section.PointerToRawData + start_offset
end = section.PointerToRawData + end_offset
# 修改指定范围并记录原始值
for offset in range(start, end + 1):
original_value = modified_binary[offset]
if original_value != fill_byte: # 仅修改非 fill_byte 的字节
modified_binary[offset] = fill_byte
differences.append((offset, original_value, fill_byte))
return modified_binary, differences
return binary_data, differences
def binary_search_rdata(pe, binary_data, output_dir, eset_path, section_name, start, end, fill_byte=0x00):
"""
使用二分法在 .rdata 中找到最小影响范围
"""
if start > end:
return []
# 当范围缩小到单个字节
if start == end:
print(f"Testing single byte at Offset: {start}")
temp_binary, diff = mutate_rdata_exact(pe, binary_data, section_name, start, end, fill_byte)
temp_file = os.path.join(output_dir, f"single_byte_{start}.bin")
with open(temp_file, "wb") as f:
f.write(temp_binary)
if not scan_with_eset(temp_file, eset_path):
print(f"Threat neutralized by modifying single byte at Offset: {start}")
return diff # 返回当前唯一的差异作为结果
return []
# 中间索引
mid = (start + end) // 2
print(f"Testing range {start}-{mid}")
left_binary, left_diff = mutate_rdata_exact(pe, binary_data, section_name, start, mid, fill_byte)
left_file = os.path.join(output_dir, f"rdata_left_{start}_{mid}.bin")
with open(left_file, "wb") as f:
f.write(left_binary)
if not scan_with_eset(left_file, eset_path):
print(f"Threat neutralized by modifying range {start}-{mid}")
return binary_search_rdata(pe, binary_data, output_dir, eset_path, section_name, start, mid, fill_byte)
print(f"Testing range {mid + 1}-{end}")
right_binary, right_diff = mutate_rdata_exact(pe, binary_data, section_name, mid + 1, end, fill_byte)
right_file = os.path.join(output_dir, f"rdata_right_{mid + 1}_{end}.bin")
with open(right_file, "wb") as f:
f.write(right_binary)
if not scan_with_eset(right_file, eset_path):
print(f"Threat neutralized by modifying range {mid + 1}-{end}")
return binary_search_rdata(pe, binary_data, output_dir, eset_path, section_name, mid + 1, end, fill_byte)
return left_diff + right_diff
def refine_to_byte_level(binary_data, effective_differences, output_dir, eset_path):
"""
在已确定的范围内逐字节确认有效的修改字节
"""
minimal_differences = []
for offset, original, modified in effective_differences:
temp_binary = bytearray(binary_data)
temp_binary[offset] = modified
temp_file = os.path.join(output_dir, f"test_byte_{offset}.bin")
with open(temp_file, "wb") as f:
f.write(temp_binary)
if not scan_with_eset(temp_file, eset_path):
print(f"Byte at offset {offset} is critical for neutralization.")
minimal_differences.append((offset, original, modified))
else:
print(f"Byte at offset {offset} does not affect neutralization. Reverting.")
return minimal_differences
def main(input_file, output_dir, eset_path):
"""
主函数
"""
# 读取二进制文件
with open(input_file, "rb") as f:
binary_data = f.read()
# 初始化 PE 文件解析器
pe = pefile.PE(data=binary_data)
# 定义目标 Sections
target_sections = ['.text', '.rdata', '.idata', '.CRT']
# 初始化输出目录
os.makedirs(output_dir, exist_ok=True)
print("Checking if the base combination triggers a threat...")
if not scan_with_eset(input_file, eset_path):
print("Base combination does not trigger any threat. Exiting.")
return
print("Base combination triggers threat. Starting binary search...")
for section in pe.sections:
name = section.Name.decode().strip('\x00')
if name in target_sections:
print(f"Analyzing section: {name}")
if name == ".rdata":
start = 0
size = section.SizeOfRawData
# 使用二分法找到初步修改范围
effective_differences = binary_search_rdata(pe, binary_data, output_dir, eset_path, name, start, size - 1)
print("\n=== Refining to Byte Level ===")
minimal_differences = refine_to_byte_level(binary_data, effective_differences, output_dir, eset_path)
# 输出最终的最小修改结果
log_file = os.path.join(output_dir, "minimal_modifications.log")
with open(log_file, "w") as log:
log.write("=== Minimal Modifications ===\n")
for offset, original, modified in minimal_differences:
log.write(f"Offset: {offset}, Original: {hex(original)}, Modified: {hex(modified)}\n")
print(f"Minimal modification log saved to {log_file}")
if __name__ == "__main__":
# 输入文件和输出目录
input_file = "test.exe" # 替换为你的实际文件路径
output_dir = "./output"
eset_path = "C:\\Program Files\\ESET\\ESET Security\\ecls.exe"
# 启动分析
main(input_file, output_dir, eset_path)
```

也就是我最小異動這個 115496這個 offset 的 byte就可以導致 eset 判別不出來我這是什麼封裝的
記得條十進位 右上角 才可以匹配偏移量

修改為0x0 ,ctrl+s


很好 你的程式就變木馬了xdd
cache.dir 應該對應到的是 nuitka 動態解壓縮的一些行為,
不過其實最開始我也有想過替換掉 mingw gcc 的 cflags -flto 或者一些方法不過還是沒辦法躲過eset檢測。