YARA - Following FALLCHILL's E8 Call
Following a near relative call address
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
.
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 instructionint32
is used to read the value.- Make sure to use
int32
and NOTuint32
as the displacement value can be negative.
- Make sure to use
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).
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:
Next, we can see the address of the following instruction:
Finally, upon calculating 0xCDE+0x482
, it takes us to the address of our 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”