xorhex logo

xorhex

Focus on Threat Research through malware reverse engineering

YARA - Following FALLCHILL's E8 Call

Following a near relative call address

xorhex

6-Minute Read

MidJourney Generated Image

Summary

This article covers how to follow a near relative call instruction, 0xE8 in YARA.

TL;DR

Calculation: Address of the instruction following the call instruction + the int32 value passed to the 0xE8 opcode == Function Start Address

Near Relative Call - Explained

The 0xE8 instruction on c9x is defined as: Call near, relative, displacement relative to next instruction . This means that the address passed to the call instruction is added to the next instruction address in order to calculate the location of the function being called.

Let’s demonstrate how this call is resolved using the following code snippet from this x86 FALLCHILL sample: d8af45210bf931bc5b03215ed30fb731e067e91f25eda02a404bd55169e3e3c3

.text:1000107D E8 DE 0C 00 00                call    sub_10001D60
.text:10001082 83 C4 0C                      add     esp, 0Ch

The value 0xCDE is passed to the call instruction, so to find the location of this function we just add 0xCDE to 0x10001082 and that resolves to 0x10001D60.

IDA Resolving 0xE8

YARA

Now let’s translate these steps to YARA. First create the YARA string that will identify this call (yes this string could be improved upon, but that is not the point of this exercise):

        /*
        0x10001030 C645EC78                      mov byte ptr [ebp - 0x14], 0x78
        0x10001034 C645ED29                      mov byte ptr [ebp - 0x13], 0x29
        0x10001038 C645EE2E                      mov byte ptr [ebp - 0x12], 0x2e
        0x1000103c C645EF4C                      mov byte ptr [ebp - 0x11], 0x4c
        0x10001040 C645F05D                      mov byte ptr [ebp - 0x10], 0x5d
        0x10001044 C645F1A3                      mov byte ptr [ebp - 0xf], 0xa3
        0x10001048 C645F2B5                      mov byte ptr [ebp - 0xe], 0xb5
        0x1000104c C645F3D0                      mov byte ptr [ebp - 0xd], 0xd0
        0x10001050 C645F467                      mov byte ptr [ebp - 0xc], 0x67
        0x10001054 C645F5F0                      mov byte ptr [ebp - 0xb], 0xf0
        0x10001058 C645F681                      mov byte ptr [ebp - 0xa], 0x81
        0x1000105c C645F7B7                      mov byte ptr [ebp - 9], 0xb7
        0x10001060 C645F836                      mov byte ptr [ebp - 8], 0x36
        0x10001064 C645F9E5                      mov byte ptr [ebp - 7], 0xe5
        0x10001068 C645FAD5                      mov byte ptr [ebp - 6], 0xd5
        0x1000106c C645FB93                      mov byte ptr [ebp - 5], 0x93
        0x10001070 6A10                          push 0x10
        0x10001072 8D4DEC                        lea ecx, [ebp - 0x14]
        0x10001075 51                            push ecx
        0x10001076 8D95E8FEFFFF                  lea edx, [ebp - 0x118]
        0x1000107c 52                            push edx
        0x1000107d E8DE0C0000                    call 0x10001d60
        */
        $call_instr = {
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            6A ??
            8D 4D ??
            51
            8D 95 ?? ?? ?? ??
            52
            E8 ?? ?? ?? ??
        }

The 0xE8 call instruction is the last instruction set captured in this example, so to find the value passed to it we do:

console.hex("Relative offset to function address: ", int32(@call_instr+!call_instr-4))

Breaking this down a bit more:

  • @call_instr is the address of the first $call_instr match
  • !call_instr is the length of the first $call_instr match
  • @call_instr+!call_instr-4 takes the address of the match, adds the length of the match, and then subtracts 4 so that it’s pointing to the address passed to the call instruction
  • int32 is used to read the value.
    • Make sure to use int32 and NOT uint32 as the displacement value can be negative.

Next we need to get the address of the ensuing instruction. The formula is: starting address of the match + the length of the match:

console.hex("Next Instruction Address: ", @call_instr+!call_instr)

