Skip to content

Commit 214a02f

Browse files
author
Ian Bouchard
committed
Beginning of new implementation for the opcache_malware_hunt tool
1 parent 013eabf commit 214a02f

File tree

3 files changed

+114
-69
lines changed

3 files changed

+114
-69
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
*.pyc
2+
*.tmp
3+
hunt_opcache

analysis_tools/compile.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
$handle = fopen($argv[1], "r");
3+
4+
if ($handle) {
5+
while (($line = fgets($handle)) !== false) {
6+
opcache_compile_file($line);
7+
}
8+
fclose($handle);
9+
}
10+
?>

analysis_tools/opcache_malware_hunt.py

+102-69
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,17 @@
55
from opcache_disassembler import OPcacheDisassembler
66
import sys
77
import os
8-
import re
9-
from termcolor import colored
10-
11-
dangerous_keywords = [
12-
"(eval)",
13-
"(exec)",
14-
"(preg_replace).*(?=SEND_VAL\(.*/e.*\);)",
15-
"(system)",
16-
"(assert)",
17-
"(str_rot13)",
18-
"(base64_encode)"
19-
]
8+
import subprocess
9+
import shutil
2010

21-
def show_help():
22-
""" Show the help menu"""
23-
24-
print "Usage : {0} [file|directory]".format(sys.argv[0])
25-
26-
def check_dangerous_keywords(ast, disassembler):
27-
""" Check for dangerous keywords
28-
29-
Arguments :
30-
ast : The AST to check against
31-
disassembler : The disassembler to use
32-
"""
33-
34-
main_op_array = disassembler.convert_branch_to_pseudo_code(ast, "main_op_array")
35-
function_table = disassembler.convert_branch_to_pseudo_code(ast, "function_table")
36-
class_table = disassembler.convert_branch_to_pseudo_code(ast, "class_table")
37-
38-
code = main_op_array + function_table + class_table
39-
40-
for keyword in dangerous_keywords:
41-
result = re.search(keyword, code, re.DOTALL)
42-
if result:
43-
print colored("\tFound potentially dangerous keyword '{0}'.".format(result.group(1)), 'yellow')
44-
45-
if __name__ == "__main__":
46-
47-
if len(sys.argv) < 2:
48-
show_help()
49-
exit(0)
11+
hunt_source_files = "hunt_source_files.tmp"
12+
hunt_ini = "hunt.ini.tmp"
13+
hunt_opcache = "hunt_opcache"
5014

51-
path = sys.argv[1]
15+
def list_opcache_files(path):
16+
""" List every opcache (.php.bin) file found in a given path """
5217

53-
# Create a disassembler
54-
disassembler = OPcacheDisassembler()
18+
opcache_files = []
5519

5620
# Check if arg[1] is a folder or a file
5721
if os.path.isdir(path):
@@ -63,38 +27,107 @@ def check_dangerous_keywords(ast, disassembler):
6327
# Only check .php.bin files
6428
if file.endswith(".php.bin"):
6529
file = os.path.join(subdir, file)
30+
opcache_files += [file]
6631

67-
print "Checking " + file
32+
else:
33+
# Only check .php.bin files
34+
if path.endswith(".php.bin"):
35+
opcache_files += [path]
6836

69-
try:
70-
# Create an AST from the compiled file
71-
ast = disassembler.create_ast(file)
72-
except KeyboardInterrupt:
73-
exit(0)
37+
return opcache_files
7438

75-
except:
76-
print "Unable to parse file " + file
77-
continue
39+
def dump_source_file_list(list):
7840

79-
# Check the ast for dangerous keywords
80-
check_dangerous_keywords(ast, disassembler)
41+
with open(hunt_source_files, 'w') as f:
42+
for file in list:
43+
f.write(file + "\n")
8144

82-
else:
83-
# Only check .php.bin files
84-
if path.endswith(".php.bin"):
45+
def setup_env(phpini_path):
46+
47+
# hunt.ini
48+
with open(phpini_path, "r") as f:
49+
with open(hunt_ini, "w") as h:
50+
for line in f.readlines():
51+
52+
# opcache.file_cache
53+
if "opcache.file_cache=" in line:
54+
line = "opcache.file_cache=" + os.path.join(os.getcwd(), hunt_opcache)
55+
56+
# opcache.enable_cli
57+
if "opcache.enable_cli=" in line:
58+
line = "opcache.enable_cli=1"
59+
60+
# opcache.enable_cli
61+
if "opcache.enable=" in line:
62+
line = "opcache.enable=1"
63+
64+
h.write(line)
65+
66+
# cache folder location
67+
os.mkdir(hunt_opcache)
68+
os.chmod(hunt_opcache, 0o777)
69+
70+
def cleanup():
71+
72+
# Remove cache folder
73+
try:
74+
shutil.rmtree(hunt_opcache)
75+
except:
76+
pass
77+
78+
# Remove hunt.ini
79+
try:
80+
os.remove(hunt_ini)
81+
except:
82+
pass
83+
84+
# Remove source files list
85+
try:
86+
os.remove(hunt_source_files)
87+
except:
88+
pass
89+
90+
def compile_source_files():
91+
command = "php -c {0} compile.php {1}".format(hunt_ini, hunt_source_files)
92+
subprocess.call(command.split(), shell=False)
93+
94+
def show_help():
95+
""" Show the help menu"""
96+
97+
print "Usage : {0} [opcache_folder] [system_id] [php.ini] ".format(sys.argv[0])
98+
99+
if __name__ == "__main__":
100+
101+
if len(sys.argv) < 3:
102+
show_help()
103+
exit(0)
104+
105+
# Remove temporary files and folders
106+
cleanup()
107+
108+
# Paths to analyse
109+
opcache_folder = sys.argv[1]
110+
system_id = sys.argv[2]
111+
phpini_path = sys.argv[3]
112+
113+
# Setup a new phpini for compilation
114+
setup_env(phpini_path)
115+
116+
# OPcache file list
117+
opcache_files = list_opcache_files(opcache_folder)
85118

86-
print "Checking " + path
87-
try:
88-
# Create an AST from the compiled file
89-
ast = disassembler.create_ast(path)
90-
except:
91-
print "Unable to parse file " + path
92-
exit(0)
119+
# Get location of source folder
120+
prefix = os.path.commonprefix(opcache_files)
121+
source_folder = prefix.split(system_id, 1)[1]
93122

94-
# Check AST for dangerous files
95-
check_dangerous_keywords(ast, disassembler)
96-
else:
97-
print "File is not a .php.bin file"
123+
# Source files list
124+
source_files = [source_folder + file.split(source_folder, 1)[1][:-4] for file in opcache_files ]
98125

126+
# Dump source files
127+
dump_source_file_list(source_files)
99128

129+
# Compile source files
130+
compile_source_files()
100131

132+
# Remove temporary files and folders
133+
cleanup()

0 commit comments

Comments
 (0)