Hello Navi

Tech, Security & Personal Notes

D'oh, I overslept and missed most of the race! But wait, my friend took a picture while I was out, but I can't tell who's in the lead. Can you help me figure out the two cars that are in the lead? Usually they like to twin around this time of night...

Flag format: texsaw{num1_num2} (e.g., texsaw{21_44})

Analysis

1. Image Metadata (EXIF)

Analyzing the provided image (or the metadata extracted from it) reveals several critical data points:

Attribute Value Analysis
Camera Model Samsung Galaxy S24 Ultra High-end mobile sensor, likely captured with 3x optical zoom.
Date/Time 2026:01:24 22:14:12 Captured during the night of January 24th, 2026.
GPS Latitude 29° 11' 4.79" N Geolocation leads to Florida, USA.
GPS Longitude 81° 4' 28.43" W Specifically, the Daytona International Speedway.
Exposure 1/30s, f/2.4, ISO 320 Nighttime setting with motion blur, typical of racetrack photography.

2. Event Identification

Plugging the coordinates and the date into a search engine confirms the event: The 2026 Rolex 24 at Daytona.

This is a premier 24-hour endurance race held annually in late January at the Daytona International Speedway. The timestamp (10:14 PM EST on Saturday) puts the photograph roughly 8-9 hours into the 24-hour race.

3. The "Twinning" Clue

The challenge description mentions: "Usually they like to twin around this time of night..."

In the context of the IMSA WeatherTech SportsCar Championship (which runs the Rolex 24), "twinning" refers to teammate cars running in close formation. During the 2026 season, the Porsche Penske Motorsport team, running the Porsche 963 in the GTP class, was famous for their identical "mirror" liveries and consistent pace that often saw them running 1-2 on the track.

The two Porsche Penske cars are:

  • Car #6
  • Car #7

4. Verification

Looking at the race leaders during the night shift of the 2026 Rolex 24, the #6 and #7 Porsche 963s were indeed dominant. To distinguish them at night, teams use colored LED "Lumirank" displays:

  • Car #7: Blue LED
  • Car #6: Red LED

The "twinning" behavior is a signature of the Penske Porsches as they manage the gap and maintain the lead together.

Flag

just try 6 first or 7 first

texsaw{6_7}

Check out our IRC server and run the command /motd!

Server: irc.texsaw.org

Solution

To solve this challenge, we need to connect to the texSAW IRC server and view the "Message of the Day" (MOTD), which is a common place for CTF organizers to hide initial information or rules.

1. Install an IRC Client

1
2
3
4
# I use Arch BTW
paru -S weechat
# or
paru -S irssi

2. Connect to the Server

Launch your client and add the TexSAW server to your configuration. This makes it easier to reconnect later.

1
2
3
# Example using irssi
/server add texsaw irc.texsaw.org
/connect texsaw

3. Find the Flag

Once you've successfully connected, the server will usually send the MOTD automatically. If you miss it, manually request it with the following command:

1
2
/motd
# texsaw{w31c0M3_t0_t3xSAW_2O26!}

Useful IRC Commands

While you're on the server, you might want to join the discussion or interact with other participants:

  • /nick [new_nick]: Change your current nickname.

or

1
2
/set irc.server_default.nicks comma,separated,list,of,nicks
/set irc.server.[SERVER_NAME].nicks "
  • /list: List all available channels on the server.
  • /join #general: Join the official general discussion channel.
  • /whois [nick]: View information about a specific user.
  • /msg [nick] [message]: Send a private message (useful for contacting admins).
  • /part #[channel]: Leave a specific channel.
  • /quit: Disconnect from the server and close your session.

Flag

texsaw{w31c0M3_t0_t3xSAW_2O26!}

Ever copy-pasted a code snippet from a browser (Gemini) into Neovim, only to see a strange + or a highlighted <U+00A0>? Why does your Python script throw a SyntaxError on a line that looks perfectly fine?

The answer lies in the "invisible" world of Unicode control characters. These characters were designed for typography, but they have become a nightmare for modern programmers.

1. The Most Common Culprit: NBSP (U+00A0)

U+00A0 (Non-Breaking Space) wasn't invented to annoy programmers. In the world of Typography, it serves a very legitimate purpose.

Core Origin: Prevent Line Wrapping

In traditional word processing and browser rendering, a standard space (U+0020) is a "soft" break point. When a line is full, the system wraps the text at the space.

However, some word pairs should never be separated. NBSP tells the rendering engine: "These two words are bound together. If you can't fit them both, move the entire block to the next line."

