Public SSH servers are constantly scanned and brute-forced. Changing
the SSH port is not real security by itself, but it reduces background
noise. fail2ban adds an actual defensive layer by watching
failed login attempts and temporarily banning abusive IP addresses.
This note records a minimal setup for SSH protection with
fail2ban and systemd logs.
Install Fail2ban
Arch Linux:
1
sudo pacman -S fail2ban
Debian/Ubuntu:
1
sudo apt install fail2ban
Enable the service:
1
sudo systemctl enable --now fail2ban
Optional: Change SSH Port
Edit the SSH daemon config:
1
sudo vim /etc/ssh/sshd_config
Set a non-default port. This article uses 22222 as an
example; replace it with your own value.
1
Port 22222
Before restarting SSH, keep your current SSH session open. A bad
config or firewall mistake can lock you out.
Validate the config:
1
sudo sshd -t
Restart SSH:
1
sudo systemctl restart sshd
Some distributions use ssh instead of sshd
as the service name:
1
sudo systemctl restart ssh
Test login from another terminal before closing the old session:
1
ssh -p 22222 user@example.com
Configure Fail2ban For SSH
Create a local jail config instead of editing the packaged
defaults:
1
sudo vim /etc/fail2ban/jail.d/sshd.local
Minimal config:
1 2 3 4
[sshd] enabled = true port = 22222 backend = systemd
If you keep SSH on the default port, use:
1
port = ssh
Restart fail2ban:
1
sudo systemctl restart fail2ban
Check Status
List enabled jails:
1
sudo fail2ban-client status
Check the SSH jail:
1
sudo fail2ban-client status sshd
View logs:
1
sudo journalctl -u fail2ban -e
On systems where fail2ban logs to a file:
1
sudo less /var/log/fail2ban.log
Unban An IP
If you accidentally ban yourself, unban the IP from another trusted
session:
1
sudo fail2ban-client set sshd unbanip 203.0.113.10
203.0.113.10 is a documentation example address. Replace
it with the real IP you need to unban.
Safer SSH Baseline
Fail2ban is only one layer. These SSH settings are usually worth
enabling too:
1 2 3
PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes
After changing SSH config, always validate and restart:
1 2
sudo sshd -t sudo systemctl restart sshd
Notes
Do not rely on port changes as the only protection.
Prefer SSH keys over passwords.
Keep an existing SSH session open while changing SSH and firewall
settings.
If a firewall is enabled, allow the SSH port before restarting
SSH.
Use /etc/fail2ban/jail.d/*.local files for local
overrides so package updates do not overwrite your changes.
This note records a minimal InspIRCd configuration for
running a small IRC server with TLS support. The example exposes a plain
client port on 6667 and a TLS client port on
6697.
The TLS setup uses the ssl_gnutls module and certificate
files stored under /etc/inspircd/cert/.
Requirements
Install InspIRCd and make sure the GnuTLS SSL module is
available:
1
sudo pacman -S inspircd
On Debian/Ubuntu-style systems, the package name may differ:
1
sudo apt install inspircd
You also need a certificate and private key. For a public domain, use
Let's Encrypt or another ACME client. The config below expects:
This is my current .gdbinit setup for binary
exploitation and reverse engineering. It loads pwndbg,
switches disassembly to Intel syntax, follows child processes after
fork, and automatically opens separate tmux
panes for disassembly, stack, backtrace, registers, and an IPython
scratch pane.
Requirements
Install these first:
gdb
pwndbg
tmux
ipython optional, only used for the scratch pane
The config assumes pwndbg is installed at:
1
/usr/share/pwndbg/gdbinit.py
If your pwndbg install path is different, change the first line of
the config.
Controls how much disassembly and stack context pwndbg prints.
Base Address Helpers
These two lines are personal scratch variables:
1 2
set $mybase1 = 0x0000555555554000 set $mybase = 0x7ffff7ffc000
They are not required. I use them as quick base-address anchors while
debugging PIE binaries or shared libraries. You can remove them or
replace them with values from piebase, vmmap,
or a leak.
Example usage:
1 2
x/10i $mybase1 + 0x1234 b *($mybase1 + 0x19e3)
Adjusting The Layout
The pane sizes are controlled by
tmux split-window -l:
Yazi is a terminal file manager written in Rust. It is fast,
keyboard-driven, and works well as a lightweight replacement for opening
a full GUI file manager when working inside a terminal or SSH
session.
This note records a minimal Linux setup: install the standalone
binary and add a shell wrapper so that exiting Yazi can change the
current shell directory.
Installation
Download the latest musl build from the official release page:
unar yazi-x86_64-unknown-linux-musl.zip cd yazi-x86_64-unknown-linux-musl
Install both binaries into a directory in $PATH:
1
sudomv yazi ya /usr/local/bin/
Check that it works:
1 2
yazi --version ya --version
Shell Wrapper
By default, a terminal file manager cannot directly change the parent
shell's current directory. Yazi solves this by writing the final
directory to a temporary file. A shell function can read that file after
Yazi exits and then cd there.
Add this function to .bashrc, .zshrc, or
the shell config you use:
When you quit Yazi, the terminal will stay in the directory you were
viewing.
Notes
yazi is the file manager itself.
ya is Yazi's helper command, used for package/plugin
management and integrations.
The musl build is convenient because it is mostly self-contained and
works on many Linux distributions.
If sudo mv fails, make sure /usr/local/bin
exists and is included in $PATH.
Update
To update, download the latest release again and replace the old
yazi and ya binaries:
1 2 3 4
wget https://github.com/sxyazi/yazi/releases/latest/download/yazi-x86_64-unknown-linux-musl.zip unar yazi-x86_64-unknown-linux-musl.zip cd yazi-x86_64-unknown-linux-musl sudomv yazi ya /usr/local/bin/
Writing to 32-bit registers (e.g., EAX) automatically
clears the upper 32 bits of the corresponding 64-bit register
(RAX). xor EAX, EAX is equivalent to
xor RAX, RAX but has a shorter encoding.
Return Value: RAX (with
RDX potentially holding high bits or extra data).
Syscall arguments: RDI,
RSI, RDX, R10, R8,
R9; syscall number in RAX.
RCX and R11 are clobbered by
syscall.
x86_32: Arguments are passed via the
stack in right-to-left order.
1 2
man syscall grep -R "__NR_execve" /usr/include/asm* /usr/include/x86_64-linux-gnu/asm* 2>/dev/null
REX.W Prefix (0x48)
The REX prefix range is 0x40-0x4F
(0100 WRXB), used to extend x86 instructions to 64-bit:
W: Set to 1 for 64-bit operations.
R, X, B: Extension for register addressing
(R8-R15).
mov RDI, RSP requires a REX prefix with W=1 →
0100 1000 = 0x48.
Stack Frame Layout
RBP (Register): Points to the bottom
of the current function stack frame. Local variables are
accessed relative to it (e.g., [RBP - 0x10]).
saved RBP (Stack Data): A backup of
the caller's RBP, used to restore the previous frame upon
return.
Prologue & Epilogue
1 2 3 4 5 6 7 8
; Prologue (Entering Function B) push RBP ; Save Caller A's RBP -> becomes saved RBP mov RBP, RSP ; Establish B's stack frame
; Epilogue (Exiting Function B) - Equivalent to 'leave; ret' mov RSP, RBP ; Clean up local variables pop RBP ; Restore Caller A's RBP ret ; Return to Caller A
In memory, bytes can represent either code or data; the CPU
distinguishes them solely based on RIP. Bytes pointed to by
RIP are executed as opcodes, while others accessed via
pointers are treated as data.
Security Mitigations
Name
Description
NX
No-eXecute bit. Hardware-level page attribute marking memory pages
as non-executable.
W^X
Write XOR Execute. Memory is either writable or executable, never
both simultaneously.
ASLR
Address Space Layout Randomization. Randomizes memory layout at
runtime.
PIE
Position Independent Executable. Randomizes the binary
base address.
canary
Stack protector. Detects buffer overflow by checking a
secret value before returning.
Checksec Reading
Mitigation
Exploitation impact
No Canary
Saved RIP overwrite is usually direct once the offset
is known.
Canary
Need leak, byte brute-force, non-return control flow, or write
primitive that skips the canary.
NX disabled
Stack/heap shellcode is viable if control can jump to it.
NX enabled
Prefer ROP, ret2libc, mprotect, mmap,
JOP/COP, or existing executable regions.
No PIE
Binary code addresses are fixed, e.g. win() / gadgets /
PLT have stable absolute addresses.
PIE enabled
Need code pointer leak, PIE base recovery, or partial
overwrite/brute force.
No RELRO
GOT is writable and can be overwritten before/after resolution.
Partial RELRO
.got.plt remains writable; lazy binding is still
present.
Full RELRO
GOT is read-only after startup; GOT overwrite is blocked.
SHSTK
Intel CET shadow stack verifies returns; classic ret
overwrite/ROP may fail.
IBT
Intel CET indirect branch tracking requires indirect-call/jump
targets to begin with endbr64.
checksec hints at the easiest path, but it is not a
proof. A binary with executable stack may still be protected by
SHSTK/IBT, input filters, unstable stack
addresses, or non-return exits.
ELF & Dynamic Linking
ELF header: architecture, entry point, program
headers.
Program headers: loader view; maps segments into
memory (LOAD, GNU_STACK,
GNU_RELRO).
Trace data flow: where user bytes land, how size is
computed, where pointers are stored, whether data is copied, validated,
freed, or printed.
Convert checks into constraints: compare constants,
printable-byte filters, checksums, jump tables, index math, bounds
checks.
Exploit mapping: choose leak/write/control-flow
primitive that matches mitigations.
Useful questions while reversing:
Is the binary stripped? If not, start with symbols. If stripped,
start with imports, strings, and cross-references.
Does the program return normally, call exit, or loop
forever? Return-address hijacking only triggers on a return path.
Does a length check use signed or unsigned comparison? Which width:
byte, word, dword, or
qword?
Are there hidden repeat/backdoor paths that re-enter the vulnerable
function before canary validation?
Does a parser use a switch/jump table? Can an out-of-range or
special directive reach extra code?
radare2 Quick Reference
1 2 3 4 5 6 7 8 9
rax2 0x28 # Hex/decimal conversion rabin2 -I ./chall # Binary info rabin2 -z ./chall # Data-section strings rabin2 -zz ./chall # All strings rabin2 -i ./chall # Imports rabin2 -e ./chall # Entry points r2 -A ./chall # Analyze and open r2 -w ./chall # Open writable for patching r2 -A -q -c "pdf @ main" ./chall # Non-interactive disassembly
Inside r2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
aaa # Full analysis afl # List functions s main # Seek to symbol/address s - # Seek back pdf # Print current function pdf @ sym.main # Print a function pd 20 # Print 20 instructions px 64 # Hexdump iz # Strings in data sections izz # Strings in whole binary axt @ addr # Cross-references to address axf @ addr # Cross-references from address VV # Visual graph wx 909090 # Patch raw bytes (requires -w) wa nop # Assemble and patch instruction (requires -w)
Patching Notes
Patching data is safer than patching code when the target check
compares constants or file-format bytes.
For code patches, inspect instruction length first; replacing a
longer conditional branch with shorter bytes needs padding with
nop.
In non-PIE ELF, runtime VA is often near file mapping base
0x400000; still convert VA to file offset using program
headers or tooling instead of guessing.
1 2 3
rasm2 -a x86 -b 64 "nop" rasm2 -a x86 -b 64 -d "90" objdump -d -Mintel ./chall | less
pwndbg /path/to/binary # Launch start # Break at main (fails if no main symbol) starti # Break at first instruction (initialization code) entry # Break at ELF entry point info functions# List all analyzed function symbols info frame # Show current stack frame piebase # Show PIE base address vmmap # Show memory layout (r-x, rw-, etc.) breakrva 0x19e3 # Break at Relative Virtual Address (useful for PIE) stack # Show stack canary # Print and find canaries on the stack context <section> # Show specific context window checksec # Check for security features (NX, ASLR, PIE, Canary) cyclic 100 # Generate 100-byte De Bruijn sequence cyclic -l 0x6161616c # Find overflow offset using crash address retaddr # Highlight return addresses in the current stack frame rop # Simple ROP Gadget search got # Show GOT state (resolved vs unresolved) plt # View Procedure Linkage Table heap # Overview of all heap chunks and their status bins # View free lists (fastbins, unsorted, small, large, tcache) arena # View detailed structure of the main arena (malloc_state)
p $rsp# Show current stack pointer disass main # Disassemble 'main' function break *main+123 # Breakpoint at main+123
Category
Command
Stepping
n (step over), s (step into),
fin (finish), c (continue)
pwn shellcraft -l # Output format (default: hex), choose from {e}lf, {r}aw, {s}tring, {c}-style array, {h}ex string, hex{i}i, {a}ssembly code, {p}reprocessed code, escape{d} hex string pwn shellcraft amd64.linux.sh -f d
De Bruijn Sequence (Cyclic)
A De Bruijn sequence \(B(k, n)\) is
a cyclic sequence where every possible string of length \(n\) over an alphabet of size \(k\) appears exactly once.
Why it's used: When a binary crashes
due to a buffer overflow, the RIP will be
overwritten by a 4 or 8-byte chunk of the sequence. Since every chunk is
unique, cyclic_find can instantly determine the exact
offset needed for the exploit.
Crash-offset workflow:
1 2 3 4 5 6 7 8 9 10
from pwn import *
context.arch = "amd64" p = process("./chall") p.send(cyclic(300)) p.wait()
For 64-bit patterns, keep n consistent between
generation and lookup if you use non-default subsequence size.
Environment Control
Disable ASLR for Debugging: gdb
disables ASLR by default. For a clean environment, use
setarch x86_64 -R /bin/bash to spawn a shell where all
child processes (excluding SUID) have ASLR disabled.
SUID Trap: gdb cannot disable
ASLR for SUID binaries due to kernel security. Remove the
SUID bit (chmod u-s) or copy the binary to a
local directory before debugging.
Global ASLR Toggle:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
(Reset to 2 when finished).
Environment affects stack addresses. argv[0] length,
environment variables, terminal, and pwntools launching mode can shift
stack buffers. Use env={} in process() or add
a NOP sled when stack shellcode depends on approximate addresses.
Information Disclosure
Causes
String Termination Problems
C strings are null-terminated, meaning they lack
length metadata and rely on a 0x00 byte to mark the
end.
Missing Null byte: If a program reads exactly \(N\) bytes into an \(N\)-byte buffer (e.g., using
read()), it won't append a null byte.
Disclosure: Functions like
printf("%s") will continue reading past the buffer until
they hit a null byte, potentially leaking adjacent
sensitive data like a flag or canary.
Uninitialized Data (Stack
Frame Reuse)
Frame Persistence: When a function returns, its
stack frame is not cleared. The RSP simply
moves back up.
Ghost Data: If a subsequent function call allocates
a frame over the same memory area and fails to initialize its variables,
it can read or "leak" the "ghost" data left by the previous
function.
Compiler
Backstabbing (Dead Store Elimination)
The Trap: A developer might use
memset(buf, 0, size) to clear a sensitive flag
before a function returns.
DSE: If the compiler (with -O2 or
-O3) determines that buf is never accessed
again before the function returns, it may "optimize out" the
memset entirely, leaving the secret data on the
stack. Use -fno-inline or specific memory
barrier techniques to prevent this.
Format String Leaks
printf(user_input) treats user bytes as a format string.
This can leak stack/register values and, with %n, write
memory.
If win_authed(token) checks a stack/local token,
sometimes jump past the check instead of calling the function entry.
This is an offset jump. Make sure the target
instruction does not depend on skipped setup.
Canary Bypass Patterns
Direct leak: program prints past the canary because
the input overwrote its leading \x00 terminator.
Residual stack leak: another function leaves a
canary copy or secret in reused stack memory.
Recursive/retry path: trigger a path that re-enters
vulnerable code before the outer canary is checked.
Fork brute-force: child crashes do not randomize
parent canary; brute-force byte-by-byte.
Skip canary write zone: corrupt loop index,
pointer, length, or destination so writes land after the canary.
Stack canary on amd64 usually has a null low byte. Leak
reconstruction often looks like:
Despite ASLR, the relative
offset between functions and data within the same
module remains constant.
Leak a known pointer (e.g., a function address in the
GOT).
Subtract its constant offset to find the base
address.
Calculate the addresses of all other gadgets/functions.
Method 2: Partial Overwrite
(YOLO)
Memory is managed in pages (typically
0x1000 bytes). The lowest 12 bits of an address represent
the page offset and are not randomized by
ASLR.
Strategy: Overwrite only the least significant 1 or
2 bytes of the return address. This allows redirecting
execution to a different instruction within the same page (or nearby)
without knowing the randomized base. Overwriting 2 bytes usually
requires a 1/16 brute-force of the 4th nibble.
Method 3: Fork Brute-force
In a fork()-based network server, child processes
inherit the exact memory layout of the parent,
including ASLR offsets and the canary.
Strategy: Brute-force the canary or
return address byte-by-byte. If the child crashes, the
parent simply forks a new one with the same values, allowing for
infinite attempts.
Canary brute-force skeleton:
1 2 3 4 5 6 7 8 9 10 11 12 13
from pwn import *
canary = b"\x00" for i inrange(7): for guess inrange(256): io = remote("host", 1337) payload = b"A" * offset_to_canary + canary + bytes([guess]) io.send(payload) out = io.recvall(timeout=0.2) io.close() ifb"stack smashing detected"notin out andb"crash"notin out: canary += bytes([guess]) break
Method 4: ret2libc
With NX enabled, call existing libc code instead of injecting
code.
Sigreturn-oriented programming uses a fake rt_sigreturn
frame to set all registers. Useful when gadgets are scarce but you can
execute syscall with RAX = 15
(rt_sigreturn).
Avoid stack self-destruction: after ret,
RSP points just above the return address. Shellcode using
push may overwrite nearby bytes below RSP.
Place shellcode sufficiently before/after the overwritten return path or
use a large sled.
Staged Shellcode
When input length is too small, first-stage shellcode reads a larger
second stage into RWX memory or stack, then jumps there.
If the first page is made RX after input, place the patching code and
patch targets on a later still-writable page, or use a first-stage jump
over the protected region.
CET: SHSTK & IBT
Intel CET adds hardware CFI:
SHSTK (Shadow Stack): return addresses are mirrored
on a protected shadow stack. ret compares the normal stack
return address against the shadow one. A classic saved-RIP
overwrite can crash even when canary is disabled.
IBT (Indirect Branch Tracking): indirect
call/jmp targets must start with
endbr64 (f3 0f 1e fa).
notrack prefix:
notrack jmp rax can bypass IBT checks for that branch, so a
corrupted function pointer/register may jump to shellcode or arbitrary
code even when IBT is enabled.
Hint 1 This kind of communication is widely used,
especially for older I/O devices and connectors.
这种通信方式应用广泛,尤其适用于老式 I/O 设备和连接器。
Hint 2 Every message starts with a falling edge.
Have you tried PulseView? 每条信息都以下降沿开始。您试过 PulseView
吗?
Hint 3 UART, 8N1. You just need to figure out the
baud rate. UART,8N1。你只需计算出波特率。
Initial Analysis
This challenge involves decoding a UART signal captured as raw logic
levels in a CSV file. The provided code.csv contains two
columns: timestamp and logic_level.
Initial Inspection: The logic level is mostly
1 (Idle high), which is characteristic of UART.
Pulse Width Analysis: By calculating the time
between transitions, we find that the minimum pulse duration is
approximately 2.996e-05 seconds.
Baud Rate Calculation:\[\text{Baud Rate} = \frac{1}{\text{Bit Duration}}
\approx \frac{1}{2.996 \times 10^{-5}} \approx 33377 \text{
baud}\] However, looking at the sample counts, the transitions
align perfectly with the sample intervals, suggesting the data was
sampled exactly at the bit rate (1 sample per bit).
Solution
Based on Hint 3 (UART, 8N1): - Start
Bit: A falling edge (1 to 0). -
Data Bits: 8 bits following the start bit (Least
Significant Bit first). - Stop Bit: 1 bit of high level
(1).
By implementing a Python script to parse these transitions, we can
extract the byte stream:
while i < len(levels): if levels[i] == 1: # Wait for start bit i += 1 continue
if i + 9 >= len(levels): break
# 8 data bits (LSB first) byte_val = 0 for bit_idx inrange(8): if levels[i + 1 + bit_idx]: byte_val |= (1 << bit_idx) decoded.append(byte_val) i += 10# Move past start + 8 data + 1 stop bit
The decoded output reveals a Linux kernel boot log. Searching through
the log, a specific line stands out:
[ 0.037500] secretflag: 554d4153537b553452375f31355f3768335f623335372c5f72316768373f7d
Batman's new state-of-the-art AI agent has deleted all of the source
code to the Batcave license verification program! There's an old debug
version lying around, but that thing has been hit by more cosmic rays
than Superman!
Hint 1 BatAI estimates there are 3 bugs (BatAI
估计有 3 个错误)
Hint 2 Rotation rotation rotation! (旋转 旋转)
Hint 3 Something about that SBOX seems off... (SBOX
似乎有些不对劲...)
Initial Analysis
The "Batcave Bitflips" challenge involves a binary that performs a
custom hashing algorithm on a user-provided license key and compares the
result to a hardcoded 32-byte hash. If the hashes match, the binary uses
the key to "decrypt" the flag. However, as the name and hints suggest,
the binary is riddled with three "bit-flip" bugs that corrupt its
logic.
1. Analysis of main
The main function (0x169f) follows a standard license
check pattern: 1. Prompts for a 33-byte license key. 2. Calls
hash(input, buffer) to generate a 256-bit (32-byte) digest.
3. Calls verify(buffer), which compares the digest to a
global array named EXPECTED (at 0x4040). 4. If successful,
calls decrypt_flag(buffer) and prints the FLAG
global (at 0x4060).
2. Identifying the 3 Bugs
Bug 1: The Decryption Logic
(decrypt_flag) The decrypt_flag
function (0x12a6) is intended to decrypt the flag using the successful
hash. Standard encryption/decryption uses XOR, but the
decompiled code shows: - Code:FLAG[i] |= *(_BYTE *)(i % 32 + a1); - Disassembly
(0x12ec):09 c1 (OR ECX, EAX) The
bit-flip changed an XOR (31 c1) into an
OR (09 c1).
Bug 2: The Rotation Logic (rotate) In a
standard 8-bit rotation, the sum of the left and right shifts must be 8.
- Decompilation:*result = (*result >> 6) | (8 * *result); -
Analysis:8 * *result is equivalent to
*result << 3. A shift of 3 combined with a shift of 6
is not a valid rotation. - The Flip: The SIB byte in
the lea instruction at 0x1275 is 0xc5 (Scale
8). If the bit-flip occurred here, changing 0xc5 to
0x85 (Scale 4), the instruction becomes
x << 2. Combined with shr 6, this forms
a proper 2-bit rotation.
Bug 3: The SBOX Data (SBOX) The
substitute function uses a global SBOX table
(0x4080). Analysis reveals: - Duplicate: The value
0x43 appears at both index 24 and index 92. -
Missing: The value 0x44 is entirely
missing from the table. This single-bit difference (0x43 vs
0x44) in the data segment is the third bug.
Solution
While we could patch the binary to fix the bugs and attempt to
reverse the custom hash, there is a much faster path. The
main function logic implies that if we provide the
"correct" license key, the resulting hash will be equal to the
EXPECTED bytes. Since the decrypt_flag
function (if fixed) would simply XOR the FLAG bytes with
the successful hash, we can perform this operation manually.
Target Hash (EXPECTED @ 0x4040):3b 54 75 1a 24 06 af 05 77 80 47 c5 e4 83 d3 48 cb 87 30 de 1a 91 45 ab 15 c7 9b 22 04 02 2b ee
The challenge provided a single image file:
challenge.png. Initial investigation focused on gathering
basic information about the file and its metadata.
File Type: Standard PNG image (640x360).
Metadata:exiftool showed no unusual
comments or hidden fields.
Strings: Running strings did not
reveal the flag in plain text, suggesting it was encoded or hidden
within the pixel data.
Embedded Files:binwalk did not detect
any appended files (like zips or other images).
Given that the file appeared to be a clean PNG with no obvious
trailing data or metadata tricks, I suspected LSB (Least
Significant Bit) Steganography. This technique hides data by
slightly modifying the last bit of color values (Red, Green, or Blue),
which is invisible to the human eye.
Solution
To extract the hidden data, we can either use automated tools or a
custom script to parse the LSBs from each color plane.
1. Automated Approach
Using zsteg, we can quickly scan for common LSB
patterns:
# Check Red (0), Green (1), and Blue (2) planes for plane inrange(3): lsb_data = bytearray() current_byte = 0 bit_count = 0 for y inrange(height): for x inrange(width): pixel = pixels[x, y] bit = pixel[plane] & 1# Extract LSB current_byte = (current_byte << 1) | bit bit_count += 1 if bit_count == 8: lsb_data.append(current_byte) current_byte = 0 bit_count = 0
ifb"UMASS{"in lsb_data: print(f"Found flag in Plane {plane}!") # Logic to print the found string...
Running the extraction against the color planes reveals that the flag
is hidden in the Blue channel (Plane 2).
I managed to get my hands on a design files from LEGO HQ. Apparently
it is the design for the new Smart Brick v2. I want to analyze it but I
don't have the hardware to do so. Can you help me figure out what it
does??
我设法从乐高总部拿到了一份设计文件。我想对它进行分析,但我没有分析所需的硬件。你能帮我弄明白它是做什么的吗?
Hint 1: I think I have seen that file format before
on an popular open-source eCAD software but I can't remember which
one... (我好像在一款流行的开源 eCAD
软件上见过这种文件格式,但记不清是哪一款了) Hint 2:
Hmmm... there seem to be 7 inputs, I wonder what encoding uses only 7
bits? (嗯......似乎有 7 个输入,我想知道什么编码只使用 7 位?)
Hint 3: I found a great python library to interact with
this file programmatically: kiutils (我发现了一个很棒的 Python
库,可以通过编程与该文件交互:kiutils)
Initial Analysis
The challenge provides a KiCad PCB design file
(smart-brick-v2.kicad_pcb) and asks to analyze it to
discover its function. Hints suggest that the board uses a 7-bit
encoding (ASCII) and points toward the kiutils Python
library for programmatic analysis.
Opening the file or inspecting the raw text reveals it is a
KiCad 9.0 board file. Key features identified:
Inputs: 7 nets labeled /IN0 through
/IN6. This confirms the hint about 7-bit encoding
(ASCII).
Outputs: 19 LEDs (D1–D19)
driven by 19 MOSFETs (Q1–Q19).
Logic: A large array of 74LS
series discrete logic gates (AND, NAND, OR, NOR, XOR,
NOT).
The circuit is a combinational logic "decoder" where each LED
represents a character in the flag. An LED will light up if the 7-bit
input matches a specific character programmed into the logic gates for
that stage.
Solution
To solve this without physical hardware or a manual schematic trace,
we can automate the logic extraction and simulation using Python.
1. Technical Approach
Step 1: Parsing the PCB Using the
kiutils library, we extract all footprints, their values
(e.g., 74LS00), and the nets connected to their pins.
We build a dependency graph where each net's value is determined by
the boolean operation of its input nets.
Step 3: Simulation Since there are only 128 possible
values for a 7-bit input (ASCII 0–127), we can brute-force the inputs
for each of the 19 output stages. For each character c from
0 to 127, we propagate the values through the logic gate graph and check
which MOSFET gates (Q1–Q19) are pulled
HIGH.
2. Execution
The simulation reveals that each LED corresponds to exactly one ASCII
character:
# Map components by reference components = {} for fp in board.footprints: ref = fp.properties.get('Reference', '') val = fp.properties.get('Value', '')
pins = {} for pad in fp.pads: if pad.net: pins[pad.number] = pad.net.name
for ref, comp in components.items(): val = comp['value'] if val in gate_defs: for inputs, output in gate_defs[val]: if output in comp['pins']: out_net = comp['pins'][output] in_nets = [comp['pins'][i] for i in inputs if i in comp['pins']] iflen(in_nets) == len(inputs): op = val[4:] # 00, 02, etc. net_logic[out_net] = (op, in_nets)
# MOSFET gates driving LEDs led_nets = [] for i inrange(1, 20): ref = f'Q{i}' if ref in components: if'1'in components[ref]['pins']: led_nets.append(components[ref]['pins']['1'])
# Simulation function defsimulate(inputs_bits): vals = {f'/IN{i}': inputs_bits[i] for i inrange(7)} vals['GND'] = False vals['+5V'] = True
memo = {} defget_val(net): if net in vals: return vals[net] if net in memo: return memo[net] if net notin net_logic: returnFalse
op, ins = net_logic[net] in_vals = [get_val(i) for i in ins]
if op == '00': returnnot (in_vals[0] and in_vals[1]) elif op == '02': returnnot (in_vals[0] or in_vals[1]) elif op == '04': returnnot in_vals[0] elif op == '08': return in_vals[0] and in_vals[1] elif op == '20': returnnotall(in_vals) elif op == '21': returnall(in_vals) elif op == '27': returnnotany(in_vals) elif op == '32': returnany(in_vals) elif op == '86': return in_vals[0] ^ in_vals[1] else: returnFalse
memo[net] = res return res
return [get_val(ln) for ln in led_nets]
# Brute force 7-bit ASCII for i inrange(19): print(f"LED {i+1}: ", end='') for code inrange(128): bits = [(code >> j) & 1 == 1for j inrange(7)] outputs = simulate(bits) if outputs[i]: print(f"'{chr(code)}' (0x{code:02x})", end=' ') print()
"The Block City Times" is a complex web application built with Spring
Boot, involving multiple internal services (editorial,
report-runner) and a Puppeteer-based bot. The challenge
requires chaining several vulnerabilities to leak a sensitive
FLAG cookie from an internal diagnostic service.
Solution
The attack follows a multi-stage exploit chain:
Arbitrary File Upload & XSS: The
/submit endpoint allows uploading "story" files. Although
it checks the Content-Type, this is easily bypassed. Files
are saved with their original extension and served via
/files/{filename}. By uploading an .html file
with Content-Type: text/plain, we can achieve stored
XSS.
Administrative Bot Triggering: The
editorial service automatically reviews every submission by
visiting the uploaded file as an administrator. This allows our XSS
payload to execute with administrative privileges.
Actuator Abuse: The application exposes Spring
Boot Actuator endpoints. Via XSS, the editorial bot can be
forced to modify application properties at runtime:
Disable production enforcement: POST /actuator/env with
app.enforce-production=false.
Switch to dev mode: POST /admin/switch?config=dev.
SSRF & Cookie Leakage: The
report-runner service logs in as an admin, sets a
FLAG cookie, and visits a user-specified API endpoint.
While it checks if the endpoint starts with /api/, this can
be bypassed with path traversal (e.g.,
/api/../files/exploit.html).
After uploading the exploit (bypassing the extension check by
modifying the filename to .html in the multipart request),
the editorial bot triggers the reconfiguration and the
report-runner. The flag is then leaked to the tags of
Article 1.
Flag
UMASS{A_mAn_h3s_f@l13N_1N_tH3_r1v3r}
+++
SYSTEM STATUS: ACTIVEENCRYPTED SECTOR 7PRTS_TERMINAL_V2.0PROTOCOL: 0x2AENCRYPTED DATA STREAMSYSTEM: ONLINE