Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Both EFLAGS and single-stepping through conditionals is broken on amd64 #499

Closed
zachriggle opened this issue Mar 30, 2016 · 14 comments
Closed
Labels

Comments

@zachriggle
Copy link
Contributor

Feel free to dupe to #496 if you want, but here's a concrete trace. I did not contribute there since this is an actual emulation failure, rather than a failure to be coherent inside a callback.

By using Binjitsu / Pwntools, you can easily assemble the following sequence of instructions:

$ asm -c amd64 'xor r14,r14; test r14d, r14d; jne $; hlt'
4d31f64585f675fef4

It's also pretty easy to launch it in a debugger.

$ asm -c amd64 'xor r14,r14; test r14d, r14d; jne $; hlt' --debug

When the debugger pops up, here's what we see:

pwndbg> x/4i $pc
=> 0x6000b0:    xor    r14,r14
   0x6000b3:    test   r14d,r14d
   0x6000b6:    jne    0x6000b6
   0x6000b8:    hlt    

By instantiating a Unicorn context object with the current state and stepping four times, we should hit the HLT instruction, but we never do.

Here's the full trace, below. Note that everything between emu_start and emu_stop is processed in callbacks.

# Instantiating Unicorn for x86-64
uc = U.Uc(4, 8)
uc.reg_write(U.x86_const.UC_X86_REG_CS, 0x33)
uc.reg_write(U.x86_const.UC_X86_REG_SS, 0x2b)
uc.reg_write(U.x86_const.UC_X86_REG_DS, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_ES, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_FS, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_GS, 0x0)
Could not set register 'fsbase'
Could not set register 'gsbase'
uc.reg_write(U.x86_const.UC_X86_REG_AX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_AH, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_AL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_BX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_BH, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_BL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_CX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_CH, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_CL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_DX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_DH, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_DL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_DIL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_SIL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_SPL, 0xffffffffffffffd0)
uc.reg_write(U.x86_const.UC_X86_REG_BPL, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_DI, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_SI, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_BP, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_SP, 0x7ffe7a8174d0)
uc.reg_write(U.x86_const.UC_X86_REG_RAX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RBX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RCX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RDX, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RDI, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RSI, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R8, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R9, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R10, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R11, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R12, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R13, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R14, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_R15, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RBP, 0x0)
uc.reg_write(U.x86_const.UC_X86_REG_RSP, 0x7ffe7a8174d0)
uc.reg_write(U.x86_const.UC_X86_REG_RIP, 0x6000b0)
uc.reg_write(U.x86_const.UC_X86_REG_EFLAGS, 0x200)
42286016 = uc.hook_add(*(112, <bound method Emulator.hook_mem_invalid of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})
42286384 = uc.hook_add(*(1, <bound method Emulator.hook_intr of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})

# Mapping 0x600000-0x601000
uc.mem_map(0x600000, 0x1000)

# Writing 0x1000 bytes
uc.mem_write(0x600000, ...)
42258192 = uc.hook_add(*(4, <bound method Emulator.trace_hook of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})

# Single-stepping at 0x6000b0: xor r14, r14
# EFLAGS before: 0x200
42375440 = uc.hook_add(*(4, <bound method Emulator.single_step_hook_code of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})
uc.emu_start(*(6291632, 0), **{'count': 1})
uc.mem_read(*(6291632, 3), **{})
# trace_hook: 0x6000b0 b'4d31f6'
# single_step: 0x6000b0
uc.mem_read(*(6291635, 3), **{})
# trace_hook: 0x6000b3 b'4585f6'
# single_step: 0x6000b3
uc.hook_del(*(42375440,), **{})
# EFLAGS after: 0x244


# Single-stepping at 0x6000b3: test r14d, r14d
# EFLAGS before: 0x244
42418864 = uc.hook_add(*(4, <bound method Emulator.single_step_hook_code of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})
uc.emu_start(*(6291635, 0), **{'count': 1})
uc.mem_read(*(6291635, 3), **{})
# trace_hook: 0x6000b3 b'4585f6'
# single_step: 0x6000b3
uc.mem_read(*(6291638, 2), **{})
# trace_hook: 0x6000b6 b'75fe'
uc.hook_del(*(42418864,), **{})
# EFLAGS after: 0x244


# Single-stepping at 0x6000b6: jne 0x6000b6
# EFLAGS before: 0x244
42427440 = uc.hook_add(*(4, <bound method Emulator.single_step_hook_code of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})
uc.emu_start(*(6291638, 0), **{'count': 1})
uc.mem_read(*(6291638, 2), **{})
# trace_hook: 0x6000b6 b'75fe'
# single_step: 0x6000b6
uc.mem_read(*(6291638, 2), **{})
# trace_hook: 0x6000b6 b'75fe'
uc.hook_del(*(42427440,), **{})
# EFLAGS after: 0x244


