Skip to content

sm-winter | 12/01/25

Description

Welcome to Super Memory World (Winter Edition)! Each level has 2 flags - one for the normies, and one for the l33t. Can you get them all? I hear there may even be another one, but I don't see any evidence of that...

To follow along with the challenge's full code, download the source here. Line numbers are set to match the source files.

Flag 1

Observations

To start, we're given a C source file for the first game level. To play the game, we can SSH into a CTF League server that only allows us to execute the compiled code. It's unlikely that we're supposed to break this limitation to, say, use GDB to jump to the flag printing code. Instead, let's take a look at the source code.

A few notes that will be helpful:

  • read_flag() is defined to fetch and print out the flag. Its implementation details are straightforward & irrelevant; there's nothing strange happening in it.
  • The game will continually loop, collecting input if any is given and updating the state accordingly. The display will also be updated with the current state, using ASCII art to provide a simple GUI. Details here (including main(), buildLevel(), and renderLevel()) are similarly irrelevant.

Revealing The Flag

On the other hand, there's some of interesting stuff happening in handleMovement(). There's a fair bit of noise I'll filter out again, but let's take a closer look at the middle of the function:

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    switch (level[*ypos][*xpos]) {
        case ' ':
            break;
        case 'f':
            return "Does this feel like the end?\n"
                "Are you proud of yourself?\n"
                "Question: Should Magellan have been proud of himself had he turned around after leaving the harbor?\n"
                "Answer: No.";
            break;
        case '1':
            read_flag();
            break;
        default:
            if (input == 'w')
                (*ypos)++;
            else if (input == 'a')
                (*xpos)++;
            else if (input == 'd')
                (*xpos)--;
            else if (input == 's')
                (*ypos)--;
    }

This is the key! It looks like we need to find a way to maneuver our character over a number 1 somewhere on the map.

Let's try playing the game to see where that might be... Here's the view from the end of the level:

++++++++++++++++++                       +++++++++++++++++                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+                                        +                                       
+            _                           +                _                      
+           \_/                          +               \_/                     
+            |._                         +                |._                    
+            |'."-._.-""--.-"-.__.'/     +                |'."-._.-""--.-"-.__.'/
+            |  \                 /      +                |  \       .-.       / 
+            |   |                (      +                |   |     (@.@)      ( 
+            |   |                 )     +                |   |   '=.|m|.='     )
+            |  /                 /      +                |  /    .='`"``=.    / 
+            |.'                 (       +                |.'                 (  
+            |.-"-.__.-""-.__.-"-.)      +                |.-"-.__.-""-.__.-"-.) 
+            |                           +                |                      
+            |                           +                |                      
+            |                           +                |                      
+            |                           +                |                      
+            f                  p        +                1                      
---------------------------------------------------------------------------------

There's the flag! So close, and yet so far... our character is the p and the + symbols represent a solid wall. There's no way we can make the jump required at the top of the screen, either 😦.

Teleportation

There must be some weakness that we're missing in the code. Let's go back and take a closer look at the beginning of the function:

 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
