Analysis of a Trojaned ssh/sshd
Tuesday, June 30, 2009
Some information about a trojaned ssh client and sshd server discovered in a recent compromise. I didn't find any details on this particular OpenSSH backdoor via Google, so hopefully this information will be of use to anyone who runs into it on their boxes.
The compromise of a group of small number of Linux boxes in a University unit was first discovered when the attackers began launching ssh brute force attacks from the boxes. Right off the bat, it was clear from the noisy activity that the attackers were not very sophisticated and did not recognize the immediate value of the compromised boxes.
Upon inspection of the filesystem, metadata showed that /usr/bin/ssh, /usr/sbin/sshd, and /usr/include/net/if_log.h had all been modified recently. While the attackers had taken care to ensure the modified ssh and sshd binaries matched their original file sizes, the modification times were not manipulated.
if_log.h, a fake header file stashed in /usr/include/net, was full of high entropy binary data and looked awfully suspicious:
00000000 bf 69 28 39 3c 4a af cc e9 28 ac 84 9a 8c f3 53 |.i(9<J...(.....S|
00000010 5e cb fa e4 5d ae be 0c 96 b0 5d ae 81 9f 7d 02 |^...].....]...}.|
00000020 0d 82 a7 54 ef 61 3e 32 aa 68 e6 83 57 17 db b6 |...T.a>2.h..W...|
00000030 f7 a4 6a 2f 5b d3 e7 18 34 3e bc 1d e8 b4 e0 e0 |..j/[...4>......|
Running a quick strings(1) on the modified sshd binary confimed the presence of the if_log.h string reference, strongly indicating that the if_log.h was an encrypted dump of passwords from the trojaned ssh/sshd.
Since we’d certainly like to know what users/passwords may have been stolen by the attackers in order to assess the risk to the rest of the network, I dug into the backdoored binaries for details. If we pull up the backdoored sshd in IDA and search for our “if_log.h” string, we find it in the rodata section along wth a number of other interesting strings:
The hardcoded IP address, FRM_IP/USER/PASS format string, and long hexadecimal key-looking values are obviously out of place in a sshd binary. Looking at the references to those strings, we find the sshpam_respond() from auth-pam.c function:
For those not familiar with the OpenSSH codebase, auth-pam.c contains the routines to handle standard password-based authentication, which is a logical place for inserting password logging code. We can see that our FRM_IP format string is being filled in via the sprintf() with the credentials of the user who just authenticated via PAM. Later in sshpam_respond(), we see that this string is dumped out to the if_log.h file:
Not only is the information being saved locally, but it is also being fired across the network via UDP to our mystery IP address, 63.118.58.1.
While we now know where the captured credentials are being stored/sent, we’ve yet to look at the routine that is encrypting this data before dumping it out. Before the trojaned routines fopen() the if_log.h to dump the credentials, we see a reference to the long hexadecimal string we encountered previously and a function call (sub_806a7a0). Sure looks like an encryption routine to me! The full disassembly of sub_806a7a0() follows:
.text:0806A7A0 ; int __cdecl sub_806A7A0(void *dest, int, size_t n, int)
.text:0806A7A0 sub_806A7A0 proc near ; CODE XREF: sub_8050900+20Ep
.text:0806A7A0 ; sub_8050900+403p ...
.text:0806A7A0
.text:0806A7A0 s = byte ptr -1E94E8h
.text:0806A7A0 var_1068 = byte ptr -1068h
.text:0806A7A0 var_20 = dword ptr -20h
.text:0806A7A0 var_1C = dword ptr -1Ch
.text:0806A7A0 var_18 = dword ptr -18h
.text:0806A7A0 var_14 = dword ptr -14h
.text:0806A7A0 var_10 = dword ptr -10h
.text:0806A7A0 dest = dword ptr 8
.text:0806A7A0 arg_4 = dword ptr 0Ch
.text:0806A7A0 n = dword ptr 10h
.text:0806A7A0 arg_C = dword ptr 14h
.text:0806A7A0
.text:0806A7A0 push ebp
.text:0806A7A1 mov ebp, esp
.text:0806A7A3 push edi
.text:0806A7A4 push esi
.text:0806A7A5 push ebx
.text:0806A7A6 sub esp, 1E94FCh
.text:0806A7AC mov edi, [ebp+arg_4]
.text:0806A7AF lea esi, [ebp+s]
.text:0806A7B5 lea ebx, [ebp+var_1068]
.text:0806A7BB mov [esp], esi
.text:0806A7BE mov [ebp+var_10], 0
.text:0806A7C5 mov dword ptr [esp+8], 1E8480h
.text:0806A7CD mov dword ptr [esp+4], 0
.text:0806A7D5 call _memset
.text:0806A7DA mov [esp], ebx
.text:0806A7DD mov [ebp+var_20], 0
.text:0806A7E4 mov [ebp+var_1C], 0
.text:0806A7EB mov [ebp+var_18], 0
.text:0806A7F2 mov [ebp+var_14], 0
.text:0806A7F9 mov dword ptr [esp+8], 1048h
.text:0806A801 mov dword ptr [esp+4], 0
.text:0806A809 call _memset
.text:0806A80E mov [esp+8], edi
.text:0806A812 mov [esp], edi
.text:0806A815 call _strlen
.text:0806A81A mov [esp], ebx
.text:0806A81D sub eax, 1
.text:0806A820 mov [esp+4], eax
.text:0806A824 call _BF_set_key
.text:0806A829 mov eax, [ebp+arg_C]
.text:0806A82C mov [esp+0Ch], ebx
.text:0806A830 mov [esp+4], esi
.text:0806A834 mov [esp+18h], eax
.text:0806A838 lea eax, [ebp+var_10]
.text:0806A83B mov [esp+14h], eax
.text:0806A83F lea eax, [ebp+var_20]
.text:0806A842 mov [esp+10h], eax
.text:0806A846 mov eax, [ebp+n]
.text:0806A849 mov [esp+8], eax
.text:0806A84D mov eax, [ebp+dest]
.text:0806A850 mov [esp], eax
.text:0806A853 call _BF_cfb64_encrypt
.text:0806A858 mov eax, [ebp+dest]
.text:0806A85B mov dword ptr [eax], 0
.text:0806A861 mov eax, [ebp+n]
.text:0806A864 mov [esp+4], esi
.text:0806A868 mov [esp+8], eax
.text:0806A86C mov eax, [ebp+dest]
.text:0806A86F mov [esp], eax
.text:0806A872 call _memcpy
.text:0806A877 mov [esp], esi
.text:0806A87A mov dword ptr [esp+8], 1E8480h
.text:0806A882 mov dword ptr [esp+4], 0
.text:0806A88A call _memset
.text:0806A88F add esp, 1E94FCh
.text:0806A895 pop ebx
.text:0806A896 pop esi
.text:0806A897 pop edi
.text:0806A898 pop ebp
.text:0806A899 retn
.text:0806A899 sub_806A7A0 endp
Translating this into some C code looks like:
int
haxor_blowfish(char *data, char *key, int len, int enc)
{
BF_KEY bf_key;
unsigned char retarded_buffer[2000000];
unsigned char ivec[8];
int num = 0;
memset(&ivec, 0, sizeof(ivec));
memset(&retarded_buffer, 0, sizeof(retarded_buffer));
memset(&bf_key, 0, sizeof(bf_key));
BF_set_key(&bf_key, strlen(key)-1, key);
BF_cfb64_encrypt(data, retarded_buffer, len, &bf_key, ivec, &num, enc);
memcpy(data, &retarded_buffer, len);
memset(&retarded_buffer, 0, sizeof(retarded_buffer));
return 0;
}
It's clear from some of the attributes of the code (2MB buffer on the stack, strlen(key)-1, unused IV, ending memsets) that the attackers (or at least the authors of the trojaned code) are not particularly sophiscated/skilled.
Running our decryption code reveals the plaintext of the affected usernames and passwords that have been captured by the trojaned binaries (NOTE: these usernames and passwords are fake and not the real ones involved in the compromise):
jonojono@dionysus ~/bfdecrypt $ ./bfdecrypt if_log.h.fake
FRM_IP: 141.212.110.114 -- USER: sushant -- PASS: tea4free
FRM_IP: 141.212.110.191 -- USER: kborders -- PASS: wEbtAprUlEz
TO_IP: 141.212.110.70 -- USER: zmao -- PASS: i-<3-beacons
The FRM_IP lines are credentials captured by incoming sshd server connections while the TO_IP lines are credentials captured by outgoing ssh client connections from the compromised machine.
Now that we know how the trojaned ssh/sshd binaries operated, we can widen our forensic investigation to the other hosts that may have been affected, notify the users that their credentials have been compromised, and start combing through NetFlow to determine who else may have talked to that destination IP address.