! gets the length of the match. The ?? ?? ?? ?? appended to the end of the string accounts for the length of the final instruction to make the length simpler to calculate.

Putting this all together, we get:

console.hex("Start of Function Address: ", @call_instr+!call_instr+int32(@call_instr+!call_instr-4))

The final rule looks like this:

import "console"

rule follow_the_fallchill_call
{
    /*
    0x10001030 C645EC78                      mov byte ptr [ebp - 0x14], 0x78
    0x10001034 C645ED29                      mov byte ptr [ebp - 0x13], 0x29
    0x10001038 C645EE2E                      mov byte ptr [ebp - 0x12], 0x2e
    0x1000103c C645EF4C                      mov byte ptr [ebp - 0x11], 0x4c
    0x10001040 C645F05D                      mov byte ptr [ebp - 0x10], 0x5d
    0x10001044 C645F1A3                      mov byte ptr [ebp - 0xf], 0xa3
    0x10001048 C645F2B5                      mov byte ptr [ebp - 0xe], 0xb5
    0x1000104c C645F3D0                      mov byte ptr [ebp - 0xd], 0xd0
    0x10001050 C645F467                      mov byte ptr [ebp - 0xc], 0x67
    0x10001054 C645F5F0                      mov byte ptr [ebp - 0xb], 0xf0
    0x10001058 C645F681                      mov byte ptr [ebp - 0xa], 0x81
    0x1000105c C645F7B7                      mov byte ptr [ebp - 9], 0xb7
    0x10001060 C645F836                      mov byte ptr [ebp - 8], 0x36
    0x10001064 C645F9E5                      mov byte ptr [ebp - 7], 0xe5
    0x10001068 C645FAD5                      mov byte ptr [ebp - 6], 0xd5
    0x1000106c C645FB93                      mov byte ptr [ebp - 5], 0x93
    0x10001070 6A10                          push 0x10
    0x10001072 8D4DEC                        lea ecx, [ebp - 0x14]
    0x10001075 51                            push ecx
    0x10001076 8D95E8FEFFFF                  lea edx, [ebp - 0x118]
    0x1000107c 52                            push edx
    0x1000107d E8DE0C0000                    call 0x10001d60
     */
    strings:
        $call_instr = {
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            C6 45 ?? ??
            6A ??
            8D 4D ??
            51
            8D 95 ?? ?? ?? ??
            52
            E8 ?? ?? ?? ??
        }
        $cmp = { 81 7D ?? 00 01 00 00 }
    
    condition:
          console.hex("Relative offset to function address: ", int32(@call_instr+!call_instr-4))
        and
          console.hex("Next Instruction Address: ", @call_instr+!call_instr)
        and
          console.hex("Start of Function: ", @call_instr+!call_instr+int32(@call_instr+!call_instr-4))
        and
          $cmp in (@call_instr+!call_instr+int32(@call_instr+!call_instr-4)..@call_instr+!call_instr+int32(@call_instr+!call_instr-4)+32)
}

The additional string $cmp = { 81 7D ?? 00 01 00 00 } was added to help demonstrate the point that we can go a step further and also look for string matches within a number of bytes from the start of the function address. The $cmp instruction is checking if the value stored in [ebp+var8] is 256 (0x100).

CMP with 256

At a cursory glance, this function looks to be setting up an RC4 substitution box. I leave that as an exercise for the reader to validate.

Reviewing the Rule Using ImHex

The figure below shows where the call to displacement address is located:

Call Displacement

Next, we can see the address of the following instruction:

Next Instruction

Finally, upon calculating 0xCDE+0x482, it takes us to the address of our function:

Start of Called Function

Also notice the first instance of $cmp is within 32 bytes from the start of the function.

Closing

Using YARA to follow a function call can be helpful for things like config extractors (malduck) or for creating very focused rules looking for particular traits.

Please reach out on twitter with any questions or comments. Happy YARA rule writing!

Primary Image generated by MidJourney using the blog’s title along with “cyberpunk, futuristic”

Recent Posts

Categories

About

Hosting my custom tools, threat research, and general reverse engineering notes.