Post

RE101 — Reverse Engineering Fundamentals Walkthrough

Analyze diverse file types including binaries, obfuscated scripts, and corrupted archives using reverse engineering techniques to extract hidden flags.

RE101 — Reverse Engineering Fundamentals Walkthrough

Overview

  
PlatformCyberDefenders
CategoryMalware Analysis
DifficultyMedium
FocusBinary Analysis · Script Deobfuscation · File Format Repair · Custom Encryption
Lab LinkRE101

The RE101 challenge is an introductory reverse engineering lab designed to teach fundamental analysis techniques across multiple file types. The challenge presents six distinct artifacts — each requiring a different approach:

  • Base64 encoding hidden in binary
  • JSFuck obfuscated JavaScript
  • Brainfuck esoteric programming language
  • Corrupted ZIP file repair
  • Stack-string obfuscation in binaries
  • Custom position-dependent XOR encryption

This challenge is not about advanced malware techniques. Instead, it emphasizes foundational skills that every reverse engineer must master: recognizing encoding schemes, understanding scripting languages, working with file format specifications, and tracing execution flow in binaries.


Objective

The goal of this lab is to extract hidden flags from six different files using:

  • Static string extraction
  • Script execution in safe environments
  • Esoteric language recognition
  • File format specification knowledge
  • Disassembly and stack string reconstruction
  • Encryption algorithm reversal

By the end of the investigation, we will demonstrate:

  • How to find encoded data in binaries using strings
  • How JSFuck uses only 6 characters to encode JavaScript
  • How to recognize and execute Brainfuck code
  • How to repair corrupted archive headers
  • How stack strings evade static analysis
  • How to reverse position-dependent XOR encryption

Tools Used

Throughout this analysis, the following tools are used:

  • strings, file, xxd — Basic file analysis
  • base64 — Decoding
  • node — JavaScript runtime
  • brainfuck — Esoteric language interpreter
  • hexedit / HxD — Hex editing
  • IDA Pro / Ghidra — Disassembly and decompilation
  • Python — Custom decryption scripts

Q1 — Base64 Encoding in Binary

Question

File: MALWARE000 - I’ve used this new encryption I heard about online for my warez; I bet you can’t extract the flag!


Initial Analysis

The first step with any unknown binary is simple: run strings.

1
strings malware000

This reveals interesting output buried in the binary:

1
2
ZmxhZzwwb3BzX2lfdXNlZF8xMzM3X2I2NF9lbmNyeXB0aW9uPgo=
If you see this message...

The string ending with = is immediately recognizable as Base64 padding.


Decoding the Flag

Standard Base64 decoding works perfectly:

1
base64 -d <<< "ZmxhZzwwb3BzX2lfdXNlZF8xMzM3X2I2NF9lbmNyeXB0aW9uPgo="

The decoded flag is:

1
0ops_i_used_1337_b64_encryption

Q2 — JSFuck Obfuscation

Question

File: Just some JS - Check out what I can do!


Initial Analysis

Opening the JavaScript file reveals something unusual — code composed entirely of six characters:

