Reverse Engineering Techniques • Windows

Reversing Hash-Based API Resolution: No Imports, No Strings

Identify pre-computed checksum algorithms, trace hashing logic, and resolve Windows APIs dynamically in binaries stripped of import tables and strings.

GOAL

▪Identify use of pre-computed checksum values.
▪Trace through logic for creating pre-computed value.

Sample Metadata

Sections

Property.text (section[0]).data (section[1])
Name.text.data
SHA-256
3EE4FECB3F94E27DD276E02A0FAB102E1CECE0CB710F094339AAF5176D2C0CB4
4ED10B24C530B5C1F291ACB87127676CA0900F9FADC9E9B3C5D72B75F6B9982A
Entropy7.7882.760
File Ratio80.00%2.50%
Raw Address (Begin)0x000002000x00004200
Raw Address (End)0x000042000x00004400
Raw Size0x00004000 (16384 bytes)0x00000200 (512 bytes)
Virtual Address (Begin)0x000010000x00005000
Virtual Address (End)0x00004EFD0x0000512C
Virtual Size0x00003EFD (16125 bytes)0x0000012C (300 bytes)

Pestudio

What you are looking is a string obfuscation. This binary exhibits some strange charecterstics. Apparently doesn’t use any libraries, no imports

and finally no recognisable strings.

Screenshot 2026-05-26 at 9.47.11 PM.png
Figure: Screenshot 2026-05-26 at 9.47.11 PM.png Click to zoom ↗
Screenshot 2026-05-26 at 9.47.34 PM.png
Figure: Screenshot 2026-05-26 at 9.47.34 PM.png Click to zoom ↗

next step load this in IDA.

IDA Analysis

Screenshot 2026-05-26 at 9.55.56 PM.png
Figure: Screenshot 2026-05-26 at 9.55.56 PM.png Click to zoom ↗

From start there’s a call to sub_404D53 . We are going to trace in this call, But before you do that you will notice. There’s another problem with this binary that is the call that follows a call to EAX .This is an indirect function call. Because we don’t now necessarily the value of EAX register when the call is made. We can trace backwards and try figure out what’s in EAX.

C
.text:00404EED                 public start
.text:00404EED start:
.text:00404EED                 call    sub_404D53
.text:00404EF2                 push    0
.text:00404EF4                 push    dword_4050E4
.text:00404EFA                 pop     eax
.text:00404EFB                 call    **eax**

Instructions above is pop eax. Which means whatever was pushed on top of the stack is placed on to that register and called. Fortunately there’s push above that. push dword_4050E4 This push instruction is pushing 4 byte value on to the stack. Unfortunately this value is empty. Which means the function that is calling is populated later during program execution.

Screenshot 2026-05-26 at 10.31.08 PM.png
Figure: Screenshot 2026-05-26 at 10.31.08 PM.png Click to zoom ↗

Analysis of sub404D53

let’s navigate to the function sub_404D53 .

By the end of the first block you will notice, there’s another indirect call. That follows the same pattern of pushing and then poping a value into the register, that dword is not populated.

Screenshot 2026-05-26 at 10.34.02 PM.png
Figure: Screenshot 2026-05-26 at 10.34.02 PM.png Click to zoom ↗

So this helps us to isolate Where these function pointer’s are being resolved.

There’s only 2 function call before this indirect call. A call to sub_401728 and sub_40175E . So it has to happen between one of these function.

C

**.text:00404D61                 call    sub_401728**
.text:00404D66                 lea     esi, unk_405078
.text:00404D6C                 lea     edi, dword_4050D4
.text:00404D72                 push    ebp
.text:00404D73                 mov     ebp, eax
**.text:00404D75                 call    sub_40175E**
Screenshot 2026-05-26 at 10.38.06 PM.png
Figure: Screenshot 2026-05-26 at 10.38.06 PM.png Click to zoom ↗

Analysis of sub401728

Before this function is called there’s a mov instruction.

