Reputation: 329
Okay we are given the following code:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "callstack.h"
#include "tweetIt.h"
#include "badguy2.c"
static char *correctPassword = "ceriaslyserious";
char *message = NULL;
int validateSanity(char *password) {
for(int i=0;i<strlen(password);i++)
if(!isalpha(password[i]))
return 0;
unsigned int magic = 0x12345678;
return badguy(password);
}
int validate(char *password) {
printf("--Validating something\n", password);
if (strlen(password) > 128) return 0;
char *passwordCopy = malloc(strlen(password) + 1);
strcpy(passwordCopy, password);
return validateSanity(passwordCopy);
}
int check(char *password, char *expectedPassword) {
return (strcmp(password, expectedPassword) == 0);
}
int main() {
char *password = "wrongpassword";
unsigned int magic = 0xABCDE;
char *expectedPassword = correctPassword;
if (!validate(password)) {
printf("--Invalid password!\n");
return 1;
}
if (check(password, expectedPassword)) {
if (message == NULL) {
printf("--No message!\n");
return 1;
} else {
tweetIt(message, strlen(message));
printf("--Message sent.\n");
}
} else {
printf("--Incorrect password!\n");
}
return 0;
}
We are supposed to trick main
into sending a tweet using the function badguy
. In badguy we have an offset from a previous problem which is the difference between the declaration of password
in main
and the argument passed to badguy
. We have been instructed to use this offset to find the addresses of the correctPassword
and password
in main and manipulate the value in password
to correctPassword
so when the password check occurs, it is believed to be legitimate. I am having some trouble figuring out how to use this offset to find the addresses and continuing from there.
Upvotes: 1
Views: 167
Reputation: 5171
First of all, make sure you have good control over your compiler behavior. That is: make sure you know the calling conventions and that they're being respected (not optimized away or altered in any manner). This usually boils down to turn off optimization settings, at least for testing under more controlled conditions until a robust method is devised. Pay special attention to variables such as expectedPassword
, since it is highly likely they'll be optimized away (expectedPassword
might never be created in the stack, being substituted with the equivalent of correctPassword
, rendering you with no stack reference to the correct password at all).
Secondly, note that "wrongpassword"
is shorter than "ceriaslyserious"
; in other words, if I got it straight, attempting to crack into the buffer pointed to by passwordCopy
(whose size is the length of "wrongpassword"
plus one) in order to copy "ceriaslyserious"
into there could result in a segmentation violation. Nonetheless, it should be relatively simple to track the address of expectedPassword
in the call stack, if it exists (see above), specially if you do have already an offset from main()
's stack frame.
Considering an x86 32-bit target under controlled circumstances, expectedPassword
will reside 8 bytes below password
(4 for password
, 4 for magic
if it is not optimized away). Having an offset from password
to a parameter as you said, it should suffice to subtract the offset from the address of that parameter, and then add 8. The resulting pointer should be expectedPassword
, which then points to the static area containing the password. Again, double check your environment. Check this for an explanation on the stack layout in x64 (the layout in the 32-bit case is similar).
Lastly, if expectedPassword
does not exist in the call stack, then, since correctPassword
is a global static, it will reside in a data segment, rendering the method useless. To achieve the goal in this situation, you would need to carefully scan the data segment with a more intelligent algorithm. It would probably be easier, though, to simply attempt to find the test for check()
's return value in the program text and replace with nop
s (after properly manipulating the page permissions to allow writing to the text segment).
If you're having problems, inspecting the resulting assembly code is the way to go. If you're using GCC, gcc -S
halts the compilation just before assembling (that is, producing an assembly source code file as output). objdump -d
could also help. gdb
can step between instructions, show the disassembly of a frame and display register contents; check the documentation.
These exercises are specially useful to understand how security breaches occur in common programs, and to provide some basic notions on defensive programming.
Upvotes: 2