Anonymous ID: 76074b May 5, 2020, 5:10 p.m. No.9044970   🗄️.is 🔗kun   >>5025 >>5133 >>5272 >>5343 >>5569 >>5607 >>5619 >>5624

Case Study for SRE

 

Anyone interested in Ghidra, pay attention. I'll walk you through the example posted by >>9043113 and show you how to approach SRE. I'm including the poster's screenshot in this post and will be referencing it heavily.

 

The middle pane of Ghidra contains the x86 disassembly output and indicates a function call is made. The compiler strips the original function names and uses one better suited for machines, and I believe Ghidra gives the function the name you see. That's why it's called FUN_140001008() and not DoCoolThing(), like a human might write. We can see the function has four arguments.

 

The right pane is most useful right now: it contains the function being called. The function was written in C++ (usage of wchar_t * is telling), and the code is reconstructed from the binary as best as possible. However, the compiler stripped out the human-given variable names, so we need to think more generally to tease out the purpose of the function.

 

We can immediately identify a few attributes of FUN_140001008:

  • It takes four arguments: a string or string buffer, an integer, a string or string buffer, and an 8-byte value.

  • It returns one value: an 8-byte value.

 

Let's now work through FUN_140001008. Go step by step until you fully understand why these statements are true. I'm quite confident with this analysis:

  • lines 5-8: declaring variables to be used in the function. Note the data types.

  • line 10: initialize uVar2 to 0 (could mean anything).

  • line 11: negative value check. This statement will only evaluate true if param_2's top bit is set, which indicates a negative number when using signed integers.

  • line 12: set uVar2 to some large value (unclear why yet).

  • line 14: this statement will evaluate true if the value of uVar2 is negative.

  • line 15: sets value of param_1 to an empty string (the '\0' is a null-terminating character for C-style strings). The "L" indicates it is a literal value.

  • line 19: the else block is the normal execution flow. From above, we will now be assuming param_2 is a non-negative integer value.

  • line 22: call _vsnwprintf. _vsnwprintf is defined here: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/vsnprintf-vsnprintf-vsnprintf-l-vsnwprintf-vsnwprintf-l?view=vs-2019

  • line 22 (cont.): write elements in local_res20 according to the format specified by param_3 and copy _Count elements to param_1.

  • line 23: true if the copy operation in line 22 either failed or didn't copy _Count elements.

  • line 24: terminate the string at param_1.

  • line 25: set uVar 2 to a different large value

  • line 29: checks if the number of characters copied equals the number of characters desired to be copied.

  • line 30: terminate the string at param_1.

  • line 35: return the value of uVar2.

 

Based off all this, I can tell a few things:

  • uVar2 is a status code indicator. 0 is returned if successful, while a non-zero value is returned if it fails. The non-zero values are unique per failure mode. This is conventional practice.

  • The heavy lifting is done on line 22, and that's just copying characters from one place to another in a particular way. Most of the code is for error handling.

  • Per the documentation for _vsnwprintf, param_1 is the destination buffer.

  • Based on the usage of _Count in the program, I infer it indicates the quantify of characters to copy. _Count is set to one less than the length of the string, which would omit the null-terminating character at the end, a desirable behavior.

  • Based on the usage of local_res20 in line 22, I infer param_4 is a 64-bit memory address (note the dereferencing and type casting into a va_list).

 

Baseline functional description: the function takes some input values(s) and combines them into a single string according to a previously-specified format.

 

The likely candidate for this function is vsnprintf(). Example usage of the function can be found here: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/vsnprintf-vsnprintf-vsnprintf-l-vsnwprintf-vsnwprintf-l?view=vs-2019#example-1

 

My reasoning largely stems from the error handling, the fact that _vsnwprintf is the only meaty line in here, and the fact the vsnprintf() can be used publicly.

 

Hope you found this instructive.