C
.text:00404D5C                 mov     ebx, 4B1FFE8Eh
.text:00404D61                 call    sub_401728
Screenshot 2026-05-26 at 10.42.36 PM.png
Figure: Screenshot 2026-05-26 at 10.42.36 PM.png Click to zoom ↗

In which the value 4B1FFE8Eh is moved into EBX. That seem rather purposeless at this point of time. So we can trace into the function sub_401728 and see if the ebx register is used.

Analysis of sub401728

If you move a little bit down. you will see ebx is acutally used in a comparison.

ebx is being compared to edx

C
.text:00401754 loc_401754:                             ; CODE XREF: sub_401728+19↑j
**.text:00401754                 cmp     edx, ebx**
.text:00401756                 mov     eax, [edi+10h]
.text:00401759                 mov     edi, [edi]
Screenshot 2026-05-26 at 10.48.20 PM.png
Figure: Screenshot 2026-05-26 at 10.48.20 PM.png Click to zoom ↗

So we know the ebx contains what appears to be random 4 bytes value.

What’s in edx? .

Highlighting edx is allows us to trace where edx is used.

By the way I switched to graph mode in IDA.

Screenshot 2026-05-26 at 10.54.31 PM.png
Figure: Screenshot 2026-05-26 at 10.54.31 PM.png Click to zoom ↗

We want to figure out what value is put into edx before the comparison.

Well We can see that before this comparison edx is used in rol instruction that’s rotate left .

edx is rotated left by 7 bits. after that the lower 8 bits of edx, dl is xor with the value in al. After that a jump is taken which will take us back at the beginning of our loop.

C
loc_40174D:
rol     edx, 7
xor     dl, al
jmp     short loc_40173D
Screenshot 2026-05-26 at 11.02.06 PM.png
Figure: Screenshot 2026-05-26 at 11.02.06 PM.png Click to zoom ↗

So now we want to figure out what’s in al .

If we highlight al you will see al is used in couple of different instructions.

Screenshot 2026-05-26 at 11.05.11 PM.png
Figure: Screenshot 2026-05-26 at 11.05.11 PM.png Click to zoom ↗

The first is the compare al with 41h.

C
cmp     al, 41h ; 'A'
jb      short loc_40174D

Next is to compare al to 5A.

C
cmp     al, 5Ah ; 'Z'
ja      short loc_40174D

Notice the condition.

Jump below and jump above. What this is saying is the value of al is in between 41 hex or 5a in hex. we are gonna enter this condition and perform an or operation with the value of al in 20 hex. What this amounts to is ensuring that the value on al which is going to be a letter. And you will see why in just a moment.

That if it’s capital it is now lowered case. So this a quick and easy way of ensuring that regardless of the capitalization of a string it’s all lowered case letter by the time we entered the loop below. To do the rotate and xor instruction.

Note this not always immediately obvious and simple. And we are still not ensure what value is in the al register.

So we are gonna continue to trace backwards in this function.

Doing so will bring us where the condition for the loop is tested.

C
loc_40173D:
lodsw
test    al, al
jz      short loc_401754
Screenshot 2026-05-26 at 11.29.42 PM.png
Figure: Screenshot 2026-05-26 at 11.29.42 PM.png Click to zoom ↗
Address / LabelInstructionExplanation
loc_40173D:lodswLoads a word (2 bytes) from memory at [DS:SI] into register AX. · Then automatically increments SI by 2.
-test al, alTests if the lower byte AL is zero (AL == 0). · Sets the Zero Flag (ZF) if AL is 0.
-jz short loc_401754Jump if Zero - If AL is 0 (i.e., null byte found), jump to loc_401754. · Otherwise, continue to the next instruction.

Here we have the instruction lodsw That’s load string word. The al register is tested after that then finally we have a conditional jump.

With the lodsw instruction it takes a value at esi and treating it as a string pointer. Loads whatever the size in this case word into the EAX register.

The test instruction looks at the value of register, and set’s the appropriate flags. based up of the results. In this case we will only take a jump when al is zero’s value. If this is an ASCII strings and ascii strings are null terminator. Then this check is looking for when we reached the end of an ascii string.

