I found that someone was downloading some encoded malware to a WordPress honey pot that I have. This is the story of how I figured out the encoding.
I also note that this implies someone actually uses the 4.x series of WSO web shells in the wild. That development is available on github, so it shouldn't be surprising.
The encrypting method is base64 encode/XOR/base 64 decode.
-
Tried to figure out encoding. I used Wikipedia to find simple ciphers under the theory that malware authors aren't very sophisticated. I tried Caesar cipher, Vigenere cipher, affine cipher. Vigenere cipher with byte value transpositions (rather than rotations) seemed to provide the best match to Hamming distance and Index of Coincidence vs key length.
I decided that the encoded files probably had an 8-byte or 8-position key.
I could not get a Vigenere or byte-transposition key that worked. In fact, I could only get a single position Vigenere, or single transposition to decode, even from a large amount of known plaintext to give me byte-value-frequencies. It seems that it's not as easy to decipher these as is commonly said.
I put my code for this examination. on github in a separate project.
-
I posted to /r/Malware to see if anybody had any ideas. I read and consider the replies. I tried some things, input differential xor and CyberChef, based on the replies.
None of them seem promising, as the Hamming distance or Index of Coincidence of encoded PHP didn't seem to match what I saw in the downloaded files.
-
I remembered a cache of WSO instances I had not reviewed. Found this decrypting PHP code in WSO 4.1.1 and 4.2.5:
function decrypt($str, $pwd) { $pwd = base64_encode($pwd); $str = base64_decode($str); $enc_chr = ''; $enc_str = ''; $i = 0; while ($i < strlen($str)) { for ($j = 0; $j < strlen($pwd); $j++) { $enc_chr = chr(ord($str[$i]) ^ ord($pwd[$j])); $enc_str .= $enc_chr; $i++; if ($i >= strlen($str)) { break; } } } return base64_decode($enc_str); }
-
WSO 4.x function
decrypt()
usesbase64_encode(md5($_REQUEST['HTTP_USER_AGENT']))
as an xor-key (see here.
-
Does a
base64_decode()
after xor-ing with key -
Does not work on the data I have. The "a" POST parameter doesn't decode to anything.
Seems like a weird choice: always ends up with a 44-byte key, only 16 different byte values in the
md5()
output, only 64 byte values inbase64_encode()
output.
-
The "a" POST parameter decodes to 4 bytes. That implies a 3-character result after xor-with-key and
base64_decode()
, since base64-encoded substitutes 4 encoded bytes for 3 cleartext bytes.There are only two, 3-letter "actions" in most/all WSO variants. WSO 4.1.1 and 4.2.5 keep the WSO structure of calling a function based on value of "a" POST parameter.
function actionPhp()
function actionSql()
-
Luck and a lucky guess
base64_encode("Php")
- 4 characters.
- I found a 4-byte xor-key that got me those 4 characters with base64-decoded "a" value
- I used that 4-byte xor-key on base64 decoded "p1" strings
- One of them has
$u0
as first 3 characters, one of themfun
-
Assuming that "fun" means the next 5 characters are "ction" calculate 4 more xor-key bytes
-
CyberChef recipe (https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)XOR(%7B'option':'Latin1','string':''%7D,'Standard',false)From_Base64('A-Za-z0-9%2B/%3D',true)) decrypts the entire 4 or 5 lines of "p1" value with an 8-character xor-key: "bWtmaWxl"
That 8-character xor-key works on all of the encoded downloads I have.
I found several instances of mumblehard code, and a few other things I need to look at in more detail.