Skip to content

Commit 5b454c7

Browse files
committed
Initial commit
0 parents  commit 5b454c7

File tree

8 files changed

+1469
-0
lines changed

8 files changed

+1469
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/target
2+
**/*.rs.bk
3+
*.i64
4+
*.idb
5+
*.meso
6+
cache
7+
coverage.txt
8+
*.zip
9+

Cargo.lock

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "mesos"
3+
version = "0.1.0"
4+
authors = ["bfalk"]
5+
6+
[dependencies]
7+
winapi = { version = "0.3.5", features = ["debugapi", "winbase", "memoryapi", "processthreadsapi", "errhandlingapi", "handleapi", "securitybaseapi", "consoleapi", "winerror", "wow64apiset"] }
8+
9+
[profile.release]
10+
debug = true
11+

coverage_scripts/ida.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import collections, re, math
2+
3+
fft = re.compile("[0-9a-f]{16} \| Freq: +([0-9]+) \| +(.*?)\+0x([0-9a-f]+) \| (.*?)\n")
4+
5+
image_base = idaapi.get_imagebase()
6+
ida_modname = GetInputFile().lower()
7+
8+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
9+
10+
inp = open(os.path.join(SCRIPT_DIR, "..", "coverage.txt"), "r").read()
11+
12+
# Reset all coloring in all functions
13+
for funcea in Functions():
14+
f = idaapi.get_func(funcea)
15+
fc = idaapi.FlowChart(f)
16+
17+
for block in fc:
18+
ea = block.startEA
19+
while ea <= block.endEA:
20+
set_color(ea, CIC_ITEM, DEFCOLOR)
21+
ea = idc.NextHead(ea)
22+
23+
freqs = collections.Counter()
24+
25+
# Parse input coverage file
26+
for thing in fft.finditer(inp):
27+
freq = int(thing.group(1), 10)
28+
module = thing.group(2)
29+
offset = int(thing.group(3), 16)
30+
31+
# Skip non-matching modules
32+
if module.lower() != ida_modname:
33+
continue
34+
35+
freqs[image_base + offset] = freq
36+
37+
# Apply coloring
38+
for addr, freq in freqs.most_common()[::-1]:
39+
function_addr = get_func_attr(addr, FUNCATTR_START)
40+
func_entry_freq = freqs[function_addr]
41+
42+
if func_entry_freq == 0:
43+
func_entry_freq = 1
44+
45+
# Log value between [0.0, 1.0)
46+
dist = math.log(float(freq) / float(func_entry_freq) + 1.0)
47+
dist = min(dist, float(1.0))
48+
49+
color = 0x808080 + (int((1 - dist) * 100.0) << 8) + (int(dist * 100.0) << 0)
50+
print("%10d | 0x%.16x | %s" % (freq, addr, get_func_off_str(addr)))
51+
52+
set_color(addr, CIC_ITEM, color)
53+
set_cmt(addr, "Freq: %d | Func entry: %.2f" % (freq, float(freq) / float(func_entry_freq)), False)
54+

generate_mesos.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import sys, subprocess, os, zipfile, threading, time, struct
2+
3+
# Maximum number of processing threads to use
4+
# idat64.exe fails silently on OOMs and stuff so we just keep this reasonable
5+
MAX_JOBS = 4
6+
7+
# Name of the input zip file created by `offline_meso.ps1`
8+
PREP_ZIP = "meso_deps.zip"
9+
10+
# Name of IDA executable
11+
IDA_NAME = "idat64.exe"
12+
13+
def usage():
14+
print("Usage:")
15+
print(" generate_mesos.py process_ida")
16+
print(" Processes all files in the meso_deps.zip file\n")
17+
18+
print(" generate_mesos.py process_ida_whitelist <str 1> <str 2> <str ...>")
19+
print(" Processes files only containing one of the strings provided\n")
20+
21+
print(" generate_mesos.py process_ida_blacklist <str 1> <str 2> <str ...>")
22+
print(" Processes files all files except for those containing one of the provided strings\n")
23+
24+
print("Examples:\n")
25+
print(" python generate_mesos.py process_ida_whitelist system32")
26+
print(" Only processes files in `system32`\n")
27+
print(" python generate_mesos.py process_ida_blacklist ntdll.dll")
28+
print(" Process all files except for `ntdll.dll`\n")
29+
30+
print("Path requirements for process_ida_*: must have `idat64.exe` in your PATH")
31+
quit()
32+
33+
def process_job(orig_name, cache_fn, cache_fn_bin, contents):
34+
if not os.path.exists(cache_fn):
35+
# Make the hirearchy for the cache file
36+
try:
37+
os.makedirs(os.path.dirname(cache_fn))
38+
except FileExistsError:
39+
pass
40+
41+
# Save file to disk
42+
with open(cache_fn_bin, "wb") as fd:
43+
fd.write(contents)
44+
45+
try:
46+
# Invoke IDA to generate the meso file
47+
subprocess.check_call([
48+
IDA_NAME, "-o%s.idb" % cache_fn, "-A",
49+
"-Smesogen_scripts/ida.py cmdline \"%s\" \"%s\"" % \
50+
(cache_fn, os.path.basename(orig_name)),
51+
"-c", cache_fn_bin], shell=True)
52+
except FileNotFoundError:
53+
print("Could not find idat64.exe, is it not in your path?")
54+
quit(-1)
55+
56+
def process_ida(whitelist, blacklist):
57+
# Open the zip file generated by an offline meso script
58+
tf = zipfile.ZipFile(PREP_ZIP, "r")
59+
60+
for member in tf.infolist():
61+
# If there's no file size (it's a directory) skip it
62+
if member.file_size == 0:
63+
continue
64+
65+
# Check if the blacklist excludes this file
66+
if blacklist is not None:
67+
in_blacklist = filter(lambda x: x in member.filename, blacklist)
68+
if len(list(in_blacklist)) > 0:
69+
continue
70+
71+
# Check if the whitelist includes a file
72+
if whitelist is not None:
73+
in_whitelist = filter(lambda x: x in member.filename, whitelist)
74+
if len(list(in_whitelist)) == 0:
75+
continue
76+
77+
print("Processing %s" % member.filename)
78+
79+
# Read the file from the archive
80+
contents = None
81+
with tf.open(member, "r") as fd:
82+
contents = fd.read()
83+
84+
# Parse out the TimeDateStamp and SizeOfImage from the PE header
85+
assert contents[:2] == b"MZ"
86+
pe_ptr = struct.unpack("<I", contents[0x3c:0x40])[0]
87+
assert contents[pe_ptr:pe_ptr+4] == b"PE\0\0"
88+
time_date_stamp = \
89+
struct.unpack("<I", contents[pe_ptr+8:pe_ptr+0xc])[0]
90+
size_of_image = \
91+
struct.unpack("<I", contents[pe_ptr+0x50:pe_ptr+0x54])[0]
92+
93+
# Compute meso and binary names for the cache folder
94+
cache_fn = "%s_%x_%x.meso" % \
95+
(member.filename, time_date_stamp, size_of_image)
96+
cache_fn_bin = "%s_%x_%x" % \
97+
(member.filename, time_date_stamp, size_of_image)
98+
99+
# Limit number of active threads
100+
while threading.active_count() > MAX_JOBS:
101+
time.sleep(0.1)
102+
103+
# Create thread
104+
threading.Timer(0.0, process_job, \
105+
args=[member.filename, cache_fn, cache_fn_bin, contents]) \
106+
.start()
107+
108+
# Wait for all jobs to finish
109+
while threading.active_count() > 1:
110+
time.sleep(0.1)
111+
112+
if len(sys.argv) == 2 and sys.argv[1] == "process_ida":
113+
process_ida(None, None)
114+
elif len(sys.argv) >= 2 and sys.argv[1] == "process_ida_whitelist":
115+
process_ida(sys.argv[2:], None)
116+
elif len(sys.argv) >= 2 and sys.argv[1] == "process_ida_blacklist":
117+
process_ida(None, sys.argv[2:])
118+
else:
119+
usage()
120+

mesogen_scripts/ida.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from idautils import *
2+
from idaapi import *
3+
from idc import *
4+
5+
# Wait for auto analysis to complete
6+
idaapi.auto_wait()
7+
8+
print("Analysis done, generating meso")
9+
10+
image_base = idaapi.get_imagebase()
11+
12+
output = bytearray()
13+
14+
SEPERATOR = "~~ed6ed28d321bbdc8~~"
15+
16+
input_name = GetInputFile()
17+
18+
if len(ARGV) >= 4 and ARGV[1] == "cmdline":
19+
input_name = ARGV[3]
20+
21+
for funcea in Functions():
22+
f = idaapi.get_func(funcea)
23+
fc = idaapi.FlowChart(f)
24+
25+
for block in fc:
26+
if is_code(getFlags(block.startEA)):
27+
output += "%s%s%s%s%x%s%s\n" % \
28+
("single", SEPERATOR,
29+
input_name, SEPERATOR,
30+
block.startEA - image_base, SEPERATOR,
31+
get_func_off_str(block.startEA))
32+
33+
filename = "%s/%s.meso" % (os.path.dirname(os.path.abspath(__file__)), input_name)
34+
if len(ARGV) >= 4 and ARGV[1] == "cmdline":
35+
filename = ARGV[2]
36+
37+
open(filename, "wb").write(output)
38+
39+
print("Generated meso: %s" % filename)
40+
41+
# Exit only if we were invoked from the command line
42+
if len(ARGV) >= 4 and ARGV[1] == "cmdline":
43+
idc.Exit(0)
44+

offline_meso.ps1

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
Param (
2+
[Parameter(Mandatory=$true)][string]$TargetPid,
3+
[string]$OutputZip = "meso_deps.zip"
4+
)
5+
6+
function is64bit($a) {
7+
try {
8+
Add-Type -MemberDefinition @'
9+
[DllImport("kernel32.dll", SetLastError = true,
10+
CallingConvention = CallingConvention.Winapi)]
11+
[return: MarshalAs(UnmanagedType.Bool)]
12+
public static extern bool IsWow64Process(
13+
[In] System.IntPtr hProcess,
14+
[Out, MarshalAs(UnmanagedType.Bool)] out bool wow64Process);
15+
'@ -Name NativeMethods -Namespace Kernel32
16+
}
17+
catch {}
18+
$is32Bit = [int]0
19+
if (!$a.Handle) {
20+
echo "Unable to open handle for process: Does the proceses exist? Do you have adequate permissions?"
21+
exit
22+
}
23+
if ([Kernel32.NativeMethods]::IsWow64Process($a.Handle, [ref]$is32Bit)) {
24+
$(if ($is32Bit) {$false} else {$true})
25+
} else {
26+
"IsWow64Process call failed"
27+
exit
28+
}
29+
}
30+
31+
# Create a new temp directory based on a random GUID
32+
function New-TemporaryDirectory {
33+
$parent = [System.IO.Path]::GetTempPath()
34+
[string] $name = [System.Guid]::NewGuid()
35+
$name = "mesotmp_" + $name
36+
New-Item -ItemType Directory -Path (Join-Path $parent $name)
37+
}
38+
39+
$pshell_bitness = (is64bit(Get-Process -Id $PID))
40+
echo "Powershell is 64-bit: $pshell_bitness"
41+
42+
$target_bitness = (is64bit(Get-Process -Id $TargetPid))
43+
echo "Target is 64-bit: $target_bitness"
44+
45+
# Validate bitnesses match
46+
if ($pshell_bitness -ne $target_bitness) {
47+
echo "Your Powershell bitness does not match the target bitness"
48+
echo "Use 32-bit Powershell for 32-bit processes or 64-bit Powershell for 64-bit processes"
49+
echo "This is to get around pathing issues between things like C:\windows\system32 and C:\windows\syswow64"
50+
exit
51+
}
52+
53+
# Get all the module/executable paths from a running process
54+
$paths = (Get-Process -Id $TargetPid -Module -FileVersionInfo).FileName
55+
56+
# Create a new temporary directory
57+
$dirname = New-TemporaryDirectory
58+
59+
ForEach ($path in $paths) {
60+
# Convert the path to not have a ":" in it by replacing it with a "_" and
61+
# then convert it to a lowercase string
62+
$lowerpath = ([string](Get-ChildItem -Path $path)).replace(":", "_").ToLower()
63+
64+
# Prepend a root-level folder "cache" to all paths that will go in the zip
65+
$lowerpath = (Join-Path "cache" $lowerpath)
66+
67+
# Compute this path in the temp folder
68+
$hirearchy = (Join-Path $dirname $lowerpath)
69+
70+
# Get the parent directory from this filename
71+
$parent = (Split-Path $hirearchy -Parent)
72+
73+
# Create the directory if it doesn't exist
74+
if (!(Test-Path -path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
75+
76+
# Copy the file :)
77+
Copy-Item -Path $path -Destination $hirearchy
78+
}
79+
80+
# Create zip from all the files in the temp folder
81+
Compress-Archive -Force -Path (Join-Path $dirname *) -DestinationPath $OutputZip
82+
83+
# Remove temp directory
84+
Remove-Item -Recurse -Force $dirname
85+

0 commit comments

Comments
 (0)