1
2
3
4
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]
+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])
[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!!
[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+...

This is JSFuck — an esoteric programming style that uses only these 6 characters: []()!+


Understanding JSFuck

JSFuck exploits JavaScript’s type coercion to construct any code from just six symbols:

ExpressionResult
![]false
!![]true
[][[]]undefined
+[]0
+!+[]1
!+[]+!+[]2

From these primitives, JSFuck can construct strings, access properties, and execute arbitrary JavaScript.


Extracting the Flag

JSFuck is valid JavaScript — just execute it:

1
node just_some_js

The output reveals:

1
what_a_cheeky_language!1!

Q3 — Brainfuck Esoteric Language

Question

File: This is not JS - I’m tired of Javascript. Luckily, I found the grand-daddy of that lame last language!


Initial Analysis

The filename is deliberately misleading. Let’s check what we’re dealing with:

1
file this_is_not_js

Output:

1
ASCII text, with very long lines (320)

Viewing the content reveals:

1
2
3
4
5
++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>>++.++++++.-----------.++++++.
<----------.++++++++++++++++++.>++++++++.++++++++.<+++++++++++++++++.------
-----.------------.+.++++++++++.++++++++++++.++++++++++.>----.<----------.>
---.++.---.<++++++++.>+++.<------.>-----..----.+++++.<++++++.<++++++++++++++
++++++++++++++.

This is Brainfuck — an esoteric programming language that uses only 8 commands: +-<>[].,


Understanding Brainfuck

CommandDescription
+Increment current cell
-Decrement current cell
>Move pointer right
<Move pointer left
[Jump past ] if cell is zero
]Jump back to [ if cell is non-zero
.Output current cell as ASCII
,Read input into current cell

Extracting the Flag

Execute the Brainfuck code:

1
brainfuck this_is_not_js

Output:

1
Now_THIS_is_programming

Q4 — ZIP File Header Repair

Question

File: Unzip Me - I zipped flag.txt and encrypted it with the password “password”, but I think the header got messed up… You can have the flag if you fix the file


Initial Analysis

Attempting to open the ZIP file fails:

1
2
3
4
5
6
file file.zip_broken
# Output: data (not recognized as ZIP)

unzip file.zip_broken
# Error: filename too long--truncating.
#        flag.txtUT^I:  bad extra field length (local)

The archive is corrupted. Let’s examine it with a hex dump:

1
xxd file.zip_broken | head -n 20

Output:

1
2
3
00000000: 504b 0304 0a00 0900 0000 5aab 8e50 5622  PK........Z..PV"
00000010: 6795 2000 0000 1400 0000 5858 1c00 666c  g. .......XX..fl
00000020: 6167 2e74 7874 5554 0900 03dc 6296 5edc  ag.txtUT....b.^.

ZIP File Structure

Understanding the Local File Header (LFH):

OffsetSizeDescription
0x004Signature (50 4B 03 04)
0x042Version needed
0x062General purpose bit flag
0x082Compression method
0x0A4Last mod time/date
0x0E4CRC-32
0x124Compressed size
0x164Uncompressed size
0x1A2Filename length ← CORRUPTED!
0x1C2Extra field length

Identifying the Bug

At offset 0x1A-0x1B, we have: 58 58

In little-endian, this equals: 0x5858 = 22,616 bytes for filename length!

But “flag.txt” is only 8 bytes: 66 6C 61 67 2E 74 78 74


Repairing the Header

Using a hex editor:

1
hexedit file.zip_broken

Navigate to offset 0x1A and change:

  • 58 5808 00

This sets the filename length to 8 bytes (little-endian: 0x0008).


Extracting the Flag

After repair:

1
2
3
4
5
zip -FF file.zip_broken --out file_fixed.zip
unzip file_fixed.zip
# Enter password: (empty - just press Enter)

cat flag.txt

The extracted flag is:

1
R3ad_th3_spec

Q5 — Stack String Obfuscation

Question

File: MALWARE101 - Apparently, my encryption isn’t so secure. I’ve got a new way of hiding my flags!


Initial Analysis

Running basic analysis reveals nothing useful:

1
2
3
4
5
6
7
8
file malware101
# Output: ELF 64-bit LSB executable, x86-64, dynamically linked, stripped

strings malware101
# Output: Nothing to see here....

./malware101
# Output: Nothing to see here....

Dynamic analysis with strace and ltrace also shows only the decoy message.


The Decompiler Trap

Loading into IDA and using the decompiler (F5) shows:

1
2
3
4
5
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  printf("Nothing to see here....\n");
  return 0;
}

This looks completely innocent! But wait — the decompiler optimizes away dead code.

The flag is constructed on the stack but never used, so the decompiler removes it entirely.


The Assembly Tells the Truth

Switching to assembly view reveals what the decompiler hid:

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
main proc near
    push    rbp
    mov     rbp, rsp
    sub     rsp, 1C0h
    mov     rdi, offset format ; "Nothing to see here....\n"
    mov     [rbp+var_4], 0
    mov     [rbp+var_1A0], 67h ; 'g'
    mov     [rbp+var_1A9], 61h ; 'a'
    mov     [rbp+var_1A3], 72h ; 'r'
    mov     [rbp+var_1A2], 69h ; 'i'
    mov     [rbp+var_1A1], 6Eh ; 'n'
    mov     [rbp+var_1AB], 73h ; 's'
    mov     [rbp+var_199], 3Eh ; '>'
    mov     [rbp+var_198], 0
    mov     [rbp+var_1A7], 6Bh ; 'k'
    mov     [rbp+var_19F], 73h ; 's'
    mov     [rbp+var_1AE], 61h ; 'a'
    mov     [rbp+var_1AA], 54h ; 'T'
    mov     [rbp+var_1A6], 5Fh ; '_'
    mov     [rbp+var_19E], 5Fh ; '_'
    mov     [rbp+var_1AF], 6Ch ; 'l'
    mov     [rbp+var_1B0], 66h ; 'f'
    mov     [rbp+var_1A5], 73h ; 's'
    mov     [rbp+var_1A4], 74h ; 't'
    mov     [rbp+var_19D], 4Ch ; 'L'
    mov     [rbp+var_1A8], 43h ; 'C'
    mov     [rbp+var_19B], 41h ; 'A'
    mov     [rbp+var_19A], 4Fh ; 'O'
    mov     [rbp+var_1AD], 67h ; 'g'
    mov     [rbp+var_1AC], 3Ch ; '<'
    mov     [rbp+var_19C], 4Dh ; 'M'
    mov     al, 0
    call    _printf
main endp

The characters are written to the stack in scrambled order — an additional obfuscation layer to confuse manual analysis!


Reconstruction

Sort by stack offset and extract the ASCII values:

OffsetHexChar
-0x1B00x66f
-0x1AF0x6Cl
-0x1AE0x61a
-0x1AD0x67g
-0x1AC0x3C<
-0x1AB0x73s
-0x1AA0x54T
-0x1A90x61a
-0x1A80x43C
-0x1A70x6Bk
-0x1A60x5F_
-0x1A50x73s
-0x1A40x74t
-0x1A30x72r
-0x1A20x69i
-0x1A10x6En
-0x1A00x67g
-0x19F0x73s
-0x19E0x5F_
-0x19D0x4CL
-0x19C0x4DM
-0x19B0x41A
-0x19A0x4FO
-0x1990x3E>

Reconstructed flag:

1
sTaCk_strings_LMAO

Extraction Script

1
2
3
4
5
6
7
8
9
10
11
12
stack_data = [
    (-0x1B0, 0x66), (-0x1AF, 0x6C), (-0x1AE, 0x61), (-0x1AD, 0x67),
    (-0x1AC, 0x3C), (-0x1AB, 0x73), (-0x1AA, 0x54), (-0x1A9, 0x61),
    (-0x1A8, 0x43), (-0x1A7, 0x6B), (-0x1A6, 0x5F), (-0x1A5, 0x73),
    (-0x1A4, 0x74), (-0x1A3, 0x72), (-0x1A2, 0x69), (-0x1A1, 0x6E),
    (-0x1A0, 0x67), (-0x19F, 0x73), (-0x19E, 0x5F), (-0x19D, 0x4C),
    (-0x19C, 0x4D), (-0x19B, 0x41), (-0x19A, 0x4F), (-0x199, 0x3E),
]

stack_data.sort(key=lambda x: x[0])
flag = ''.join(chr(val) for _, val in stack_data)
print(flag)  # flag<sTaCk_strings_LMAO>

Q6 — Custom Position-Dependent XOR Encryption

Question

File: MALWARE201 - Ugh… I guess I’ll just roll my own encryption. I’m not too good at math, but it looks good to me!


Initial Analysis

Running the binary provides helpful output:

1
./malware201

Output:

1
2
3
The encrypted flag is: "6d7861 6cdd7e657e476a4fccf7ca736855425 3dcd7d46becd bd2e11c6dded1c2"

encrypt("my message", 10) == "6f7d61676dd7ed676fdb617ddb7b71"

The binary shows us:

  1. The encrypted flag (32 bytes)
  2. A sample encryption of “my message”

Disassembly Analysis

Examining the encryption function in IDA reveals the algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned char* encrypt(const char* plaintext, size_t length) {
    unsigned char* output = calloc(length, 1);
    
    for (size_t i = 0; i < length; i++) {
        // Position-dependent key
        unsigned char key = ((i % 0xFF) | 0xA0);
        
        // Transform plaintext: double and set LSB
        unsigned char transformed = (2 * plaintext[i]) | 1;
        
        // XOR encryption
        output[i] = key ^ transformed;
    }
    
    return output;
}

Encryption Algorithm Breakdown

Formula:

1
encrypted[i] = ((i % 0xFF) | 0xA0) ^ ((2 * plaintext[i]) | 1)

Components:

  1. Position-dependent key: (i % 0xFF) | 0xA0
    • Creates unique key per position
    • OR with 0xA0 ensures bits 7 and 5 are set
  2. Plaintext transformation: (2 * plaintext[i]) | 1
    • Doubles the byte (left shift)
    • Sets LSB to 1 (makes it odd)
  3. XOR: Combines key and transformed plaintext

Decryption Process

To reverse the encryption:

Step 1: XOR encrypted byte with position key

1
temp = encrypted[i] ^ ((i % 0xFF) | 0xA0)

Step 2: Remove LSB and reverse doubling

1
plaintext[i] = (temp ^ 1) / 2

Encrypted Flag Data

From .rodata section (32 bytes):

1
2
6D 78 61 6C DD 7E 65 7E 47 6A 4F CC F7 CA 73 68
55 42 53 DC D7 D4 6B EC DB D2 E1 1C 6D DE D1 C2

Decryption Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
encrypted_flag = [
    0x6D, 0x78, 0x61, 0x6C, 0xDD, 0x7E, 0x65, 0x7E,
    0x47, 0x6A, 0x4F, 0xCC, 0xF7, 0xCA, 0x73, 0x68,
    0x55, 0x42, 0x53, 0xDC, 0xD7, 0xD4, 0x6B, 0xEC,
    0xDB, 0xD2, 0xE1, 0x1C, 0x6D, 0xDE, 0xD1, 0xC2
]

def decrypt_byte(encrypted_byte, index):
    key = (index % 0xFF) | 0xA0
    temp = key ^ encrypted_byte
    plaintext = (temp ^ 1) // 2
    return plaintext

flag = ''.join(chr(decrypt_byte(b, i)) for i, b in enumerate(encrypted_flag))
print(flag)

Verification

Verifying position 0 manually:

Encryption of ‘f’ (0x66) at position 0:

1
2
3
key = (0 % 0xFF) | 0xA0 = 0xA0
transformed = (2 * 0x66) | 1 = 0xCC | 1 = 0xCD
encrypted = 0xA0 ^ 0xCD = 0x6D ✓

Decryption of 0x6D at position 0:

1
2
3
key = 0xA0
temp = 0x6D ^ 0xA0 = 0xCD
plaintext = (0xCD ^ 1) / 2 = 0xCC / 2 = 0x66 = 'f' ✓

The decrypted flag is:

1
malwar3-3ncryp710n-15-Sh17

Conclusion

The RE101 lab covers foundational reverse engineering concepts that appear repeatedly in real-world analysis:

QuestionTechniqueFlag
Q1Static strings + Base640ops_i_used_1337_b64_encryption
Q2JSFuck executionwhat_a_cheeky_language!1!
Q3Brainfuck esoteric languageNow_THIS_is_programming
Q4ZIP header repairR3ad_th3_spec
Q5Stack string reconstructionsTaCk_strings_LMAO
Q6Position-dependent XOR reversalmalwar3-3ncryp710n-15-Sh17

Skills Demonstrated

Static Analysis:

  • String extraction with strings
  • Hex dump interpretation
  • Disassembly reading
  • File format understanding

Dynamic Analysis:

  • Safe code execution
  • System/library call tracing

Reverse Engineering Concepts:

  • Encoding vs Encryption vs Obfuscation
  • Esoteric programming languages
  • Stack-based string construction
  • Position-dependent encryption
  • XOR properties and reversibility

These fundamentals form the building blocks for advanced malware analysis. Mastering them ensures you can handle obfuscation and encoding schemes before tackling more complex evasion techniques.

see yaa again <3

This post is licensed under CC BY 4.0 by the author.