picoCTF 2024 - FactCheck Walkthrough
1. Preparation
Before we begin the walkthrough, please ensure to have the following:
- The binary challenge
- Ghidra or IDA for static analysis
- GDB for dynamic analysis
2. Static Analysis in Ghidra
To objectively narrow down the scope, we can cheat a little by searching for “picoCTF” strings in the binary.

Flag Substring Search
From the figure above, Ghidra suggests that the string is found at 0x00102005.
Clicking on it leads us to where the string was defined. However, you will notice that the flag is incomplete, and it is referenced in main().

Flag Suffix Definition
Again, click on the reference address, and it will bring us to main().

Partial Flag Reference in main()
Looking at the decompiled code, the first part of the flag is assigned to the local_248, which is a pointer to memory in heap, where the constructed string will be placed. Subsequently, other strings are defined as well. (See Listing 1)
undefined8 main(void) {
...
std::__cxx11::string::string(local_248,"picoCTF{wELF_d0N3_mate_",&local_249);
...
std::__cxx11::string::string(local_228,"0",&local_249)
...
std::__cxx11::string::string(local_208,"5",&local_249);
...
}
After initializing a bunch of strings, it proceeds to append the other defined strings to the flag through a bunch of checks.
undefined8 main(void) {
...
if (*pcVar2 < 'B') {
std::__cxx11::string::operator+=(local_248,local_c8);
}
pcVar2 = (char *)std::__cxx11::string::operator[]((ulong)local_a8);
if (*pcVar2 != 'A') {
std::__cxx11::string::operator+=(local_248,local_68);
}
...
if ((int)cVar1 - (int)*pcVar2 == 3) {
std::__cxx11::string::operator+=(local_248,local_1c8);
}
...
// Flag's ending tag is appended
std::__cxx11::string::operator+=(local_248,'}');
...
// Truncated: Calls to string deconstructor
}
There are a few things we need to figure out before proceeding to dynamic analysis with GDB:
- What is the base address of main()?
0x00101289 - Which instruction appends the flag’s ending tag?
0010185b e8 a0 f8 CALL <EXTERNAL>::std::__cxx11::string::operator+= ff ff - What is that instruction’s offset from main()?
>>> hex(0x0010185b - 0x00101289) 0x5d2
3. Dynamic Analysis in GDB
3.1 Method 1 - Stepping Over Last String Concat
Start GDB by loading the binary and break on main() + 0x5d2, before running the binary.
$ gdb ./bin
(gdb) set pag off
(gdb) set disassembly-flavor intel
(gdb) set output-radix 16
(gdb) b *(main + 0x5d2)
(gdb) run
Once our breakpoint is hit, ensure that we really are at the correct instruction by listing 3 instructions before RIP and 6 instructions after RIP.
$pc is an alias for $ripBreakpoint 1, 0x000055555555585b in main ()
(gdb) x/10i $pc-15
0x55555555584c <main+1475>: lea rax,[rbp-0x240]
0x555555555853 <main+1482>: mov esi,0x7d
0x555555555858 <main+1487>: mov rdi,rax
=> 0x55555555585b <main+1490>: call 0x555555555100 <_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEpLEc@plt>
0x555555555860 <main+1495>: mov ebx,0x0
0x555555555865 <main+1500>: lea rax,[rbp-0x40]
0x555555555869 <main+1504>: mov rdi,rax
0x55555555586c <main+1507>: call 0x5555555550f0 <_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev@plt>
0x555555555871 <main+1512>: lea rax,[rbp-0x60]
0x555555555875 <main+1516>: mov rdi,rax
Looking at the listing above, the flag pointer is stored in RDI just before the call. We can verify this by examining the string that is pointed to by the address in RDI.
(gdb) x/gx $rdi
0x7fffffffd530: 0x000055555556b2d0
(gdb) x/s 0x000055555556b2d0
0x55555556b2d0: "picoCTF{wELF_d0N3_mate_e9da2c0e"
# For convenience we store this address into a variable
(gdb) set $flag = {unsigned long long}$rdi
For completeness, step over this call and print the flag again.
(gdb) ni
(gdb) x/s $flag
0x55555556b2d0: "picoCTF{wELF_d0N3_mate_e9da2c0e}"
3.2 Method 2 - Lazy Heap Memory Search
If you are rather lazy and just want to get the flag before it is freed, you can set a breakpoint at the ret instruction. This happens to be at main+0x9ca.
(gdb) b *(main + 1504)
(gdb) run
When the breakpoint hits, identify where the start and end of the heap is.
(gdb) info proc mappings
...
0x0000555555559000 0x000055555557a000 0x21000 0x0 rw-p [heap]
...
(gdb) set $heap_start = 0x0000555555559000
(gdb) set $heap_end = 0x000055555557a000 - 1
We are now able to search through the heap for the substring “pico” to get its address.
(gdb) find /b $heap_start, $heap_end, 'p', 'i', 'c', 'o'
0x55555556b2d0
1 pattern found.
As only 1 pattern is found, this becomes straightforward by examining the buffer stored in that address.
(gdb) x/s 0x55555556b2d0
0x55555556b2d0: "picoCTF{wELF_d0N3_mate_e9da2c0e}"