Proper Use Cases

  • Values & Units: 100 kg or 500 MHz. You don't want 100 at the end of a line and kg at the start of the next.
  • Names & Titles: Mr. Anderson or Dr. Freeman.
  • Language Specifics: In French typography, characters like : or ! must be preceded by a space. To prevent the punctuation from being isolated on a new line, NBSP is used.

Why It's a Coding Nightmare

Web developers and WYSIWYG editors (like Microsoft Word) often abuse &nbsp; to force indentation or spacing. Because browsers "collapse" multiple standard spaces (U+0020) into one, people use NBSP to create "hard" whitespace.

When you copy code from these sources, U+00A0 is carried over into your terminal. Python, Bash, and C are rigorous: they only recognize U+0020 as a valid syntax separator. Anything else is an "invalid character."

2. Visualizing and Fixing NBSP in Neovim

If you use Neovim, you can expose these hidden characters by setting listchars.

Configuration (init.lua)

1
2
3
4
5
6
7
8
9
vim.opt.list = true -- Enable list mode to show invisible characters

vim.opt.listchars = {
nbsp = '☠', -- Highlight U+00A0 as a skull (or '✗', '⍽')
trail = '·', -- Show trailing spaces
tab = '▸ ', -- Make Tabs visible
extends = '❯', -- Show wrap indicators
precedes = '❮',
}

The Quick Fix

To substitute all NBSP characters with normal spaces in the current buffer:

1
:%s/\%u00a0/ /g

3. The Hidden Menace: Zero-Width Characters (U+200B - U+200F)

If NBSP is a nuisance, Zero-Width Characters are the "shadow realm" of Unicode. These characters are completely invisible in most GUI editors but occupy bytes in your file.

Common Variants

  • <U+200B> Zero Width Space (ZWSP): A "potential" break point for long URLs or languages without natural spaces (like Thai).
  • <U+200C> Zero Width Non-Joiner (ZWNJ): Prevents characters from forming a ligature (e.g., stopping f and i from becoming fi).
  • <U+200D> Zero Width Joiner (ZWJ): The "stitcher." It combines multiple characters into one.
    • Emoji Magic: A "Woman Astronaut" (👩‍🚀) is actually Woman (👩) + ZWJ + Rocket (🚀).
    • Family: 👨‍👩‍👧‍👦 is a chain of 4 emojis connected by 3 ZWJs.
  • <U+200E> (LRM) & <U+200F> (RLM): Used to control Left-to-Right and Right-to-Left text direction in bi-directional (Bidi) text.

4. Why They Are Dangerous (The Invisible Threat)

  1. Syntax Error Hell: You copy a Python script, and it fails with SyntaxError: invalid character in identifier. The error is "invisible" because the zero-width character is hidden inside a variable name.
  2. Security (Homoglyph Attacks): Attackers can create two identical-looking URLs. github.com and github.com (with a hidden <U+200B>) can lead you to a phishing site.
  3. Invisible Fingerprinting: Some companies use combinations of zero-width characters to encode a "hidden watermark" or employee ID in sensitive documents. If you leak the text, they can extract the ID from the invisible characters.

5. The Neovim Purge: Clean Your Code

Neovim's listchars will often render these as hex codes like <U+200B> if they aren't explicitly handled, making them easy to spot.

To wipe your file of all zero-width "garbage" from U+200B to U+200F:

1
:%s/[\%u200B-\%u200F]//g

This regex matches the entire range of common zero-width control characters and deletes them instantly.

Conclusion

In the UNIX philosophy, content and presentation are separate. Relying on invisible characters to control layout is "soulless." As a power user, your code should be clean, visible, and free of typography-bloat.

Keep your listchars on, and never trust a copy-paste from a browser blindly.

OSINT II

TIFFANY&Co.

Halloween Day 3 - Python Obfuscation

多层 zlib(base64(reverse())) 混淆,递归解包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64
import re
import zlib

payload_bytes = b"" # <- 第一层 payload

layer = 1
while True:
try:
decoded = zlib.decompress(base64.b64decode(payload_bytes[::-1])).decode()
match = re.search(r"b'([^']+)'|b\"([^\"]+)\"", decoded)
if match:
payload_bytes = (match.group(1) or match.group(2)).encode()
layer += 1
else:
print(decoded)
break
except Exception:
print(decoded)
break

print(f"[*] Layer: {layer}")

最终明文是一个逐字符打印脚本,答案藏在变量里:zieltext = "Die Antwort lautet GRABREDE."

