forked from lautarolecumberry/DetectingFilelessMalware
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWindows.Memory.Mem2Disk.yaml
More file actions
279 lines (252 loc) · 10.1 KB
/
Copy pathWindows.Memory.Mem2Disk.yaml
File metadata and controls
279 lines (252 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
name: Windows.Memory.Mem2Disk
author: Lautaro Lecumberry, Dr. Michael Denzel
description: |
This artifact compares executables in memory (RAM) with those
on hard disk. This way, RAM injections are detected. This rarely
happens legitimately and is mostly used by malware.
This check is executed without dumping the memory and works live
on the target system(s).
See also https://github.com/lautarolecumberry/DetectingFilelessMalware
(Ignored) False Positives
- `ASLR`: If jumps or comparisons are not relative and Address Space Layout Randomization
(ASLR) is enabled, then addresses within these jumps are adjusted in RAM
with a constant offset. This offset can be computed and ignored.
- `BaseOfData`: Relative Virtual Adresses (RVA) cause an offset in the code in memory of a
32-bit process. This is the case when the field BaseOfData is set.
Like ASLR it is a constant offset that is added to addresses.
Test this artifact using `AMSIBypassPatch.ps1`
references:
- https://github.com/okankurtuluss/AMSIBypassPatch/blob/090b54a518fecf1ccf8f54f8691805ef0f9a30f1/AMSIBypassPatch.ps1
parameters:
- name: UploadFindings
description: Upload all executables where code in memory does not match code on disk. This
can potentially generate a lot of traffic. Dry-run before enabling this option.
default: False
type: bool
- name: ProcessNameFilter
type: regex
default: .
- name: PidFilter
default: .
type: regex
- name: ModuleRegEx
type: regex
description: Filter for modules to check. If you want to scan all modules (i.e. libraries) within
a binary, replace with `.*`. The default parameter checks the original binary itself (.exe)
as well as kernelbase.dll, ntdll.dll, user32.dll, kernel32.dll, shell32.dll, msvcrt.dll,
advapi32.dll, and comdlg32.dll (i.e. commonly injected libraries).
default: "\\.exe$|(KERNELBASE|ntdll|amsi|user32|kenrel32|shell32|msvcrt|advapi32|comdlg32)\\.dll$"
- name: Workers
type: int
default: "5"
description: "Number of parallel workers to use"
precondition: SELECT OS From info() where OS = 'windows'
export: |
-- These functions help to resolve the Kernel Device Filenames
-- into a regular filename with drive letter.
LET DriveReplaceLookup <= SELECT
split(sep_string="\\", string=Name)[-1] AS Drive,
upcase(string=SymlinkTarget) AS Target,
len(list=SymlinkTarget) AS Len
FROM winobj()
WHERE Name =~ "^\\\\GLOBAL\\?\\?\\\\.:"
LET _DriveReplace(Path) = SELECT Drive + Path[Len:] AS ResolvedPath
FROM DriveReplaceLookup
WHERE upcase(string=Path[:Len]) = Target
LET DriveReplace(Path) = _DriveReplace(Path=Path)[0].ResolvedPath ||
Path
sources:
- query: |
-- get all processes
LET GetPids = SELECT Pid,
Name,
Username,
if(condition=IsWow64, then=4, else=8) AS IntSize
FROM pslist()
WHERE Name =~ ProcessNameFilter
AND format(format="%d", args=Pid) =~ PidFilter
-- get all memory pages for a certain pid
LET InfoFromVad(Pid) = SELECT Address,
Size,
DriveReplace(Path=MappingName) AS Path
FROM vad(pid=Pid)
WHERE MappingName
AND Protection =~ "xr-"
AND MappingName =~ ModuleRegEx
LET GetTextSegment(Path) = filter(condition="x=>x.Name = '.text'",
list=parse_pe(file=Path).Sections)[0]
-- parse the executable (PE) from memory (specifically, the text segment)
LET GetMetadata(Pid) = SELECT
Path,
str(str=Pid) AS PidFilename,
Address,
GetTextSegment(Path=Path) AS TextSegmentData
FROM InfoFromVad(Pid=Pid)
WHERE Address != 0
AND TextSegmentData.FileOffset
-- helper function for formating
LET Hex(X) = format(format="%#x", args=X)
LET Int64(X) = parse_binary(profile="",
struct="int64",
accessor="data",
filename=X)
-- ASLR may be shifted due to alignment.
-- This is OK so the following values are allowed.
LET CalculateAllowedASLR(ASLR) = SELECT *
FROM foreach(row={
SELECT format(format="00" * 7 + "%08x" + "00" * 8, args=ASLR) AS Data
FROM scope()
}, query={
SELECT int(int="0x" + Data[_value:(_value + 16)]) AS Allowed
FROM range(start=0, end=22, step=2)
})
-- read the executable from memory and hard disk
LET GetContent(Pid, Name) = SELECT
*,
Name,
Path,
Address AS MemAddress,
TextSegmentData.RVA AS BaseRVA,
--calculate ASLR offset to later filter it out
Address - TextSegmentData.VMA AS ASLR,
CalculateAllowedASLR(ASLR=Address - TextSegmentData.VMA).Allowed AS AllowedASLR,
read_file(accessor="process",
offset=Address,
filename=PidFilename,
length=TextSegmentData.Size) AS MemoryData,
TextSegmentData.FileOffset AS DiskAddress,
TextSegmentData.Size AS SegmentSize,
read_file(accessor="file",
offset=TextSegmentData.FileOffset,
filename=Path,
length=TextSegmentData.Size) AS DiskData
FROM GetMetadata(Pid=Pid)
WHERE MemoryData
AND log(dedup=-1,
message="Inspecting Pid %v (%v): %#x-%#x vs %#x-%#x in %v",
args=[Pid, Name, Address, Address + SegmentSize,
DiskAddress, DiskAddress + SegmentSize, Path])
LET FilterContent(Pid, Name) = SELECT *
MemoryData = DiskData AS Comparison
FROM GetContent(Pid=Pid, Name=Name)
-- Filter out not needed comparisons early
WHERE NOT Comparison
-- parameter for start PAGESIZE; compare 1 MB pages first
LET PAGESIZE <= 1024 * 1024
-- helper function for comparisons of PAGESIZE
LET _CompareRegions(Base, X, Y, PAGESIZE) = SELECT
_value + Base AS Offset,
X[_value:(_value + PAGESIZE)] AS XInt,
Y[_value:(_value + PAGESIZE)] AS YInt
FROM range(end=len(list=X), step=PAGESIZE)
WHERE XInt != YInt
-- compare full pages (to speed up comparison)
-- for each 1 MB page which does not match
-- compare 4096 B page
-- for each 4096 B page which does not match
-- compare single integers
LET CompareRegions(X, Y, IntSize) = SELECT
Offset,
Hex(X=XInt) AS X,
Hex(X=YInt) AS Y,
Int64(X=XInt) AS ValueX,
Int64(X=YInt) AS ValueY
FROM foreach(row={
-- 1 MB pages
SELECT *
FROM _CompareRegions(Base=0, X=X, Y=Y, PAGESIZE=PAGESIZE)
},
query={
SELECT *
FROM foreach(row={
-- 4096 B pages
SELECT *
FROM _CompareRegions(Base=Offset, X=XInt, Y=YInt, PAGESIZE=4096)
},
query={
-- single integers
SELECT *
FROM _CompareRegions(Base=Offset, X=XInt, Y=YInt, PAGESIZE=IntSize)
})
})
LIMIT 500
-- check if offsets between X and Y (i.e. RAM and disk) are
-- always the same offset. Then it is ASLR or BaseOfData.
LET CompareUniqueRegions(X, Y, IntSize, ASLR) = SELECT *,
ValueX - ValueY AS Difference
FROM CompareRegions(X=X, Y=Y, IntSize=IntSize)
WHERE ValueX AND NOT Difference IN AllowedASLR
GROUP BY Difference
LET DescribeAddress(rva, module) = version(function="describe_address") != NULL &&
describe_address(rva=rva, module=module).func
-- compare the executable from memory and hard disk
-- only print the ones where they do not match
LET Compare(Pid, Name, IntSize) =
SELECT Pid,
Name,
Path,
ASLR,
{
SELECT Offset + BaseRVA AS RVA,
Offset AS TextOffset,
DescribeAddress(rva= Offset + BaseRVA, module=Path) AS Func,
X AS MemoryValue,
Y AS DiskValue,
Hex(X=Difference) AS Difference,
Hex(X=ASLR) AS ASLR
FROM CompareUniqueRegions(
X=MemoryData,
Y=DiskData,
IntSize=IntSize,
ASLR=ASLR)
} AS Differences,
MemAddress,
DiskAddress,
SegmentSize
FROM FilterContent(Pid=Pid, Name=Name)
WHERE Differences AND log(dedup=-1,
message="Comparing process %v - %v", args=[Pid, Name])
-- compare with uploading the suspicious executables
LET CompareAndUpload(Pid, Name, IntSize) = SELECT
Pid,
Name,
Path,
ASLR,
MemAddress,
DiskAddress,
SegmentSize,
upload(
file=pathspec(DelegateAccessor="process",
DelegatePath=PidFilename,
Path=[dict(Offset=MemAddress, Length=SegmentSize), ]),
name=pathspec(parse=format(format="%s.%d.mem", args=[Path, Pid]),
path_type="windows"),
accessor="sparse") AS UploadMem,
upload(
file=pathspec(DelegateAccessor="file",
DelegatePath=Path,
Path=[dict(Offset=DiskAddress, Length=SegmentSize), ]),
name=pathspec(parse=format(format="%s.%d.disk", args=[Path, Pid]),
path_type="windows"),
accessor="sparse") AS UploadDisk,
Differences
FROM Compare(Pid=Pid, Name=Name, IntSize=IntSize)
-- for every process, evaluate the memory-harddisk-comparison
SELECT *,
Hex(X=MemAddress) AS MemAddress,
Hex(X=DiskAddress) AS DiskAddress,
Hex(X=SegmentSize) AS SegmentSize
FROM foreach(row=GetPids,
workers=Workers,
query={
SELECT *
FROM if(condition=UploadFindings,
then={
SELECT *
FROM CompareAndUpload(Pid=Pid, Name=Name, IntSize=IntSize)
},
else={
SELECT *
FROM Compare(Pid=Pid, Name=Name, IntSize=IntSize)
})
})