# Single-stepping at 0x6000b6: jne 0x6000b6
# EFLAGS before: 0x244
42427440 = uc.hook_add(*(4, <bound method Emulator.single_step_hook_code of <pwndbg.emu.emulator.Emulator object at 0x7f2becba1940>>), **{})
uc.emu_start(*(6291638, 0), **{'count': 1})
uc.mem_read(*(6291638, 2), **{})
# trace_hook: 0x6000b6 b'75fe'
# single_step: 0x6000b6
uc.mem_read(*(6291638, 2), **{})
# trace_hook: 0x6000b6 b'75fe'
uc.hook_del(*(42427440,), **{})
# EFLAGS after: 0x244

...

This repeats forever. Actually stepping forward in GDB shows that the HLT is indeed hit.

pwndbg> c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000000006000b8 in ?? ()
pwndbg> x/i $pc
=> 0x6000b8:    hlt    

It looks like the underlying issue is that EFLAGS is not properly updated. The real value is 0x246, the emulated value (above) is 0x244.

(gdb) x/i $pc
=> 0x6000b0:    xor    %r14,%r14
(gdb) p/x $eflags
$1 = 0x200
(gdb) p $eflags
$2 = [ IF ]
(gdb) si
0x00000000006000b3 in ?? ()
(gdb) p/x $eflags
$3 = 0x246
(gdb) p $eflags
$4 = [ PF ZF IF ]

(gdb) x/i $pc
=> 0x6000b3:    test   %r14d,%r14d
(gdb) p/x $eflags
$5 = 0x246
(gdb) p $eflags
$6 = [ PF ZF IF ]
(gdb) si
0x00000000006000b6 in ?? ()
(gdb) p/x $eflags
$7 = 0x246
(gdb) p $eflags
$8 = [ PF ZF IF ]

(gdb) x/i $pc
=> 0x6000b6:    jne    0x6000b6
(gdb) p/x $eflags
$9 = 0x246
(gdb) p $eflags
$10 = [ PF ZF IF ]
(gdb) si
0x00000000006000b8 in ?? ()
(gdb) p/x $eflags
$11 = 0x246
(gdb) p $eflags
$12 = [ PF ZF IF ]

(gdb) x/i $pc
=> 0x6000b8:    hlt    
(gdb) p/x $eflags
$13 = 0x246
(gdb) p $eflags
$14 = [ PF ZF IF ]
(gdb) si

Program received signal SIGSEGV, Segmentation fault.
0x00000000006000b8 in ?? ()
(gdb) p/x $eflags
$15 = 0x10246
(gdb) p $eflags
$16 = [ PF ZF IF RF ]
@aquynh
Copy link
Member

aquynh commented Mar 30, 2016

can you send a PR to put your Python testcase above under tests/regress/ ?

@aquynh
Copy link
Member

aquynh commented Mar 30, 2016

btw, which assembler are you using for that "asm" tool?

@zachriggle
Copy link
Contributor Author

Can do. The assembler is just gas.
On Wed, Mar 30, 2016 at 3:59 AM Nguyen Anh Quynh [email protected]
wrote:

btw, which assembler are you using for that "asm" tool?


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
#499 (comment)

zachriggle added a commit to zachriggle/unicorn that referenced this issue Mar 31, 2016
zachriggle added a commit to zachriggle/unicorn that referenced this issue Mar 31, 2016
@zachriggle
Copy link
Contributor Author

Uploaded a test cast / pull request.

zachriggle added a commit to zachriggle/unicorn that referenced this issue Mar 31, 2016
@lunixbochs
Copy link
Contributor

count=1 is facilitated by a HOOK_CODE callback and a counter, so it's likely the same issue.

edit: nvm, I manually synced eflags and this test still fails. why isn't the parity flag getting set?

aquynh added a commit that referenced this issue Mar 31, 2016
@zachriggle
Copy link
Contributor Author

It still fails even if you make this change to the test.

diff --git a/tests/regress/x86_64_eflags.py b/tests/regress/x86_64_eflags.py
index ca7c48d..561fd36 100755
--- a/tests/regress/x86_64_eflags.py
+++ b/tests/regress/x86_64_eflags.py
@@ -13,7 +13,7 @@ class WrongEFLAGS(regress.RegressTest):

         uc.mem_map(0x600000, 0x1000)
         uc.mem_write(0x6000b0, CODE)
-        uc.emu_start(0x6000b0, 0, count=1)
+        uc.emu_start(0x6000b0, 0x6000b3)


         # Here's the original execution trace for this on actual hardware.

