Challenge: Solution to “Memgraph Capture The Flag”

Flag icon on grey background

The “Memgraph Capture the Flag” challenge invites you to learn and practice memory debugging and symbolication with command line tools. If you haven’t yet attempted the challenge or otherwise don’t want to be spoiled on the necessary steps to complete it, we recommend returning to the original challenge page. Otherwise, read on!

Challenge: Memgraph Capture the Flag

The challenge begins with the following: “One of our engineers has hidden a memory easter egg in our secret app. We're trying to track it down but all we know is that it has format flag_<unknown_string_here>@WWDC. You’ll have to use the command line tools offered by macOS to investigate the memory issue, recover missing symbols, and and capture the rogue flag.”

The following solution is one of the possible paths to capture the flag. To start, the challenge article supplies you with a memgraph file and a dSYM, along with the following hint: “Memgraph is a special binary plist. What can you find in its properties?”

To look at the properties of a Memgraph, use plutil. In the output, you’ll find more hints:

$ plutil -p secret.memgraph
"hint" => "the flag is hiding in a memory leak"
"one_more_hint" => "you might also want to explore the 'symbols' in the dSYM”

The hint invites you to investigate the memory leak, while one_more_hint directly encourages you to use the symbols CLI tool. In the WWDC21 session “Symbolication: Beyond the basics,” engineer Alejandro Lucena mentions that it’s a good idea to specify the architecture with this tool. As such, you can use the memgraph to learn the “secret” app’s architecture.

You can try heap, leaks or vmmap. When used with a memgraph, the first few lines are the same in the output of each of these tools. This is how you learn which architecture the “secret" process uses: Code Type: X86-64.

As detailed in the WWDC21 session “Detect and diagnose memory problems,” you can use the vmmap command against memgraph files in addition to targeting the running process. Running it agaisnt this memgraph provides the following information:

$ vmmap -summary secret.memgraph 
Process:         secret [2901]
Path:            /Users/*/secret
Load Address:    0x10d264000
Identifier:      secret
Version:         0
Code Type:       X86-64
Platform:        macOS
Parent Process:  zsh [1438]

Now you’re ready to use the symbols command. Use the -noSources option to restrict the output to symbol names so you have less output to look through. Hidden amidst the symbols in the “secret” dSYM, you’ll find another breadcrumb towards the solution:

$ symbols -arch x86_64 -noSources secret.dSYM [macOS Monterey+, Xcode 13+]
$ symbols -arch x86_64 -noSources secret.dSYM/Contents/Resources/DWARF/secret [macOS before Monterey, Xcode before 13]

The leaked memory in this memgraph is definitely starting to sound interesting — it’s probably a specific leaked address. Let’s see which secret addresses the hint is referring to. To determine if the “secret” app was leaking memory, you can check the memgraph for leaks with the leaks command line tool.

You’ll want to pay attention to this portion of the output:

$ leaks secret.memgraph
6   hint                               0x7fff204edf3d how would you translate secret Addresses TO Symbols? + 1
5   secret                                0x10d267ee8 0x10d264000 + 16104
4   secret                                0x10d267dc5 0x10d264000 + 15813
3   secret                                0x10d267ccf 0x10d264000 + 15567
2           0x7fff2059576f __CFArrayCreateInit + 190
1           0x7fff2054df07 _CFRuntimeCreateInstance + 587
0   libsystem_malloc.dylib             0x7fff20314071 _malloc_zone_malloc + 242 

In this output, you’ll spot five leaks, all of which originated from the same place in code. They are united by the same call stack backtrace each time a CFArray was allocated, but never freed. You’ll find three secret addresses here — 0x10d267ee8, 0x10d267dc5 and 0x10d267ccf — along with a new hint sporting some interesting capitalization. This clue hints that you should try and use atos tool to symbolicate the secret addresses.

To call atos, you need several components: The DWARF binary in the dSYM, architecture, and addresses to symbolicate. You’re missing the load address, however, and can find it for the “secret” binary image within the call stack of the leaks next to all three secret addresses: 0x10d264000. You can also find it in the the process description and list of binary images portions of the leaks output:

$ leaks secret.memgraph
Process:         secret [2901]
Path:            /Users/*/secret
Load Address:    0x10d264000
Binary Images:
       0x10d264000 -        0x10d267ff7 +secret (0) <6676D338-8C26-3019-B919-88C1CB4AA324> /Users//secret

Now, you can use atos to translate the secret addresses to symbols:

$ atos -o secret.dSYM/Contents/Resources/DWARF/secret -arch x86_64 -l 0x10d264000 0x10d267ee8 0x10d267dc5 0x10d267ccf
main (in secret) (main.m:226)
very_nice_function (in secret) (main.m:205)
good_job_but_the_flag_is_inlined (in secret) (main.m:186)

You’re getting close: The flag is inlined, so you need to add the -i command line option while calling atos to display the inlined functions too:

$ atos -o secret.dSYM/Contents/Resources/DWARF/secret -arch x86_64 -l 0x10d264000 0x10d267ee8 0x10d267dc5 0x10d267ccf -i
main (in secret) (main.m:413)

very_nice_function (in secret) (main.m:392)

IGZsYWdfbWVNMHJ5VDBPTHNEZWJ1R0cxbmdQcjBAV1dEQyAg (in secret) (main.m:86)
whats_wrong_with_encoding (in secret) (main.m:204)
omg_you_found_it (in secret) (main.m:333)
good_job_but_the_flag_is_inlined (in secret) (main.m:373)

Closer still! Run this command and you’ll get a string of gibberish that looks like it might be base64-encoded. Run that decode and you get:

$ echo "IGZsYWdfbWVNMHJ5VDBPTHNEZWJ1R0cxbmdQcjBAV1dEQyAg" | base64 -d
 flag_meM0ryT0OLsDebuGG1ngPr0@WWDC  %

As a note: Make sure you also pass the -arch flag to atos, because atos defaults to the architecture you're actively using. For example, if you run this command from x86_64 (either on x86 hardware or in Rosetta 2), you won't see any change. But if you run the tool from an Apple Silicon machine without the -arch flag, you'll get strings delivered for the wrong architecture:

IOKdmuKWiOKVkOKVkOKWiOKdmiAg4p2a4paI4pWQ4pWQ4paI4p2a (in secret) (main.m:396)
IOKWhyDiloUg4paIIOKWhSDilocg4paCIOKWgyDiloEg4paB (in secret) (main.m:381)

Convert those from their base64 encoding, and you'll get some very pretty ASCII art weights.

❚█══█❚ ❚█══█❚

▇ ▅ █ ▅ ▇ ▂ ▃ ▁ ▁

While these might be helpful for filling the fitness rings on your Apple Watch, you need the -arch x86_64 parameter to capture this particular flag.

One last tip: The secret.memgraph was generated when the “secret” process was running with MallocStackLogging enabled; this allows you to see the call stack of the leak. Don’t forget to enable MallocStackLogging for your own app when generating memgraphs. This can be done in the scheme settings in Xcode (Run > Diagnostics > MallocStackLogging = Live Allocations Only) or with the environment variable when launching from a terminal:

`$ MallocStackLogging=lite <command>`

This is just one path you can follow to get the flag using the command line tools built into macOS for memory debugging and symbolication. Check out the full repository of Coding and Design Challenges for other fun coding and design explorations, or dive deeper into debugging with our most recent WWDC21 videos.


  • WWDC21

Symbolication: Beyond the basics

  • WWDC21

Detect and diagnose memory issues

Explore more coding & design challenges