→ GRABREDE

Halloween Day 4 - Scream Cipher

1
ĀA̰Á AÂÃȀÁȂ A̦ÅÄ AȂÁ ĂÅÅẠẢÂA̋ A̮ÅȂ ẢÃ ȦĂÅÅA̱...

Scream Cipher (XKCD) → BLOOD

Halloween Day 5 - Hex Colors

6 个弹孔的红色色值,取高位字节:

1
2
#670000 #650000 #660000 #610000 #680000 #720000
67 65 66 61 68 72

from hex → gefahr

Halloween Day 6a - Base64

直接 base64 decode → Bein

Halloween Day 6b - Morse Code

... - .. .-. -... → STIRB

Winter Day 1 - View Source

Ctrl+Shift+U 查看源码,答案在被雪覆盖的 <p> 标签中 → snowflake

Winter Day 2 - Base64

QXBlcmxhYXE= → Aperlaaq

Winter Day 4 - Wham! Typos

歌词中混入了 prompt injection 和故意拼写错误,提取异常字母:

原文 应为 异常字母
beby baby e
A wrapped I wrapped A
Nor Now r
sowl soul w
rear year r
mover lover m

→ Earworm

Octopus

A human counts with 10 fingers, an octopus with 8 tentacles. The answer is octopus2471.

2471 (octal) → 1337

Brainfuck?

实际是 JSFuck,Node.js 中 console.log(/* code */) 执行即可。

Host

1
$ curl http://185.26.156.141

响应中 <kbd>aegir.uberspace.de</kbd> 即为答案。

Rockyou

前端 JS 校验 sha256(salt + value),rockyou 字典爆破:

1
2
3
4
5
6
7
8
9
10
11
from hashlib import sha256

salt = "3NL/usjb4vEg"
target = "9bcf0c8289a97d33021b4790659396d9f8af1085210d2186b8ec38efcdc31472"

with open("/path/to/rockyou.txt") as f:
for line in f:
word = line.strip()
if sha256((salt + word).encode()).hexdigest() == target:
print(word)
break

Time Zones

Chameleon 插件修改浏览器时区为 UTC-10。

Free Fall

\[h = \frac{1}{2} g t^2 = \frac{1}{2} \times 9.81 \times 1.43^2\]

Treasure Hunt

  • The largest online encyclopedia → wikipedia
  • Delay to allow data to travel from one point to another → latency
  • A finite, unambiguous set of instructions → algorithm
  • A machine learning model inspired by the human brain's structure → neural network

UTF-5

□ = 0, ■ = 1,5-bit 编码(1=A, 2=B, ...):

编码 二进制 值 字母
□■■□□ 01100 12 L
□■■■■ 01111 15 O
□□□■■ 00011 3 C
□■□□□ 01000 8 H
□■□■■ 01011 11 K
□□□□■ 00001 1 A
■□□■□ 10010 18 R
■□■□□ 10100 20 T
□□■□■ 00101 5 E

→ LOCHKARTE

Alphabet - Control Characters

ASCII contains an alphabet of uppercase letters and one of lowercase letters. But there is also a third, which is used in this file.

控制字符 = 第 N 个字母,空格保持:

1
2
3
$ xxd a
00000000: 1420 2020 010e 2020 2020 2009 2020 2020 . .. .
00000010: 0e14 2020 0c0a .. ..
字节 值 字母
0x14 20 T
0x01 1 A
0x0e 14 N
0x09 9 I
0x0e 14 N
0x14 20 T
0x0c 12 L

T _ _ _ A N _ _ _ _ _ I _ _ _ _ N T _ _ L → control

Honey Morello - Zero-Width Steganography

文本中藏有 zero-width characters(U+200B = 0, U+200C = 1),每行提取后按 5-bit 解码:

1
2
3
4
5
6
7
8
9
for line in text.split("\n"):
bits = ""
for ch in line:
if ch == "\u200b":
bits += "0"
elif ch == "\u200c":
bits += "1"
if bits and (val := int(bits, 2)):
print(chr(96 + val), end="")

→ the answer is meme

Mental Arithmetic

15 道限时算术题,JS 自动填充:

1
2
3
4
5
for (let i = 0; i < 15; i++) {
let task = document.getElementById("task" + i).innerText.replace("=", "").trim();
document.getElementById("ans" + i).value = eval(task);
}
document.forms[0].submit();

Base4096

Emoji 编解码:https://base4096.infinityfreeapp.com/?i=1

Transposed - Rail Fence Cipher

