The descendant of an “obscene” trojan or how to steal passwords on FTP.
Yesterday I parsed the “obscene” trojan (http://vilgeforce.habrahabr.ru/blog/44130.html), and today I am cutting its descendant ftp34.dll. This creature, by the way, is much more interesting than the vast majority of trojans. At least by the fact that it steals information not from a disk, but directly from network traffic. How? Look under the cat.
In the first series, one of the components of the Troyan Zalupko complex was almost completely dismantled. He dropped the ftp34.dll file onto the disk and loaded it using LoadLibrary. And this happened with every activation of the trojan.
ftp34.dll is a file of 4608 bytes in size, is packaged by UPX, therefore it is unpacked in Linux (I'm at home in Linux) in 5 seconds. The unpacked file weighs 7608 bytes. The DLL, like its dropper, uses the same principle of encrypting strings - XOR with one byte, the function code is the same. The script for IDA is written in a minute.
The IDA carefully folded the entry point code by moving the cursor to DllMain. In DllMain, the reason for the call is checked: if the library is loading, some actions are performed, if unloading, others. Since there were fewer calls for unloading, I started from this piece. In the Detach product (let's call it that) - a strange code:
.text: 10001D2F push eax; lpNumberOfBytesWritten
.text: 10001D30 push 6; nSize
.text: 10001D32 push offset unk_1001213C; lpBuffer
.text: 10001D37 push lpBaseAddress; lpBaseAddress
.text: 10001D3D push 0FFFFFFFFh; hProcess
.text: 10001D3F call ds: WriteProcessMemory
and it is repeated 4 times. If I do not confuse anything, then hProcess equal to -1 means writing to its own address space. A quick analysis of the references to the addresses of the buffer for reading and writing showed that the DLL is quite actively reading / writing to them using Read / WriteProcessMemory. Open question “Why?” I'll leave it for later.
Download Actions
Like its "parent", this library prepares lines containing the paths to the necessary files:% TEMP% \ r43q34.tmp and% TEMP% \ mpz.tmp. The presence in the system of other instances of the library is determined using the mutex, and if it is not, the thread Thread1 is created. I do not give the names of the mutexes, because I doubt that someone will check the system for their presence :-) Now - the most interesting! The library PATCHES Windows Sockets functions in memory. How does this happen? Here is the code:
.text: 10001B72 push 4; int
.text: 10001B74 push offset aSw676Hh; "Ws2_32.dll"
.text: 10001B79 call decryptXor
.text: 10001B7E pop ecx
.text: 10001B7F pop ecx
.text: 10001B80 push eax; lpModuleName
.text: 10001B81 call ds: GetModuleHandleA; We
get the ws2_32.dll .text handle: 10001B87 mov [ebp + ws2_32handle], eax
.text: 10001B8A push 5; int
.text: 10001B8C push offset aWFs; "Recv"
.text: 10001B91 call decryptXor
.text: 10001B96 pop ecx
.text: 10001B97 pop ecx
.text: 10001B98 push eax; lpProcName
.text: 10001B99 push [ebp + ws2_32handle]; hModule
.text: 10001B9C call ds: GetProcAddress; We
get the address of the recv .text function : 10001BA2 mov recvAddr, eax
.text: 10001BA7 lea eax, [ebp + NumberOfBytesWritten]
.text: 10001BAA push eax; lpNumberOfBytesRead
.text: 10001BAB push 6; nSize
.text: 10001BAD push offset originalCode; lpBuffer
.text: 10001BB2 push recvAddr; lpBaseAddress
.text: 10001BB8 push 0FFFFFFFFh; hProcess
.text: 10001BBA call ds: ReadProcessMemory; Read the first 6 bytes of the recv
.text function into the originalCode buffer : 10001BC0 mov HookCode, 68h; In the buffer written to the beginning of recv (), we put the opcode of the push
.text command : 10001BC7 mov dword ptr HookCode + 1, offset newRecv; Following push is the address of our new
.text handler : 10001BD1 mov HookCode + 5, 0C3h; And now RET
.text: 10001BD8 lea eax, [ebp + NumberOfBytesWritten]
.text: 10001BDB push eax; lpNumberOfBytesWritten
.text: 10001BDC push 6; nSize
.text: 10001BDE push offset HookCode; lpBuffer
.text: 10001BE3 push recvAddr; lpBaseAddress
.text: 10001BE9 push 0FFFFFFFFh; hProcess
.text: 10001BEB call ds: WriteProcessMemory; We write our insert at the beginning of recv (). It is done!
Alas, it turned out to be much less readable than in the IDA :-( In short: we got the address of the desired function. We read 6 bytes from this address, prepared a buffer with the
push offset code myRecv
ret
and recorded it at the beginning of the intercepted procedure. The combination of push-ret - switching to the address we need is not in an entirely obvious way. The following functions are intercepted: recv (), WSARecv (), WSASend (), send (). Now it has become clear that this is written to memory when the DLL is unloaded: it restores the original code of the intercepted functions. The main question remains - how is control transferred to the original functions? So what about Thread1? She installs her handler (which, incidentally, will not give anything criminal) using SetWindowsHookEx. What for? I don’t know for sure ... But yes, I think it doesn’t matter.
Interceptor functions
All interceptors have much in common: these are short procedures containing, roughly speaking, only 2 calls. The first call is the same for send () and WSASend () interceptors, and the second is for recv () and WSARecv (), that is, functional separation. I will name these two functions HookSend and HookRecv respectively. The second call in the interceptors is different, it is a function call that patches the intercepted functions to the initial state, calls them, and then patches them to the interceptor option.
The HookSend () and HookRecv () functions receive three parameters - socket, buffer, and length. The initial code also matches: we get the address to which the socket is connected, we convert this address to a string, and also translates the address from the network byte order to the host one. Here a moment that is not quite clear to me arises:
.text: 100015CA push [ebp + s]; s
.text: 100015CD call ds: getpeername
.text: 100015D3 push dword ptr [ebp + name.sa_data + 2]; in
.text: 100015D6 call ds: inet_ntoa
.text: 100015DC push eax; Source
.text: 100015DD push offset byte_10011C10; Dest
.text: 100015E2 call strcpy
.text: 100015E7 pop ecx
.text: 100015E8 pop ecx
.text: 100015E9 push dword ptr [ebp-12h]; netshort
.text: 100015EC call ds: ntohs
.text: 100015F2 movzx eax, ax
.text: 100015F5 cmp eax, 25
.text: 100015F8 jnz short loc_10001607
s is the socket. I don’t understand how we get the port after calling ntohs in ax? Or will there really be a port and I read bad docks? In general, intuition and familiar numbers (25, 80, 110 :-) suggested that there was a check of the port to which the connection was made. For reception, traffic is intercepted on the following ports: 25, 80, 110. For transmission: 25, 80, 21. Moreover, traffic on the 21st port is processed somehow tricky. Transmission over port 80 seems to make some changes to traffic: if the string “gzip,” is found in the transmitted data, it will be replaced by 5 bytes with the code 0x6E (“n”). What for? I don’t know ... At this point, only the search procedures for the transmitted passwords for FTP and mail addresses, as well as the recording of this stuff into files, were not parsed. Lines for the theft of email passwords are not visible, as well as the code sending the collected information over the network.
If such an infection gets widespread, then no recommendations from Pinch such as “Do not store passwords on disks” will help. All that remains is the transition to encrypted communication channels. But given the interception of all traffic, this, I think, will help little :-(
What is not clear and strange to me in all of this:
1) WriteProcessMemory uses -1 as a handle. Why I doubt that calls for all applications will be intercepted.
2) Why is SetWindowsHookEx used?
3) Is trojan activity detected by behavioral analyzers? In general, at least some kind of software (except for signature search).
4) How do so many people learn about my post? As far as I know, less than 10 people read me :-D
Analysis time is about 2 hours (while responding to comments). Tools - UPX + IDA Pro + OllyDbg (you could have done without it), a head with brains.
In the first series, one of the components of the Troyan Zalupko complex was almost completely dismantled. He dropped the ftp34.dll file onto the disk and loaded it using LoadLibrary. And this happened with every activation of the trojan.
ftp34.dll is a file of 4608 bytes in size, is packaged by UPX, therefore it is unpacked in Linux (I'm at home in Linux) in 5 seconds. The unpacked file weighs 7608 bytes. The DLL, like its dropper, uses the same principle of encrypting strings - XOR with one byte, the function code is the same. The script for IDA is written in a minute.
The IDA carefully folded the entry point code by moving the cursor to DllMain. In DllMain, the reason for the call is checked: if the library is loading, some actions are performed, if unloading, others. Since there were fewer calls for unloading, I started from this piece. In the Detach product (let's call it that) - a strange code:
.text: 10001D2F push eax; lpNumberOfBytesWritten
.text: 10001D30 push 6; nSize
.text: 10001D32 push offset unk_1001213C; lpBuffer
.text: 10001D37 push lpBaseAddress; lpBaseAddress
.text: 10001D3D push 0FFFFFFFFh; hProcess
.text: 10001D3F call ds: WriteProcessMemory
and it is repeated 4 times. If I do not confuse anything, then hProcess equal to -1 means writing to its own address space. A quick analysis of the references to the addresses of the buffer for reading and writing showed that the DLL is quite actively reading / writing to them using Read / WriteProcessMemory. Open question “Why?” I'll leave it for later.
Download Actions
Like its "parent", this library prepares lines containing the paths to the necessary files:% TEMP% \ r43q34.tmp and% TEMP% \ mpz.tmp. The presence in the system of other instances of the library is determined using the mutex, and if it is not, the thread Thread1 is created. I do not give the names of the mutexes, because I doubt that someone will check the system for their presence :-) Now - the most interesting! The library PATCHES Windows Sockets functions in memory. How does this happen? Here is the code:
.text: 10001B72 push 4; int
.text: 10001B74 push offset aSw676Hh; "Ws2_32.dll"
.text: 10001B79 call decryptXor
.text: 10001B7E pop ecx
.text: 10001B7F pop ecx
.text: 10001B80 push eax; lpModuleName
.text: 10001B81 call ds: GetModuleHandleA; We
get the ws2_32.dll .text handle: 10001B87 mov [ebp + ws2_32handle], eax
.text: 10001B8A push 5; int
.text: 10001B8C push offset aWFs; "Recv"
.text: 10001B91 call decryptXor
.text: 10001B96 pop ecx
.text: 10001B97 pop ecx
.text: 10001B98 push eax; lpProcName
.text: 10001B99 push [ebp + ws2_32handle]; hModule
.text: 10001B9C call ds: GetProcAddress; We
get the address of the recv .text function : 10001BA2 mov recvAddr, eax
.text: 10001BA7 lea eax, [ebp + NumberOfBytesWritten]
.text: 10001BAA push eax; lpNumberOfBytesRead
.text: 10001BAB push 6; nSize
.text: 10001BAD push offset originalCode; lpBuffer
.text: 10001BB2 push recvAddr; lpBaseAddress
.text: 10001BB8 push 0FFFFFFFFh; hProcess
.text: 10001BBA call ds: ReadProcessMemory; Read the first 6 bytes of the recv
.text function into the originalCode buffer : 10001BC0 mov HookCode, 68h; In the buffer written to the beginning of recv (), we put the opcode of the push
.text command : 10001BC7 mov dword ptr HookCode + 1, offset newRecv; Following push is the address of our new
.text handler : 10001BD1 mov HookCode + 5, 0C3h; And now RET
.text: 10001BD8 lea eax, [ebp + NumberOfBytesWritten]
.text: 10001BDB push eax; lpNumberOfBytesWritten
.text: 10001BDC push 6; nSize
.text: 10001BDE push offset HookCode; lpBuffer
.text: 10001BE3 push recvAddr; lpBaseAddress
.text: 10001BE9 push 0FFFFFFFFh; hProcess
.text: 10001BEB call ds: WriteProcessMemory; We write our insert at the beginning of recv (). It is done!
Alas, it turned out to be much less readable than in the IDA :-( In short: we got the address of the desired function. We read 6 bytes from this address, prepared a buffer with the
push offset code myRecv
ret
and recorded it at the beginning of the intercepted procedure. The combination of push-ret - switching to the address we need is not in an entirely obvious way. The following functions are intercepted: recv (), WSARecv (), WSASend (), send (). Now it has become clear that this is written to memory when the DLL is unloaded: it restores the original code of the intercepted functions. The main question remains - how is control transferred to the original functions? So what about Thread1? She installs her handler (which, incidentally, will not give anything criminal) using SetWindowsHookEx. What for? I don’t know for sure ... But yes, I think it doesn’t matter.
Interceptor functions
All interceptors have much in common: these are short procedures containing, roughly speaking, only 2 calls. The first call is the same for send () and WSASend () interceptors, and the second is for recv () and WSARecv (), that is, functional separation. I will name these two functions HookSend and HookRecv respectively. The second call in the interceptors is different, it is a function call that patches the intercepted functions to the initial state, calls them, and then patches them to the interceptor option.
The HookSend () and HookRecv () functions receive three parameters - socket, buffer, and length. The initial code also matches: we get the address to which the socket is connected, we convert this address to a string, and also translates the address from the network byte order to the host one. Here a moment that is not quite clear to me arises:
.text: 100015CA push [ebp + s]; s
.text: 100015CD call ds: getpeername
.text: 100015D3 push dword ptr [ebp + name.sa_data + 2]; in
.text: 100015D6 call ds: inet_ntoa
.text: 100015DC push eax; Source
.text: 100015DD push offset byte_10011C10; Dest
.text: 100015E2 call strcpy
.text: 100015E7 pop ecx
.text: 100015E8 pop ecx
.text: 100015E9 push dword ptr [ebp-12h]; netshort
.text: 100015EC call ds: ntohs
.text: 100015F2 movzx eax, ax
.text: 100015F5 cmp eax, 25
.text: 100015F8 jnz short loc_10001607
s is the socket. I don’t understand how we get the port after calling ntohs in ax? Or will there really be a port and I read bad docks? In general, intuition and familiar numbers (25, 80, 110 :-) suggested that there was a check of the port to which the connection was made. For reception, traffic is intercepted on the following ports: 25, 80, 110. For transmission: 25, 80, 21. Moreover, traffic on the 21st port is processed somehow tricky. Transmission over port 80 seems to make some changes to traffic: if the string “gzip,” is found in the transmitted data, it will be replaced by 5 bytes with the code 0x6E (“n”). What for? I don’t know ... At this point, only the search procedures for the transmitted passwords for FTP and mail addresses, as well as the recording of this stuff into files, were not parsed. Lines for the theft of email passwords are not visible, as well as the code sending the collected information over the network.
If such an infection gets widespread, then no recommendations from Pinch such as “Do not store passwords on disks” will help. All that remains is the transition to encrypted communication channels. But given the interception of all traffic, this, I think, will help little :-(
What is not clear and strange to me in all of this:
1) WriteProcessMemory uses -1 as a handle. Why I doubt that calls for all applications will be intercepted.
2) Why is SetWindowsHookEx used?
3) Is trojan activity detected by behavioral analyzers? In general, at least some kind of software (except for signature search).
4) How do so many people learn about my post? As far as I know, less than 10 people read me :-D
Analysis time is about 2 hours (while responding to comments). Tools - UPX + IDA Pro + OllyDbg (you could have done without it), a head with brains.