The jump that we take is to the location where we do the comparison between a ebx and edx.

Screenshot 2026-05-26 at 11.42.05 PM.png
Figure: Screenshot 2026-05-26 at 11.42.05 PM.png Click to zoom ↗

And while we still haven’t confirmed 100% that we have an ascii strings everything is starting to make sense.

We are making sure that the character’s are all lowered case, we are rotating the bit’s. So we are doing some bit manipulation. and after a null byte is found.

The value in edx is compared with the value in ebx. The lodsw instruction expects a pointer to be in esi. And if we continue to trace a little bit further up in this function. we’ll see that we have a mov instruction into esi, [edi + 28h]

C
loc_401738:
**mov     esi, [edi+28h]**
xor     edx, edx
Screenshot 2026-05-26 at 11.51.52 PM.png
Figure: Screenshot 2026-05-26 at 11.51.52 PM.png Click to zoom ↗

Now in order to understand how we got a string value into [edi + 28h] . We need to continue the investigate the instructions above.

In this case we can set a breakpoint on this mov instruction and see what value is in esi .

Address of the mov instruction:

C
.text:00401738                 mov     esi, [edi+28h]
Screenshot 2026-05-26 at 11.56.10 PM.png
Figure: Screenshot 2026-05-26 at 11.56.10 PM.png Click to zoom ↗

Windbg

let’s fire up windbg

set break point on move instruction.

C
0:000> bp 00401738

and run it.

Screenshot 2026-05-27 at 12.02.36 AM.png
Figure: Screenshot 2026-05-27 at 12.02.36 AM.png Click to zoom ↗

Since i set the breakpoint in mov instructions it hasn’t been executed yet.

So we will step one more time then we will inspect the contents of esi

now we can see value , esi contents 007227AA .

C
ESI: 007227AA  
Screenshot 2026-05-27 at 12.11.48 AM.png
Figure: Screenshot 2026-05-27 at 12.11.48 AM.png Click to zoom ↗

Windbg offers series of memory inspection commands da is one of those.

in which it will take as an argument a pointer to what we think as ascii string and it’ll try to print those value to our command output window.

if we ran the command.

C
0:000> da 007227AA

we will only get a single character.

Screenshot 2026-05-27 at 12.18.47 AM.png
Figure: Screenshot 2026-05-27 at 12.18.47 AM.png Click to zoom ↗

We can also inspect the memory using memory inspection commands.

dd allows us to dump dword values.

C
0:000> dd
007227ac  006d0061 006c0070 002e0065 00780065
007227bc  00000065 003a0043 0055005c 00650073
007227cc  00730072 0072005c 00640065 00650074
007227dc  006d0061 0044005c 00730065 0074006b
007227ec  0070006f 0073005c 006d0061 006c0070
007227fc  002e0065 00780065 00000065 003a0043
0072280c  0055005c 00650073 00730072 0072005c
0072281c  00640065 00650074 006d0061 0044005c

it will give you a series of null bytes followed by a single byte value. S what we are dealing with is uncode or multibyte string.

The null bytes becomes a problem because when dumping ascii strings The null bytes indicate the end of the strings.

Screenshot 2026-05-27 at 12.20.58 AM.png
Figure: Screenshot 2026-05-27 at 12.20.58 AM.png Click to zoom ↗

windbg also offers to inspect multibyte strings using du provifding the same argument.

we will get the full string.

C
0:000> du 007227AA
007227aa  "sample.exe"
Screenshot 2026-05-27 at 12.32.40 AM.png
Figure: Screenshot 2026-05-27 at 12.32.40 AM.png Click to zoom ↗

So what this function is doing. Is just walking through each executable and dll in memory getting the name of that executable and then computing the checksum value.

This sample is also obfuscating it’s function calls. There are two steps to resolving a function.

▪1st is find the module or dll in memory
▪2nd is walking the export table.

Stay tune for next part.

PART-2: https://github.com/Lynk4/mare/tree/main/Malware%20Analysis/Windows/Dynamic%20API%20Resolution

Copied