char *handleMovement(unsigned char *xpos, unsigned char *ypos, char level[LEVELHEIGHT][LEVELWIDTH], int input) {
    if (level[*ypos+1][*xpos] != ' ') {
        jumpCount = 0;
    }
    if (input == 'd')
        (*xpos) ++;
    if (input == 'a')
        (*xpos) --;
    if (input == 'w') {
        if (level[*ypos+1][*xpos] != ' ') {
            jumpCount = 3;
        }
        if (jumpCount > 0) {
            (*ypos) --;
        }
    }
    if (input == 's')
        (*ypos) ++;

This is interesting! It doesn't appear that there're any bounds checks occurring before players are moved, meaning the player could end up with negative coordinates. What does that mean? Well, let's take another look at how the player's location is stored:

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
int main() {
    unsigned char xpos;
    unsigned char ypos;
    char input = 0;
    char *message_buffer;

    buildLevel(level, &xpos, &ypos);
    // Clear screen
    printf("\e[1;1H\e[2J");
    renderLevel(level, xpos, ypos, 80, 40);
    system ("/bin/stty cbreak");
    while((input = getchar()) != 3) {
        system ("/bin/stty cooked");
        // Clear screen
        printf("\e[1;1H\e[2J");
        message_buffer = handleMovement(&xpos, &ypos, level, input);

Since xpos and ypos are both unsigned chars, they can only hold values from 0 - 255. In the world of unsigned integers, \(0 - 1 = 255\). This means that if the player moves left from the left edge of the screen (where x == 0), x will suddenly become 255, teleporting them to the 256th column of the map!

The whole map looks like this:

                                                                                  _                ++++++++++++++++++++++                                                                                                                                        
                                                                              .+(`  )`.                                                                                                                                                                          
                                                                             :(   .    )                              +++++++++++++++++++++++++++                                                                                                                
                                                                        .-.,_`.    (  ) )                                                                                                                                                                        
                                                                     .=(    )    (   ) )                                                       +++++++++++++++++++++++                                                                                           
                                                                     (   .- ( ` (    .--._  _                                                                                                                                                                    
                                                                    (   (  ( ) ((...(   . '  ')`.                                                                                                                                                                
                                                                     `- _-( .=(   (   . (   .    )                                          ++++++++++++++++++                                                                                                   
                                                                           ((   .  (__ `.  (    ) )                                                                                                                                                              
                                                                            `- __.'   `.. ._`-,) )                                                             +++++                                                                                             
                                                                                            *                                                                                                                                                                    
                                                                        *      *         *                                                                                                                                                                       
                                                                                    *      *                                                                                                                                                                     
                                                                    *                   *                                           .')                                                                                                                          
                                                                                     *                                             (_  )                                  ++++++++++++++++++                       +++++++++++++++++                             
                                                                          *   *                *                                                                          +                                        +                                             
                                                                                      *     *                                                                             +                                        +                                             
                                                                      *                                                                                                   +                                        +                                             
                                                                                       *   *                                 _                                            +                                        +                                             
                                                                          *       *               *                      .+(`  )`.                                        +                                        +                                             
                  _                                                                       *                             :(   .    )                                       +                                        +                                             
                (` `).                   _                                  *                      *               .--  `.  (    ) )                                      +                                        +                                             
   -  --==     (     ).=--           .+(` ')`.                                    *        *                    .=(   )   ` _`  ) )                                       +                                        +                                             
  )           (        '`.          :(   .    )                                       *         *               (   .  )     (   )  ._                                    +                                        +                                             
          .+(`(      .    )    .--  `.  (    ) )                                          *           *        (   (   ))     `-'.:(`  )                                  +            _                           +                _                            
         ((    (..__.:'---'..=(   )   ` _`  ) )                              *       *                          `- __.'         :(      ))                                +           \_/                          +               \_/                           
  `.     `(       ) )       (   .  )     (   )  ._                                 *      *                       *             `(    )  ))                               +            |._                         +                |._                          
    )      ` __.:'   )     (   (   ))     `-'.:(`  )                                                *                             ` __.:'                                 +            |'."-._.-""--.-"-.__.'/     +                |'."-._.-""--.-"-.__.'/      
  )  )  ( )       --'       `- __.'         :(      ))                            *                      *                *                                               +            |  \                 /      +                |  \       .-.       /       
  .-'  (_.'          .')                *   `(    )  ))                      *        *  *  *                     *                  *                                    +            |   |                (      +                |   |     (@.@)      (       
+                   (_  )          *          ` __.:'                              *          *  *     *                *                                                 +            |   |                 )     +                |   |   '=.|m|.='     )      
+            *                                                                                     *      *                   *                                           +            |  /                 /      +                |  /    .='`"``=.    /       
+                *             *          *                                          *                                                                                    +            |.'                 (       +                |.'                 (        
+ p                     *           *                                                        *      \            o          _|_        *                                  +            |.-"-.__.-""-.__.-"-.)      +                |.-"-.__.-""-.__.-"-.)       
++++                                           __                                                   ))          }^{        /___\                                          +            |                           +                |                            
++++++                                       _|__|_                                               .-#-----.     /|\     .---'-'---.    .-#-----.                          +            |                           +                |                            
++++++++                                      (oo)                                           ___ /_________\   //|\\   /___________\  /_________\                         +            |                           +                |                            
++++++++++                  ++         ++  >-(    )-<                                       /___\ |[] _ []|   ///|\\\   | A /^\ A |    |[] _ []|                          +            |                           +                |                            
++++++++++++        ++++    ++      ++++ +  (......)                                        |"#"| |  |*|  |  ////|\\\\  |   |"|   |    |  |*|  |                          +            f                           +                1                            
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

It just happens to be 256 columns wide, meaning that stepping off the left boundary will teleport us to the right edge of the screen!

Exploit

While we can't immediately walk off the left boundary, after climbing a bit up the snowflakes, we can jump off the upper left edge get warped around to the right side! Then, we simply need to walk over to the 1 to complete the level and get the flag.

Flag 2

Observations

For this flag, we're given the source code for the game's second level. This code bears several key similarities to level 1:

  • readFlag() is still called when the player stands over the 1 character on the map
  • The game still loops, collects input, and renders using the same abstractions
  • handleMovement() still doesn't check bounds before moving the player

Let's also take a look at the whole map to start. Once again, we start in the bottom left corner:

+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+                                                                                                                                                                                                                                                              +
+                                                                                                                                                                                                                                                              +
+                                                                                                                                                                                                                                                              +
+                                                                                                                                                                                                                                                              +
+                                                                                                                                                                                                                                                              +
+                                            _                                                                                                                                                                                                                 +
+                                        .+(` ')`.                                                                                                                                                                                                             +
+                                       :(   .    )                                                                                                                                                                                                            +
+                                       `.  (    ) )                                                                                                                                                                                                           +
+                                         ` _`  ) )                                                                                                                                                                                                            +
+                                            (   )                                                                                                                                                                                                             +
+                                             `-'                                                                                                                                                                                                              +
+                                                                                    ~~.                                                                                                                                                                       +
+                                                                                   (~  )                                                                                                                                                                      +
+                                                                                  ( _.)_)                                          (`~                                                                                                                        +
+                                                                                                                                  (. _)                                                                                                                       +
+                                                                                                                                                                                                                                                              +
+               |                                                                                                                                                                                                                                              +
+      .               /                                                                                                                                                                           _                                                           +
+       \       |                                                                        .--._  _                            _                                                                 .+(` ')`. _  _                                                  +
+                   /                                                              .-..=(   . '` ')`.                    .+(`  )`.                                                          .=:(   .    ) '` ')`.                                              +
+         \  ,g88R_                                                             .=(   (   .:(   .    )                  :(   .    )                                                         (   .( (   :(   .    )                                             +
+           d888(` `).                   _                                      (   .(   ( `.  (    ) )            .-.,_`.    (  ) )                                                       (   (  ` `-'`.  (    ) )                                            +
+  -  --==  888(     ).=--           .+(` ')`.                                 (   (  `-.__..` _`-,) )          .=(    )    (   ) )                                                         `- __._;:__..` _`-,) )                                             +
+ )         Y8P(       '`.          :(   .    )                                 `- __.'                         (   .- ( ` (    .--._  _                                                                 *                                                     +
+         .+(`(      .    )    .--  `.  (    ) )                                             *                 (   (  ( ) ((...(   . '` ')`.                      _                               *                  _                                         +
+        ((    (..__.:'---'..=(   )   ` _`  ) )                                     *                           `- _-( .=(   (   . (   .    )                    \e/                                         *      \e/                                        +
+ `.     `(       ) )       (   .  )     (   )  ._                                              *                     ((   .(     `.  (    ) )                    |._                                *               |._                                       +
+   )      ` __.:'   )     (   (   ))     `-'.:(`  )                                    *                              `- __.'-.__..` _`-,) )                     |'."-._.-""--.-"-.__.'/                            |'."-._.-""--.-"-.__.'/                   +
+ )  )  ( )       --'       `- __.'    *    :(      ))                                       *       *                                   *                        |  \                 /      *                      |  \       .-.       /                    +
+ .-'  (_.'          .')                    `(    )  ))                          *                                     *                                          |   |                (                 *           |   |     (@.@)      (                    +
+                   (_  )           *         `-+-.:'                                                                          *                                  |   |                 )                            |   |   '=.|m|.='     )                   +
+                            *          *       +     -------           +                                                             *                           |  /                 /            *         *      |  /    .='`"``=.    /                    +
+               b $                             +      $$$  C   d       +                 ++++++                            *               *                     |.'                 (                              |.'                 (                     +
+               -----                         --+---  ------------      +                             +++++++++                                                   |.-"-.__.-""-.__.-"-.)        *                    |.-"-.__.-""-.__.-"-.)                    +
+                     +          *           ---+----                   +             +++++    ++++++                                                             |                                                  |                                         +
+                    +++                  -------------                 +                                                                                         |                                                  |                                         +
+a       +          +  c+                                +              +          ++++                                       +++++++++                           |                                 +                |                                         +
++  p    +         +++B+++                             + ++             +                              ++++++                                        +            |                                 +                |                                         +
++$ +    A                                             + ++++           D        +++                                                                 +            f                                 [                1                                         +
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Unfortunately, it's immediately clear that we won't be able to exploit the lack of bounds checking this time since there're now walls all the way around the map. We'll need to find a new way to reach the l33t flag!

New Mechanics

There're some new mechanics in this level that may help with this. These are shown below the map in a new status printout:

---------------------------------
+       +       +       +       +
+       +       +       +       +  $0
+       +       +       +       +
---------------------------------

The grid on the left is an inventory. There are lowercase letters a, b, c, and d scattered across the map. These represent keys that we can add to our inventory by walking over them and pressing s. Once in our inventory, we can unlock and walk past the corresponding gates - uppercase A, B, C, and D.

There's also a money display in the status printout. Scattered across the map are a handful of $ that each increment our wallet by $1 when we walk across them!

Finally, near the far right of the map, between the false flag and the skull flag, there's another gate that looks like this:

+
+
[

We must pay a fee to unlock this gate and reach the skull flag, as can be seen in the paid_unlock() function:

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
char* paid_unlock(char key, struct PlayerInfo *player_info) {
    if (blocks[key].solid == 1) {
        if (player_info->dollars == 1650549605) {
            player_info->dollars = 0;
            blocks[key].solid = 0;
            return "Unlocked!";
        }
        else if (player_info->dollars > 1650549605) {
            return "Your excessive wealth disgusts me.";
        }
        else {
            return "Sorry! You need $1650549605 to unlock me!";
        }
    }
    return "";
};

How do we get that much money?? There's only 5 $ available to pick up in the game!

Inventory Storage

There must be another way to get rich... Let's take a look at where the player's wallet data is stored and see if there's anything interesting around that:

13
14
15
16
17
18
19
20
struct PlayerInfo {
    int jumpCount;
    int collected_items;
    unsigned char xpos;
    unsigned char ypos;
    char inventory[4];
    uint dollars;
};

Nothing seems to be too special here, but the dollars attribute is defined directly after the new inventory feature. Let's go take a look at how that inventory array is used:

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
    if (input == 's' && current_block_properties.mustInteract) {
        if (current_block_properties.specialProperty) {
            message = (*current_block_properties.specialProperty)(current_level_location, player_info);
        }
        if (current_block_properties.collectable) {
            player_info->inventory[player_info->collected_items] = current_level_location;
            player_info->collected_items ++;
        }
        if (current_block_properties.disappears) {
            current_level_location = ' ';
        }
    }
    else if (!current_block_properties.mustInteract) {
        if (current_block_properties.specialProperty) {
            message = (*current_block_properties.specialProperty)(current_level_location, player_info);
        }
        if (current_block_properties.collectable) {
            player_info->inventory[player_info->collected_items] = current_level_location;
            player_info->collected_items ++;
        }
        if (current_block_properties.disappears) {
            current_level_location = ' ';
        }
    }

To understand this code snippet, it's helpful to know that collectible means that the user is able to pick up an item from the space — in this level, only keys are collectible. It's also helpful to know that current_level_location is a macro that gets the character the player is standing on (e.g. a $ for money or an a for the A key).

Something interesting is going on with this collection code! As the highlighted lines show, the game doesn't perform bounds checking for the inventory array before or after allowing the user to pick up an item. Keys also aren't removed after they're collected because their disappears attribute is false. This means there is no limit to the number of keys we can pick up!

Buffer Overflow

How is that useful? Well, the location the collectibles are stored at is incremented each time one is picked up. This means that we can continue writing into memory past the end of the array allocated for player.inventory[]. As we saw in the PlayerInfo struct, this allows us to overwrite the previous value in player_info->dollars and gain significantly more money!

Even better, we just saw that when keys are added to the (potentially overflowed) inventory array, the value actually stored is the ASCII value for the character shown on screen, e.g. an a for the A key. These ASCII values take 1 byte each, so we can control how memory is overwritten on a per-byte basis.

Going back to paid_unlock(), we need to get $1650549605 in order to beat the level. Let's take a look at some alternate representations of 1650549605 to figure out the order we need to pick up extra keys in!

Big-endian Little-endian
Hexadecimal 6261 6365 6563 6162
ASCII b a c e e c a b

We're on a little-endian system, meaning that we need to overwrite the 4 bytes in the uint player wallet with keys in the order e, c, a, b to get $1650549605. Unfortunately, there's no key e!

Thankfully, we don't need one. The e is needed for the least significant byte of the dollar amount, which it translates to $101 (the ASCII code for e is 101). The ASCII code for d (a valid key!) is 100, which would give us $100 if we used it first with the extra keys bug. We can then pick up a single $ to get the extra dollar needed to unlock the system! This means our new sequence for picking up extra keys is d, c, a, b, plus a $ that we pick up after starting to exploit this bug.

Exploit

Warning

Remember that you need to leave at least 1 $ behind to pick up after you start exploiting the extra keys bug to gain extra money! The first time you exploit the bug and pick up another d, your wallet will be set to $100, regardless of what was there before.

Let's go back to the game and carry this out! We can jump over the $ next to key b, saving it for after we've reached all the keys and can start overwriting the wallet. After picking up a second d key, our wallet should jump to $100. After this, we can grab the last $ at any time and pick up the remaining 3 keys in the correct order. With that, we can finally make it past the toll booth to get the second flag!

Not seeing your wallet increase the first time you exploit the bug?

Something interesting is happening here... the first time (and second time!) you try to exploit the bug, you probably won't see your wallet's value increase. This is because of how the compiler lays out structs in memory. Even though uint dollars immediately follows char inventory[4] in the struct definition, the compiler may add additional space in a process known as struct padding. This helps speed up operations that reference struct elements, and is even required on some architectures! Look up struct padding and packing to learn more.

Flag 3

Observations

For this flag, we were given the executable file for level 2, so the flag must also be buried somewhere in level 2. This is the compiled version of the C file we were just looking at, so all of the same code is here, including the buffer overflow vulnerability in the player's inventory. Quickly running file on the executable shows that ASLR is disabled. This will make life a lot easier if we need to reference specific memory addresses in our attack!

level2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=234ff2f183068255d1e960d7d9bb2381c3de8310, for GNU/Linux 3.2.0, with debug_info, not stripped

objdump Output

Since we were specifically given the executable for this flag, let's start by examining its contents for anything unexpected using objdump -d level2 --disassembler-color=on | less -R. Near the bottom, there is indeed something unexpected that's not in the C file we looked at before:

Disassembly of section .hidden_in_plain_sight:

0000000063626161 <another_flag>:
    63626161:   f3 0f 1e fa             endbr64
    63626165:   55                      push   %rbp
    63626166:   48 89 e5                mov    %rsp,%rbp
    63626169:   48 81 ec 90 00 00 00    sub    $0x90,%rsp
    63626170:   89 f8                   mov    %edi,%eax
    63626172:   48 89 b5 70 ff ff ff    mov    %rsi,-0x90(%rbp)
    63626179:   88 85 7c ff ff ff       mov    %al,-0x84(%rbp)
    6362617f:   48 8d 05 2e ba dd 9c    lea    -0x632245d2(%rip),%rax        # 401bb4 <_IO_stdin_used+0x4>
    63626186:   48 89 c6                mov    %rax,%rsi
    63626189:   48 8d 05 27 ba dd 9c    lea    -0x632245d9(%rip),%rax        # 401bb7 <_IO_stdin_used+0x7>
    63626190:   48 89 c7                mov    %rax,%rdi
    63626193:   e8 38 a8 dd 9c          call   4009d0 <fopen@plt>
    ...
    636261dd:   48 89 c7                mov    %rax,%rdi
    636261e0:   e8 4b a7 dd 9c          call   400930 <fread@plt>
    636261e5:   48 89 45 f0             mov    %rax,-0x10(%rbp)
    636261e9:   48 8b 0d 90 de dd 9c    mov    -0x63222170(%rip),%rcx        # 404080 <stdout@GLIBC_2.2.5>
    636261f0:   48 8b 55 f0             mov    -0x10(%rbp),%rdx
    636261f4:   48 8d 45 80             lea    -0x80(%rbp),%rax
    636261f8:   be 01 00 00 00          mov    $0x1,%esi
    636261fd:   48 89 c7                mov    %rax,%rdi
    63626200:   e8 db a7 dd 9c          call   4009e0 <fwrite@plt>
    63626205:   48 8b 45 f8             mov    -0x8(%rbp),%rax
    63626209:   48 89 c7                mov    %rax,%rdi
    6362620c:   e8 2f a7 dd 9c          call   400940 <fclose@plt>
    63626211:   48 8d 45 80             lea    -0x80(%rbp),%rax
    63626215:   48 89 c7                mov    %rax,%rdi
    63626218:   e8 03 a7 dd 9c          call   400920 <puts@plt>
    6362621d:   48 8b 05 5c de dd 9c    mov    -0x632221a4(%rip),%rax        # 404080 <stdout@GLIBC_2.2.5>
    63626224:   48 89 c7                mov    %rax,%rdi
    63626227:   e8 94 a7 dd 9c          call   4009c0 <fflush@plt>
    6362622c:   90                      nop
    6362622d:   c9                      leave
    6362622e:   c3                      ret

This is awesome! It looks like an extra function another_flag() has been placed at address 0x63626161 which, as we discovered with flag 2, is a number whose bytes correspond with the ASCII string aabc (on a little-endian system). It looks like we need to reuse the buffer overflow vulnerability from flag 2 to overwrite some return address and cause another_flag() to be executed.

Vulnerable Return Address

There's just one issue, though: handleMovement() doesn't loop at all. Instead, the game loop is handled in runGame().

237
238
239
240
241
242
243
244
245
246
    while ((input = getchar()) != 3 && input != 'q') {
        system ("/bin/stty cooked");
        // Clear screen
        printf("\e[1;1H\e[2J");
        message_buffer = handleMovement(&player_info, level, input);
        renderLevel(level, player_info.xpos, player_info.ypos, 80, 20);
        printInventory(player_info.inventory, player_info.dollars, inventory_size);
        printf("%s\n", message_buffer);
        system ("/bin/stty cbreak");
    }

If player_info was a local variable in handleMovement(), we would be unable to exploit flag 2's vulnerability to overwrite any return addresses because we need to overwrite 4 bytes and the function would return after each byte is overwritten, probably crashing after the first byte of the return address is overwritten and definitely preventing us from overwriting any more.

Thankfully, player_info is passed by reference for efficiency, so it actually exists in the stack frame of runGame()! As long as there's nothing crucial in memory between player_info and runGame()'s return address, we can overflow the buffer during the while ((input = getchar()) != 3 && input != 'q') { loop as much as necessary without crashing the program. Then, after exiting the loop by pressing Q, runGame() will return to whatever address we specify.

Checking if there's anything critical between player_info and runGame()'s return address

Before trying to exploit this vulnerability, we should check that there isn't anything critical (i.e. that might make our attack fail if overwritten) in the section of memory that we need to overwrite. In this case, that would be any other local variables in runGame(). input is a crucial variable that could have unexpected consequences for overwriting since it's used in the while loop's condition. However, we can't overwrite it with a 3 or q, so we don't have to worry about accidentally triggering a program exit. It's also passed by value to handleMovement() (i.e. a copy of it is made for handleMovement()) before it can be overwritten. This means we don't need to worry about unexpected behavior in that function, either.

message_buffer could also be a risk since the address it holds is passed to printf() to print a message before the end of the while loop ends. If this address is overwritten with something that isn't mapped memory, the program will crash when printf() dereferences it to print the string. Thankfully, though, we don't have to worry about this either since in C's calling convention, parameters are pushed to the stack before functions are called. As long as we don't keep overwriting memory past runGame()'s return address, we shouldn't encounter this issue.

Finding the Return Address

All that's left is figuring out how far to overflow the player_info.inventory buffer to overwrite runGame()'s return address. The stack frame offset for player_info can be found from objdump -d by looking at the arguments runGame() passes to handleMovement():

call    0x400970 <printf@plt>
movsbl  -0x1(%rbp), %edx
leaq    -0x20(%rbp), %rax
leaq    0x2743(%rip), %rcx      # 0x4040a0 <level>
movq    %rcx, %rsi
movq    %rax, %rdi
callq   0x401061 <handleMovement>

-0x1(%rbp) is only 1 byte, which definitely isn't enough for a PlayerInfo struct. 0x2743(%rip)'s annotation indicates that it holds level, not player_info. By process of elimination, -0x20(%rbp) must be the offset for player_info.

Reassembling runGame()'s full stack frame

While not strictly necessary, we can use a similar process of searching for distinctive variable usage and deduction to figure out the exact stack frame of runGame().

Alternatively, with a little math, gdb can be used to reconstruct the frame:

pwndbg> b runGame
pwndbg> r
pwndbg> info frame
Stack level 0, frame at 0x7fffdf0998f0:
rip = 0x4018bc in runGame (/level2/level2.c:223); saved rip = 0x401b94
called by frame at 0x7fffdf099910
source language c.
Arglist at 0x7fffdf0998e0, args: inventory_size=4, message_buffer=0x0
Locals at 0x7fffdf0998e0, Previous frame's sp is 0x7fffdf0998f0
Saved registers:
  rbp at 0x7fffdf0998e0, rip at 0x7fffdf0998e8
pwndbg> p &message_buffer
$1 = (char **) 0x7fffdf0998b0
pwndbg> p &inventory_size
$2 = (int *) 0x7fffdf0998bc
pwndbg> p &player_info
$3 = (struct PlayerInfo *) 0x7fffdf0998c0

Either way, the stack frame should look like this (growing down, so the highest addresses and earliest elements pushed to the stack are on top):

|  location  | size |     data      |
|------------+------+---------------|
|                                   |
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
| 0x10(%rbp)  (0x4)  inventory_size |
| 0x8(%rbp)   (0x8)      %rip       |
| (%rbp)      (0x8)      %rbp       |
| - - - - - - - - - - - - - - - - - |
| -0x1(%rbp)  (0x1)  input          |
| -0xc(%rbp)  (0xb)  #      padding |
| -0x20(%rbp) (0x14) player_info    |
| -0x28(%rbp) (0x8)  #      padding |
| -0x30(%rbp) (0x8)  message_buffer |
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|

Since the struct is padded, we also need to use gdb to find the exact offset the overflow starts at:

pwndbg> b level2.c:244
pwndbg> r
pwndbg> p &player_info
$1 = (struct PlayerInfo *) 0x7ffedd0a1df0
pwndbg> p &player_info.inventory
$2 = (char (*)[4]) 0x7ffedd0a1dfa
pwndbg> p sizeof player_info
$4 = 20
pwndbg> p (0x7ffedd0a1df0 + 20) - 0x7ffedd0a1dfa
$3 = 10
gdb not properly reaching runGame()?

The game we're hacking makes calls to system("/bin/stty cbreak"). This spawns a child process each time which gdb will follow by default. To turn this off and continue examining the actual program, run the following:

pwndbg> set detach-on-fork on
pwndbg> set follow-exec-mode same
pwndbg> set follow-fork-mode parent

This shows player_info.inventory starts 10 bytes before the end of player_info. player_info.inventory is 4 bytes long, so the first overflow byte is 6 bytes before the end of player_info. Those 6 bytes must hold 2 bytes of padding plus the 4 byte integer player_info.dollars.

Adding this up, we can see that the stored %rip that we want to overwrite is 26 bytes after the end of the player_info.inventory buffer (6 for padding & player_info.dollars + 12 for padding & input + 8 for stored %rbp).

Figuring this out more quickly

This takes a lot of work and several tools. It turns out there's an easier way to do it, using just gdb!

pwndbg> b level2.c:244
pwndbg> r
pwndbg> p &player_info.inventory
$1 = (char (*)[4]) 0x7ffd8e21e24a
pwndbg> info frame
Stack level 0, frame at 0x7ffd8e21e270:
rip = 0x401a00 in runGame (/level2/level2.c:244); saved rip = 0x401b94
called by frame at 0x7ffd8e21e290
source language c.
Arglist at 0x7ffd8e21e260, args: inventory_size=4, message_buffer=0x401bc8 ""
Locals at 0x7ffd8e21e260, Previous frame's sp is 0x7ffd8e21e270
Saved registers:
  rbp at 0x7ffd8e21e260, rip at 0x7ffd8e21e268
pwndbg> p 0x7ffd8e21e268 - 0x7ffd8e21e24a
$2 = 30

This calculation shows there are 30 bytes from the beginning of player_info.inventory to runGame()'s return address. player_info.inventory takes up 4 bytes itself for the 4 inventory slots, so the first overflowed byte is 26 bytes from the return address.

Exploit

Pulling all of this together, we need to fill our inventory with keys, collect 26 more keys to reach the return address we want to overwrite, then collect specifically the keys:

  1. a
  2. a
  3. b
  4. c

This will overwrite the runGame()'s return address and cause another_flag() to execute when the game ends. All we need to do now is press q to quit the game and the flag will be printed!

Conclusion

While flags 2 and 3 introduced some new behavior with padded structs, tonight's challenge was largely based on concepts already seen this term in friendly-work and ret2win. I hope you enjoyed playing the game and got a better sense of how these vulnerabilities can be exploited in real-world programs!