Rail Fence Cipher decode, key = 4

challenges

Game 20

1
reverseme: ELF 32-bit LSB executable, Intel i386, statically linked, not stripped

Source

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE s[30]; // [ebp-2Eh]
int v5; // [ebp-34h] BYREF
unsigned int v7;

if ( argc > 1 || strcmp(*argv, "./suninatas") ) // 必须重命名为 suninatas
return 0;
// 清空所有环境变量
for ( i = 0; envp[i]; ++i )
for ( j = 0; j < strlen(envp[i]); ++j )
envp[i][j] = 0;

_isoc99_scanf("%30s", s);
v7 = Base64Decode(s, &v5);
if ( v7 <= 0xC ) // 解码后最多 12 字节
{
memcpy(&input, v5, v7); // 拷贝到 BSS 段全局变量 input
if ( auth(v7) == 1 )
correct();
}
}

_BOOL4 __cdecl auth(int a1)
{
_BYTE v2[8]; // [ebp-14h]
char *s2; // [ebp-Ch]
int v4; // [ebp-8h] BYREF <-- 关键:距 saved ebp 仅 8 字节

// buffer overflow
memcpy(&v4, &input, a1); // 12 字节拷贝:8 字节填满 v4,4 字节溢出覆盖 saved ebp
s2 = (char *)calc_md5((int)v2, 12);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}

void __noreturn correct()
{
if ( input == 0xDEADBEEF )
IO_puts("Congratulation! you are good!");
exit(0);
}

Analysis

限制条件:

  • 文件必须重命名为 suninatas(strcmp(*argv, "./suninatas"))
  • Base64 解码后最多 12 字节,无法直接覆盖 return address(需要 12+4=16 字节)

Stack Pivot

auth 中 v4 位于 [ebp-8h],memcpy 拷入 12 字节时:

1
2
3
4
[ebp-8h] v4       ← bytes 0-3
[ebp-4h] ← bytes 4-7
[ebp+0h] saved ebp ← bytes 8-11 (被覆盖!)
[ebp+4h] ret addr ← 无法触及

虽然无法直接控制 return address,但可以控制 saved ebp,利用 leave; ret 的链式效应实现栈迁移:

  1. auth 的 leave 恢复了被篡改的 ebp 给 main
  2. main 结束时执行 leave(mov esp, ebp; pop ebp),esp 被迁移到 &input
  3. main 执行 ret,从 &input + 4 弹出值作为 EIP

Payload 结构

12 字节 = 3 个 DWORD(little-endian):

Offset 值 作用
0-3 0xDEADBEEF main 的 pop ebp 读取,同时满足 correct() 中 input == 0xDEADBEEF
4-7 &correct (0x0804925f) main 的 ret 弹入 EIP
8-11 &input (0x0811c9ec) 覆盖 auth 的 saved ebp,触发栈迁移

Exploit

获取地址(statically linked,地址固定):

1
2
3
$ objdump -t ./suninatas | grep -E ' (correct|input)$'
0811c9ec g O .bss 0000000d input
0804925f g F .text 00000031 correct

生成 payload:

1
2
python -c "import base64, struct; print(base64.b64encode(struct.pack('<III', 0xDEADBEEF, 0x0804925f, 0x0811c9ec)).decode())"
****************

because

1
memcpy(&input, v5, v7);  // 拷贝到 BSS 段全局变量 input

&input + 0 (前 4 字节): 0xDEADBEEF

&input + 4 (中 4 字节): 0x0804925f (&correct 的绝对地址)

&input + 8 (后 4 字节): 0x0811c9ec (&input 的绝对地址)

1
memcpy(&v4, &input, a1);  // 12 字节拷贝:8 字节填满 v4,4 字节溢出覆盖 saved ebp

[ebp-8] 到 [ebp-5]: 填入 0xDEADBEEF

[ebp-4] 到 [ebp-1]: 填入 0x0804925f

[ebp+0] 到 [ebp+3]: 填入 0x0811c9ec (&input) <-saved ebp

when auth calls leave; ret to return to main

mov esp, ebp: esp = ebp -> &input

pop ebp: esp -> ebp + 4 = &input + 4 and put 0xDEADBEEF into ebp

ret (pop eip): eip -> esp = &input + 4 = 0x0804925f = &correct

at &correct check input == 0xDEADBEEF

1
2
3
4
$ ./suninatas
Authenticate : ****************
hash : d932bf2657c20fd638787756d680c95c
Congratulation! you are good!
776t3l+SBAjsyREI