Skip to content

Commit b05bca8

Browse files
committed
fixed up everything and it runs correctly again
1 parent 4658bdc commit b05bca8

File tree

2 files changed

+43
-48
lines changed

2 files changed

+43
-48
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ If the haystack is a true backup of a Windows computer, it is very likely there
1414

1515
## Usage
1616
```
17-
python3 main.py /path/to/haystack
17+
python3 needle.py /path/to/haystack
1818
```
1919

2020
## Expected Output
21-
`python3 main.py /mnt/large.vm.backup.tar`
21+
`python3 needle.py /mnt/large.vm.backup.tar`
2222
```
2323
Potentially found SAM at offset 2170928 within searched chunk 977. Writing to 905b5cd4-f9bf-421f-916c-531ad97b5d34_SAM
2424
Potentially found SECURITY at offset 2461744 within searched chunk 977. Writing to de639e0e-a5ad-4d2e-aff2-b415c0087604_SECURITY
@@ -52,7 +52,7 @@ NL$KM:6e0e6b09c158fa85e3ad464f21944dda6a1e237b67bbd302f96cccabe3dc158eeef2bb6536
5252
- [x] Dump local hashes using secretsdump
5353
- [x] Find SECURITY in haystack and write to file
5454
- [x] Expand dumping to include Machine Account
55-
- [ ] Refactor to use argparse (WIP)
55+
- [x] Refactor to use argparse
5656
- [ ] Refactor patterns into a list for easier expandability
5757
- [ ] Add ability to skip to certain chunks if ran before
5858
- [ ] Add flag to only look for system, sam, security, etc

needle.py

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
found = [False, False, False, False]
1717

1818

19-
def init(haystack):
19+
def init(haystack, clean, no_auto_dump):
2020

2121
#if (len(sys.argv) < 2):
2222
# print("Please provide a filename to run on")
@@ -28,19 +28,7 @@ def init(haystack):
2828

2929
f = open(haystack, 'rb')
3030
f_size = os.stat(haystack).st_size
31-
main(f, f_size)
32-
33-
34-
# https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
35-
def str2bool(v):
36-
if isinstance(v, bool):
37-
return v
38-
if v.lower() in ('yes', 'true', 't', 'y', '1'):
39-
return True
40-
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
41-
return False
42-
else:
43-
raise argparse.ArgumentTypeError('Boolean value expected.')
31+
main(f, f_size, clean, no_auto_dump)
4432

4533
# https://stackoverflow.com/questions/4664850/how-to-find-all-occurrences-of-a-substring
4634
def cust_findall(string, substring):
@@ -78,21 +66,27 @@ def autodump(sam, system, security, ntds):
7866
__SAMHashes = SAMHashes(_sam, bootKey, isRemote=False)
7967
print("")
8068
# https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py#L169
81-
__SAMHashes.dump()
69+
try:
70+
__SAMHashes.dump()
71+
except:
72+
pass
73+
8274
if security and system:
8375
for _security in SECURITY_filenames:
84-
print("")
85-
# sometimes this isn't hit and idk why
76+
# sometimes this isn't hit and idk why. future nic here, it's because of the cleaning,
77+
# it caused an exception and we'd skip to the end without dumping LSA
8678
__LSASecrets = LSASecrets(_security, bootKey, None, isRemote=False, history=False)
87-
__LSASecrets.dumpSecrets()
79+
try:
80+
__LSASecrets.dumpSecrets()
81+
print("")
82+
except:
83+
pass
8884
except NameError:
8985
print("Not able to auto parse hives using existing tools. Please install impacket or manually check and dump the registry hives.")
9086
except:
9187
pass
92-
#sys.exit(0)
93-
9488

95-
def search_chunk(chunk, chunk_num, chunk_size, misaligned):
89+
def search_chunk(chunk, chunk_num, chunk_size, haystack, f_size, misaligned, clean):
9690
_temp_SAM = cust_findall(chunk, SAM_pattern)
9791
_temp_SYSTEM = cust_findall(chunk, SYSTEM_pattern)
9892
_temp_SECURITY = cust_findall(chunk, SECURITY_pattern)
@@ -106,7 +100,7 @@ def search_chunk(chunk, chunk_num, chunk_size, misaligned):
106100
SAM_filenames.append(tmp_name)
107101
print("Potentially found SAM at offset {} within searched chunk {}. Writing to {}".format(temp_SAM, chunk_num, tmp_name))
108102
with open(tmp_name, "wb") as SAM:
109-
g = open(filename, 'rb')
103+
g = open(haystack, 'rb')
110104
if misaligned:
111105
g.seek((chunk_size) + ((chunk_num - 1) * chunk_size) + (temp_SAM - 0x30))
112106
else:
@@ -124,7 +118,7 @@ def search_chunk(chunk, chunk_num, chunk_size, misaligned):
124118
print("Potentially found SYSTEM at offset {} within searched chunk {}. Writing to {}".format(temp_SYSTEM, chunk_num, tmp_name))
125119
#print(hexdump.hexdump(chunk[temp_SYSTEM - 0x2F : temp_SYSTEM + len(SYSTEM_pattern)]))
126120
with open(tmp_name, "wb") as SYSTEM:
127-
g = open(filename, 'rb')
121+
g = open(haystack, 'rb')
128122
if misaligned:
129123
g.seek((chunk_size) + ((chunk_num - 1) * chunk_size) + (temp_SYSTEM - 0x2D))
130124
else:
@@ -134,15 +128,16 @@ def search_chunk(chunk, chunk_num, chunk_size, misaligned):
134128
small_chunk = g.read(min(f_size, 17000000))
135129
last_yeet = 0
136130
to_write = b""
137-
for yeet in cust_findall(small_chunk, b"\xFF"*0x200):
138-
if small_chunk[ yeet - 0x1 ] == b"\xFF":
139-
to_write += small_chunk[ last_yeet : yeet ]
140-
last_yeet = yeet
141-
elif small_chunk[ yeet + 0x200 : yeet + 0x205 ] == b"hbin\x00":
142-
# trim
143-
to_write += small_chunk[ last_yeet : yeet ]
144-
last_yeet = yeet + 0x200
145-
to_write += small_chunk[ yeet : ]
131+
if (clean):
132+
for yeet in cust_findall(small_chunk, b"\xFF"*0x200):
133+
if small_chunk[ yeet - 0x1 ] == b"\xFF":
134+
to_write += small_chunk[ last_yeet : yeet ]
135+
last_yeet = yeet
136+
elif small_chunk[ yeet + 0x200 : yeet + 0x205 ] == b"hbin\x00":
137+
# trim
138+
to_write += small_chunk[ last_yeet : yeet ]
139+
last_yeet = yeet + 0x200
140+
to_write += small_chunk[ last_yeet : ]
146141
SYSTEM.write(to_write)
147142
g.close()
148143
found[1] = True
@@ -154,7 +149,7 @@ def search_chunk(chunk, chunk_num, chunk_size, misaligned):
154149
SECURITY_filenames.append(tmp_name)
155150
print("Potentially found SECURITY at offset {} within searched chunk {}. Writing to {}".format(temp_SECURITY, chunk_num, tmp_name))
156151
with open(tmp_name, "wb") as SECURITY:
157-
g = open(filename, 'rb')
152+
g = open(haystack, 'rb')
158153
if misaligned:
159154
g.seek((chunk_size) + ((chunk_num - 1) * chunk_size) + (temp_SECURITY - 0x30))
160155
else:
@@ -175,20 +170,20 @@ def search_chunk(chunk, chunk_num, chunk_size, misaligned):
175170
# NTDS_filenames.append(tmp_name)
176171
# print("Potentially found Microsoft ESEDB, treating as NTDS at offset {} within searched chunk {}. Writing to {}".format(temp_NTDS - 8, chunk_num, tmp_name))
177172
# with open(tmp_name, "wb") as NTDS:
178-
# g = open(filename, 'rb')
173+
# g = open(haystack, 'rb')
179174
# g.seek(((chunk_num - 1) * chunk_size) + (temp_NTDS - 0x8))
180175
# # 16MB is supposedly max size of registry hives on disk; impacket doesn't seem to have a problem with extra data at the end of the registry hives.
181176
# NTDS.write(g.read(min(f_size, 16 * 1024 * 1024)))
182177
# g.close()
183178
# found[3] = True
184179

185-
def check():
180+
def check(no_auto_dump):
186181
# shoutout to @knavesec for this monstrosity, summing across a list of bools hurts me
187-
if ((found[1] == True) and (sum(found) >= 2)):
182+
if ((not no_auto_dump) and ((found[1] == True) and (sum(found) >= 2))):
188183
autodump(found[0], found[1], found[2], found[3])
189184

190185

191-
def main(f, f_size):
186+
def main(f, f_size, clean, no_auto_dump):
192187
# reading in chunks and scanning through the chunks, if we don't find anything, maybe our chunks were too small and the pattern was at the boundry of chunks so we need to seek by chunk / 2 and scan again
193188

194189
chunk_size = 4 * 1024 * 1024 # 4MiB
@@ -200,17 +195,17 @@ def main(f, f_size):
200195
f.seek(start)
201196
chunk = f.read(chunk_size)
202197
chunk_num += 1
203-
if (search_chunk(chunk, chunk_num, chunk_size, False) == True):
198+
if (search_chunk(chunk, chunk_num, chunk_size, haystack, f_size, False, clean) == True):
204199
break
205200
start = end
206201
end += chunk_size
207202

208203
# finish last partial chunk just in case
209204
f.seek(start)
210205
chunk = f.read(f_size - start)
211-
search_chunk(chunk, chunk_num, chunk_size, False)
206+
search_chunk(chunk, chunk_num, chunk_size, haystack, f_size, False, clean)
212207

213-
check()
208+
check(no_auto_dump)
214209

215210
# misalign and search the new chunks in case the \x00S\x00A\x00M and regf are across chunk boundries
216211
chunk_size = chunk_size // 4
@@ -221,12 +216,12 @@ def main(f, f_size):
221216
f.seek(start)
222217
chunk = f.read(chunk_size)
223218
chunk_num += 1
224-
if (search_chunk(chunk, chunk_num, chunk_size, True) == True):
219+
if (search_chunk(chunk, chunk_num, chunk_size, haystack, f_size, False, clean) == True):
225220
break
226221
start = end
227222
end += chunk_size
228223

229-
check()
224+
check(no_auto_dump)
230225

231226
if __name__ == '__main__':
232227
parser = argparse.ArgumentParser(
@@ -235,15 +230,15 @@ def main(f, f_size):
235230
epilog=textwrap.dedent('''Examples:\npython3 needle.py /mnt/HTB/Bastion/file.vhd --hacky-clean\npython3 needle.py /mnt/VeritasNetbackup/dc.tar''')
236231
)
237232
# https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
238-
parser.add_argument('--hacky-clean', type=str2bool, nargs='?', const=True, default=False, help="Clean dirty on disk registry keys in a very hacky way that somehow works (usually needed for vhd)")
239-
parser.add_argument('--auto-dump', type=str2bool, nargs='?', const=True, default=True, help="Try to automatically use secretsdump if SAM and SYSTEM or SYSTEM and SECURITY are found")
233+
parser.add_argument('--clean', action='store_true', default=False, help="Clean dirty on disk registry keys in a very hacky way that somehow works (usually needed for vhd)")
234+
parser.add_argument('--no-auto-dump', action='store_true', default=False, help="Try to automatically use secretsdump if SAM and SYSTEM or SYSTEM and SECURITY are found")
240235
parser.add_argument('haystack', metavar='haystack', type=str, nargs='*', help='Haystack to parse')
241236

242237
args = parser.parse_args()
243238

244239
if (args.haystack != None):
245240
#do things
246241
for haystack in args.haystack:
247-
init(haystack)
242+
init(haystack, args.clean, args.no_auto_dump)
248243
else:
249244
parser.print_help()

0 commit comments

Comments
 (0)