NationalSecurityAgency / ghidra

Ghidra is a software reverse engineering (SRE) framework
https://www.nsa.gov/ghidra
Apache License 2.0
52.1k stars 5.91k forks source link

x86 FPU emulation doesn't change FPU instruction pointer #7205

Open bthiae2934 opened 1 week ago

bthiae2934 commented 1 week ago

Describe the bug I tried to use Ghidra to emulate shellcode encoded by shikata_ga_nai. Ghidra can't emulate following instruction sequence correctly:

        00000005 da  ce           FCMOVE     ST0 ,ST6
                                                      $U44a00 :1 =  BOOL_NEGATE  ZF
                                                      CBRANCH  *[ram ]0x7 :4, $U44a00 :1
                                                      ST0  =  COPY  ST6
        00000007 d9  74  24       FNSTENV    [ESP  + -0xc ]
                 f4
                                                      $U3780 :4 =  INT_ADD  0xfffffff4 :4, ESP
                                                      STORE  ram ($U3780 :4), FPUControlWord
                                                      $U4ae80 :4 =  INT_ADD  $U3780 :4, 4:4
                                                      STORE  ram ($U4ae80 :4), FPUStatusWord
                                                      $U4af00 :4 =  INT_ADD  $U3780 :4, 8:4
                                                      STORE  ram ($U4af00 :4), FPUTagWord
                                                      $U4af80 :4 =  INT_ADD  $U3780 :4, 20 :4
                                                      STORE  ram ($U4af80 :4), FPUDataPointer
                                                      $U4b000 :4 =  INT_ADD  $U3780 :4, 12 :4
                                                      STORE  ram ($U4b000 :4), FPUInstructionPointer
                                                      $U4b080 :4 =  INT_ADD  $U3780 :4, 18 :4
                                                      STORE  ram ($U4b080 :4), FPULastInstructionOpcode
        0000000b 5d              POP        EBP
                                                      $U2e680 :4 =  COPY  0:4
                                                      $U2e680 :4 =  LOAD  ram (ESP )
                                                      ESP  =  INT_ADD  ESP , 4:4
                                                      EBP  =  COPY  $U2e680 :4

These three instructions are a clever way to get EIP into EBP. The outcome of the first one is to set FPU instruction pointer to 0x5 and the second one is used to save FPU instruction pointer (and several other registers) on the stack.

Ghidra doesn't change FPU last instruction pointer so this register will remain at 0. Maybe it's necessary to change PCode of many floating-point instructions.

To Reproduce Steps to reproduce the behavior:

  1. Create a file (shellcode) with following content: bf d3 7a d2 76 da ce d9 74 24 f4 5d 29 c9 b1 06 31 7d 12 83 c5 04 03 ae 74 30 83 61 40 43 8d 31 59 c4 61 42 36 14 16 8b a4 7d 88 5a cb b3 d4
  2. Import this binary, arch: x86 (i386).
  3. Run emulator to emulate this shellcode.
  4. Emulate instruction at offset 0x5.

Expected behavior FPUInstructionPointer should change to 0x5.

Screenshots image

Attachments Nothing

Environment (please complete the following information):

Additional context From Intel Developer Manual: image

nsadeveloper789 commented 5 days ago

I'm in agreement that p-code to set FPUInstructionPointer to 5 is missing. For this particular case, I think the following patch would fix it, but I'm not currently in a great position to test:

diff --git a/Ghidra/Processors/x86/data/languages/ia.sinc b/Ghidra/Processors/x86/data/languages/ia.sinc
index f7a29889a9..fa086de47f 100644
--- a/Ghidra/Processors/x86/data/languages/ia.sinc
+++ b/Ghidra/Processors/x86/data/languages/ia.sinc
@@ -4608,7 +4608,7 @@ define pcodeop to_bcd;
 :FNCLEX         is vexMode=0 & byte=0xDB; byte=0xE2                 { FPUStatusWord[0,8] = 0; FPUStatusWord[15,1] = 0; } 

 :FCMOVB ST0, freg   is vexMode=0 & byte=0xDA; frow=12 & fpage=0 & freg & ST0        { if ( !CF ) goto inst_next; ST0 = freg; }   
-:FCMOVE ST0, freg   is vexMode=0 & byte=0xDA; frow=12 & fpage=1 & freg & ST0        { if ( !ZF ) goto inst_next; ST0 = freg; }   
+:FCMOVE ST0, freg   is vexMode=0 & byte=0xDA; frow=12 & fpage=1 & freg & ST0        { FPUInstructionPointer = inst_start; if ( !ZF ) goto inst_next; ST0 = freg; }   
 :FCMOVBE ST0, freg  is vexMode=0 & byte=0xDA; frow=13 & fpage=0 & freg & ST0        { if ( !CF & !ZF ) goto inst_next; ST0 = freg; } 
 :FCMOVU  ST0, freg  is vexMode=0 & byte=0xDA; frow=13 & fpage=1 & freg & ST0        { if ( !PF ) goto inst_next; ST0 = freg; }   
 :FCMOVNB ST0, freg  is vexMode=0 & byte=0xDB; frow=12 & fpage=0 & freg & ST0        { if ( CF ) goto inst_next; ST0 = freg; }   

This is incomplete, however, as you've pointed out. Many of the FP instructions would need this change. If you can confirm the above change fixes your case, then we'll know how to get started. Thanks!

bthiae2934 commented 4 days ago

@nsadeveloper789 I have tried your patch on master branch. Ghidra emulates this shellcode correctly after your patch applied. Thanks! image I changed base address and ESP to verify whether it works.

Shellcode after decode: image image