TA505 Unpacker is a python 2.7 script that is able to unpack statically x86 TA505 packed Samples.
Link: https://twitter.com/Tera0017/status/1215655196690452480
13 January 2020
23 July 2019
ServHelper Vigenère encryption
MD5: eb7cdf5a96ae5f5a596a6ed423f786a7
Post: https://twitter.com/VK_Intel/status/1152669759382654976
ServHelper: https://www.proofpoint.com/us/threat-insight/post/servhelper-and-flawedgrace-new-malware-introduced-ta505
Threat: ServHelper
Campaign ID: "ju11"
Key: "exchanger"
Xor Key: "lol"
Vigenère Key: "XMGG"
C&Cs:
Post: https://twitter.com/VK_Intel/status/1152669759382654976
ServHelper: https://www.proofpoint.com/us/threat-insight/post/servhelper-and-flawedgrace-new-malware-introduced-ta505
Threat: ServHelper
Campaign ID: "ju11"
Key: "exchanger"
Xor Key: "lol"
Vigenère Key: "XMGG"
C&Cs:
- towerprod3[.]com/docs/saz.php
- lotmoji[.]com/docs/saz.php
- gdr55asf3145zadgsgsdw[.]icu/docs/saz.php
Previous ServHelper samples had all their strings unencrypted.
1.Early version example
As for the communication with the C&C observed only later versions of tunnel variant to encrypt/decrypt the request/response data with a simple xor function.
2. Xor encryption of request parameters values
In latest version of ServHelper (Campaign ju11) most of the string are encrypted with Vigenère cipher (Keys observed: XMGG, XBJB, ...)
3. Encrypted strings
IDA PRO Decryption script.
# ServHelper vigenere decrypt
# @Tera0017
from idc import *
from idaapi import *
def prepare_string(s, alphabet):
temp = s
alphaList = list(alphabet)
sList = list(temp)
for index in range(len(sList)):
for index2 in range(len(alphaList)):
if temp[index] == alphaList[index2]:
break
if index2 + 1 == len(alphaList):
sList.remove(temp[index])
final = ''.join(sList)
return final
def c2i(c, alphabet):
return alphabet.index(c)
def i2c(i, alphabet):
return alphabet[i]
def vigenere_decode(ciphertext, key, alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"):
cipherList = list(ciphertext)
keyList = list(key)
alphaList = list(alphabet)
decodeText = ""
for index in range(len(ciphertext)):
if ciphertext[index] not in alphaList:
decodeText += ciphertext[index]
else:
cipherIndex = c2i(cipherList[index], alphabet)
keyIndex = c2i((keyList[index % len(key)]), alphabet)
decodeTextIndex = (cipherIndex - keyIndex) % 26
decodeText += i2c(decodeTextIndex, alphabet)
return decodeText
def get_string(addr):
out = ""
bytes2zero = 0
while True:
if Byte(addr) != 0:
out += chr(Byte(addr))
bytes2zero = 0
else:
bytes2zero += 1
if bytes2zero == 2:
break
addr += 1
return out
def decrypt_str(addr, KEY):
print 'org:', '%08x ' % addr,
addr_c = addr
for i in range(5):
addr = idc.PrevHead(addr)
mnem = GetMnem(addr)
# X86 and X64 support
if ((mnem == "mov" and GetOpnd(addr, 0) == "eax" and i == 0 and 'offset' in GetOpnd(addr, 1)) or (mnem == "lea" and GetOpnd(addr, 0) == "rdx" and i == 1)):
off_addr = GetOperandValue(addr, 1)
encrypted = get_string(off_addr)
decrypted = vigenere_decode(encrypted, KEY)
print decrypted
MakeComm(addr_c, decrypted)
MakeComm(off_addr, decrypted)
break
elif i >= 1:
print 'Error finding encrypted'
break
# TODO find vigenere function and add address + add the Key
decrstr_func = 0x50f4d8
refs = [addr.frm for addr in XrefsTo(decrstr_func, flags=0)]
for ref in refs:
decrypt_str(ref, KEY='XMGG')
22 January 2019
GandCrab Version 5.1 "Anti-Disassembly"
MD5: 3f61255bbe14bc3ecae46c0c7fb7b6d0
POST: https://twitter.com/ValthekOn/status/1085896672897650688
Again new trick related to the anti-disassembly from the GandCrab Ransomware in version 5.1.
Below the "problem", which can be found in most of the functions.
IDA is not able to disassembly correctly the opcodes. Find below a manually "fixed" disassembly.
Part 1:
Explanation (Part 1):
1. Stores to ecx 0x1C3D2
2. Calls sub_40572E, and makes [esp] = 0x405723 (return pointer)
3. Adds to the return ptr [esp] + 0x12 == 0x405735. So ones the return instruction is found won't return to 0x405723 but to 0x405735.
4. Jumps to 0x405723
5. Compares the previously stored ecx value to 0x1FEFF58.
6. Will jump to 0x405719+2 as the previous comparison will set the sign flag.
4. Jumps to 0x405723
5. Compares the previously stored ecx value to 0x1FEFF58.
6. Will jump to 0x405719+2 as the previous comparison will set the sign flag.
Part 2:
Explanation (Part 2):
7. As mentioned in the 6th step the process will jump to the address 0x405719+2, the opcode at this location is C3, which is translated
to the retn instruction. Now the program will return to the new return pointer which was edited in 3rd step, 0x405735.
8. First instruction out of the anti-disassembly technique.
By replacing those hex values "B9D2C30100E80B00000081F958FFFE0178F079F8E983042412E2EFE8", with "90909090909090909090909090909090909090909090909090909090" we manage to fix the code to be viewable in IDA Graph view, able to decompile corectly and to be usable (without changing addresses).
IDA Graph View after applying the "fix"
1. Change Example (OEP)
2. Change Example (Now possible to decompile the code correctly)
Code to fix this "problem".
1: # GandCrabversion 5.1anti-dissasembly fix 2: import os 3: import sys 4: 5: import os.path 6: 7: def pre_checks(): 8: # pre-checks & returns in & out file 9: if len(sys.argv) not in [2,3]: 10: error_log('Too few or too many arguments, --help') 11: in_file = sys.argv[1] 12: try: 13: out_file = sys.argv[2] 14: except IndexError: 15: out_file = get_output(in_file) 16: if not os.path.isfile(in_file): 17: error_log("Input file doesn't exist") 18: exit() 19: return in_file, out_file 20: 21: def error_log(msg): 22: print msg 23: exit() 24: 25: def get_output(fname): 26: if not os.path.isfile(fname): 27: error_log("Input file doesn't exist") 28: path = os.path.dirname(os.path.realpath(fname)) 29: return path + "/output.bin" 30: 31: def main(in_, out_): 32: bytes1 = "\xB9\xD2\xC3\x01\x00\xE8\x0B\x00\x00\x00\x81\xF9\x58\xFF\xFE\x01\x78\xF0\x79\xF8\xE9\x83\x04\x24\x12\xE2\xEF\xE8" 33: bytes2 = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" 34: fp1 = open(in_, 'rb') 35: fd1 = fp1.read() 36: fp1.close() 37: fp2 = open(out_, 'wb') 38: fd2 = fd1.replace(bytes1, bytes2) 39: fp2.write(fd2) 40: print out_, " successfully created!!" 41: 42: if __name__ == "__main__": 43: in_file, out_file = pre_checks() 44: main(in_file, out_file)
05 December 2018
GandCrab "Anti-Disassembly"
I won't do an analysis on what GandCrab is, or how it works, there are some posts/papers related.
What I am going to assist, is the annoying "Anti-Disassembly" technique they are using, which breaks the IDA Graph view. This technique is used at least in the versions 5.x.x, I saw some post mentioning it also in the 4.x.x.
Below the "problem"(OEP function of GandCrab)
Same situation, we are going to find in the most GandCrab functions.
IDA doesn't disassembly this technique correctly, below is manually how the assembly should look.
What exactly is happening is:
By replacing those hex values "E9281458FFE000E9", with "909090E900000000" we manage to fix the code to be viewable in IDA Graph view as well as usable.
Code to fix this "problem".
What I am going to assist, is the annoying "Anti-Disassembly" technique they are using, which breaks the IDA Graph view. This technique is used at least in the versions 5.x.x, I saw some post mentioning it also in the 4.x.x.
Below the "problem"(OEP function of GandCrab)
Same situation, we are going to find in the most GandCrab functions.
IDA doesn't disassembly this technique correctly, below is manually how the assembly should look.
What exactly is happening is:
- With "call $+5" is jumping to EIP + 0x5 = 0x406231, but when using call the pointer to return is pushed to stack, so [esp] = 0x406231.
- Adds to the first stack value 0x11. In other words, adds to the return pointer of "call $+5" 0x11.
- Then depending on the "ZF" flag value it will jump to loc_40623D address, either by jnz or by jz instruction. Similar to those two jumps would be to have "jmp 40623D"
- With "pop eax", eax is receiving the previous addition 0x406231 + 0x11 = 0x406242 and then jumps to this address.
By replacing those hex values "E9281458FFE000E9", with "909090E900000000" we manage to fix the code to be viewable in IDA Graph view as well as usable.
1. Change Example (OEP function)
2. Change Example
3. Change Example
Code to fix this "problem".
1: # GandCrab anti-dissasembly fix
2: import os
3: import sys
4:
5: import os.path
6:
7: def pre_checks():
8: # pre-checks & returns in & out file
9: if len(sys.argv) not in [2,3]:
10: error_log('Too few or too many arguments, --help')
11: in_file = sys.argv[1]
12: try:
13: out_file = sys.argv[2]
14: except IndexError:
15: out_file = get_output(in_file)
16: if not os.path.isfile(in_file):
17: error_log("Input file doesn't exist")
18: exit()
19: return in_file, out_file
20:
21: def error_log(msg):
22: print msg
23: exit()
24:
25: def get_output(fname):
26: if not os.path.isfile(fname):
27: error_log("Input file doesn't exist")
28: path = os.path.dirname(os.path.realpath(fname))
29: return path + "/output.bin"
30:
31: def main(in_, out_):
32: bytes1 = "\xE9\x28\x14\x58\xFF\xE0\x00\xE9"
33: bytes2 = "\x90\x90\x90\xE9\x00\x00\x00\x00"
34: fp1 = open(in_, 'rb')
35: fd1 = fp1.read()
36: fp1.close()
37: fp2 = open(out_, 'wb')
38: fd2 = fd1.replace(bytes1, bytes2)
39: fp2.write(fd2)
40: print out_, " successfully created!!"
41:
42: if __name__ == "__main__":
43: in_file, out_file = pre_checks()
44: main(in_file, out_file)
Subscribe to:
Posts (Atom)











