← Back to Writeups
CUSTOMEasyReversing

list (RE4_list)

XESXOR9/2/20263 min read
#reversing#custom#easy#even_index_extraction#linked_list_pointer_walk#static_elf_parsing#string_reversal#vaddr_to_offset_mapping

list (RE4_list)

Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: even_index_extraction, linked_list_pointer_walk, static_elf_parsing, string_reversal, vaddr_to_offset_mapping

Summary

Walkthrough of the CUSTOM Reversing list (RE4_list). Task: 32-bit non-stripped ELF builds a linked list from 47 global node structs in .data and only prints an XOR checksum of the chars, never the flag. Solution: parse ELF section headers to map vaddr->

Description

Evil programmer tries to hide flag in global variables. Prove you're smart, find the flag!

English summary: We are given a 32-bit ELF (list.elf, Intel 80386, dynamically linked, not stripped). The program runs and prints only:

XOR of all flags chars is <number>

It never reveals the flag. The flag is stored across a linked list constructed entirely from global variables in the .data section. The goal is to reconstruct the list and read the flag out of it. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

.data contains 47 global node objects named glob0..glob46, each 12 bytes (0xc). The node struct layout is:

c
struct node {
    struct node *next;   // +0  (4 bytes)
    int          index;  // +4  (4 bytes)
    char         c;      // +8  (1 byte, then 3 bytes padding)
};

main starts at the head node glob46 (vaddr 0x804a244) and walks the next pointer chain for 0x2f (47) iterations.

The disassembled main loop, paraphrased:

c
node = glob46;            // head
accum = 0;
for (i = 0; i <= 0x2e; i++) {
    if ((i & 1) == 0)                 // EVEN i only
        accum ^= (signed char)node->c;
    node = node->next;
}
printf("XOR of all flags chars is %d\n", accum);

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Two important observations:

  1. The printed XOR checksum is a red herring — the actual flag data is the individual characters stored in the nodes, not the XOR.
  2. The loop XORs only even-index nodes. That is the hint: the even walk-positions hold the real flag characters, while the odd positions are decoy filler (mostly the letter l).

Walking the next chain from the head yields 47 characters in this order:

l5o3o1cc__eerraa__ssttccuurltlsl_lslil_lgla?lxf

Splitting out the even walk-positions gives: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

looc_era_stcurts_si_galf

The list head is the last character of the flag, so the chain is stored backwards. Reversing the even-position string spells the flag:

REDACTED

Solution

Parse the ELF32 section headers to build a virtual-address → file-offset map (needed because the next pointers are virtual addresses), then walk the chain from the head node glob46 at 0x804a244. Collect each node's char field, keep the even walk-positions, and reverse.

python
#!/usr/bin/env python3
import struct
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

data = open('list.elf', 'rb').read()

# --- parse ELF32 section headers ---
e_shoff     = struct.unpack('<I', data[0x20:0x24])[0]
e_shentsize = struct.unpack('<H', data[0x2e:0x30])[0]
e_shnum     = struct.unpack('<H', data[0x30:0x32])[0]

secs = []
for i in range(e_shnum):
    off = e_shoff + i * e_shentsize
    name, typ, flags, addr, offset, size = struct.unpack('<IIIIII', data[off:off+24])
    secs.append((addr, offset, size))

def v2o(v):
    """map a virtual address to a file offset using loadable sections"""
    for addr, offset, size in secs:
        if addr != 0 and addr <= v < addr + size:
            return offset + (v - addr)
    return None
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# --- walk the linked list from the head node glob46 ---
cur = 0x804a244            # head node (glob46)
chars = []
for _ in range(47):        # 0x2f iterations
    o   = v2o(cur)
    nxt = struct.unpack('<I', data[o:o+4])[0]   # node->next  @ +0
    c   = data[o + 8]                           # node->c     @ +8
    chars.append(chr(c))
    cur = nxt

walk = ''.join(chars)
even = ''.join(chars[i] for i in range(47) if i % 2 == 0)

print('walk        :', walk)
print('even        :', even)
print('flag        :', even[::-1])

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Output:

walk        : l5o3o1cc__eerraa__ssttccuurltlsl_lslil_lgla?lxf
even        : looc_era_stcurts_si_galf
flag        : REDACTED

The flag was submitted via POST flag; the server accepted both the bare value and the flag{...}-wrapped form. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering even_index_extraction, linked_list_pointer_walk, static_elf_parsing, string_reversal, vaddr_to_offset_mapping was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-06-15 • CUSTOM • list (RE4_list) • Difficulty: Easy