@zachriggle
Copy link
Contributor Author

I verified that this test also fails in the 0.9 tag.

@zachriggle
Copy link
Contributor Author

I also verified that this behavior does not occur in vanilla QEMU with qemu-user, even with the GDB stub and single-stepping.

[DEBUG] cpp -C -nostdinc -undef -P -I/home/user/binjitsu/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
    .section .shellcode,"awx"
    .global _start
    .global __start
    _start:
    __start:
    .intel_syntax noprefix
    xor r14,r14
     test r14d, r14d
     jne $
     hlt
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/user/pwn-asm-CJBU1X/step2 /tmp/user/pwn-asm-CJBU1X/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/user/pwn-asm-CJBU1X/step3 /tmp/user/pwn-asm-CJBU1X/step4
[DEBUG] Building ELF:
    .section .shellcode,"awx"
    .global _start
    .global __start
    _start:
    __start:
    .intel_syntax noprefix
    .string "\x4d\x31\xf6\x45\x85\xf6\x75\xfe\xf4"
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/user/pwn-asm-VegjhT/step2-obj /tmp/user/pwn-asm-VegjhT/step1-asm
[DEBUG] /usr/bin/x86_64-linux-gnu-ld --oformat=elf64-x86-64 -EL -z execstack -o /tmp/user/pwn-asm-VegjhT/step3-elf /tmp/user/pwn-asm-VegjhT/step2-obj
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -Sg /tmp/user/pwn-asm-VegjhT/step3-elf
[DEBUG] /usr/bin/x86_64-linux-gnu-strip --strip-unneeded /tmp/user/pwn-asm-VegjhT/step3-elf
[+] Starting program '/usr/bin/qemu-x86_64-static' argv=['qemu-x86_64-static', '-g', '15972', '/tmp/user/pwn-asm-VegjhT/step3-elf'] : Done
[DEBUG] Wrote gdb script to '/tmp/user/pwnjIEyIb.gdb'
    set endian little
    set architecture i386:x86-64
    target remote 127.0.0.1:15972
[*] running in new terminal: gdb-multiarch "/tmp/user/pwn-asm-VegjhT/step3-elf" -x "/tmp/user/pwnjIEyIb.gdb" ; rm "/tmp/user/pwnjIEyIb.gdb"
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', 'gdb-multiarch "/tmp/user/pwn-asm-VegjhT/step3-elf" -x "/tmp/user/pwnjIEyIb.gdb" ; rm "/tmp/user/pwnjIEyIb.gdb"']

@aquynh
Copy link
Member

aquynh commented Apr 4, 2016

can you elaborate how to reproduce the behavior of qemu-user with isntruction "xor r14,r14"? the output above seems to be generated by pwndbg, which i am not familiar with. thanks

@aquynh
Copy link
Member

aquynh commented Apr 4, 2016

this issue with EFLAGS is due to Unicorn does not turn on the reserved flag (bit 1) on, but this behavior is undocumented in Intel manual. i dont see where Qemu handles this flag, or i missed that.s
o for now i am suspecting Qemu also has the same issue with Unicorn in this case (?)

@zachriggle
Copy link
Contributor Author

Here's the quick way to repro with QEMU.

$ asm --context amd64 --format elf 'xor r14,r14; test r14d, r14d; jne $; hlt' > example
$ objdump -d example

example:     file format elf64-x86-64


Disassembly of section .shellcode:

00000000006000b0 <.shellcode>:
  6000b0:       4d 31 f6                xor    %r14,%r14
  6000b3:       45 85 f6                test   %r14d,%r14d
  6000b6:       75 fe                   jne    0x6000b6
  6000b8:       f4                      hlt    
        ...

$ qemu-x86_64-static -d cpu,in_asm -singlestep ./example &> qemu.log

The generated qemu.log is available here.

The punchline is:

RIP=00000000006000b3 RFL=00000246 [---Z-P-] CPL=3 II=0 A20=1 SMM=0 HLT=0

If you'd like a copy of the ELF binary example, I've put it here.

@zachriggle
Copy link
Contributor Author

To answer the lingering question in #499 (comment) -- no, vanilla QEMU works correctly, even in single-step mode. This is a Unicorn-specific problem.

@zachriggle
Copy link
Contributor Author

Added yet-another-test in #511.

@zachriggle zachriggle changed the title EFLAGS is broken on amd64 Both EFLAGS and single-stepping through conditionals is broken on amd64 Apr 5, 2016
@danghvu danghvu added the bug label Jul 9, 2016
@zachriggle
Copy link
Contributor Author

Ping? :)

@wtdcode wtdcode closed this as completed Oct 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants