Advertisement
aaSSfxxx

Andromeda 20.7 bot config extractor

Apr 16th, 2013
66
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.08 KB | None | 0 0
  1. #!/usr/bin/python2
  2. #-*- coding: utf-8 -*-
  3.  
  4. # Andromeda 2.07 botnet configuration extractor
  5. # Written by aaSSfxxx for phun and chocapicz :þ
  6. # Decompression algorithm taken from http://goo.gl/UTP6o and modified to
  7. # match Andromeda's aPLib variant.
  8. # And as usual, I'm using pefile to load the malware :D
  9.  
  10. import pefile
  11. import struct
  12. import sys
  13.  
  14. # aPLib variant used by Andromeda (jCalc1)
  15. def int2lebin(value, size):
  16.     """ouputs value in binary, as little-endian"""
  17.     result = ""
  18.     for i in xrange(size):
  19.         result = result + chr((value >> (8 * i)) & 0xFF )
  20.     return result
  21.  
  22. def modifystring(s, sub, offset):
  23.     """overwrites 'sub' at 'offset' of 's'"""
  24.     return s[:offset] + sub + s[offset + len(sub):]
  25.  
  26. def getbinlen(value):
  27.     """return the bit length of an integer"""
  28.     result = 0
  29.     if value == 0:
  30.         return 1
  31.     while value != 0:
  32.         value >>= 1
  33.         result += 1
  34.     return result
  35.  
  36. class _bits_decompress():
  37.     """bit machine for variable-sized auto-reloading tag decompression"""
  38.     def __init__(self, data, tagsize):
  39.         self.__curbit = 0
  40.         self.__offset = 0
  41.         self.__tag = None
  42.         self.__tagsize = tagsize
  43.         self.__in = data
  44.         self.out = ""
  45.  
  46.     def getoffset(self):
  47.         """return the current byte offset"""
  48.         return self.__offset
  49.  
  50.     def read_bit(self):
  51.         """read next bit from the stream, reloads the tag if necessary"""
  52.         if self.__curbit != 0:
  53.             self.__curbit -= 1
  54.         else:
  55.             self.__curbit = (self.__tagsize * 8) - 1
  56.             self.__tag = ord(self.read_byte())
  57.             for i in xrange(self.__tagsize - 1):
  58.                 self.__tag += ord(self.read_byte()) << (8 * (i + 1))
  59.  
  60.         bit = (self.__tag  >> ((self.__tagsize * 8) - 1)) & 0x01
  61.         self.__tag <<= 1
  62.         return bit
  63.  
  64.     def is_end(self):
  65.         return self.__offset == len(self.__in) and self.__curbit == 1
  66.  
  67.     def read_byte(self):
  68.         """read next byte from the stream"""
  69.         if type(self.__in) == str:
  70.             result = self.__in[self.__offset]
  71.         elif type(self.__in) == file:
  72.             result = self.__in.read(1)
  73.         self.__offset += 1
  74.         return result
  75.  
  76.     def read_fixednumber(self, nbbit, init=0):
  77.         """reads a fixed bit-length number"""
  78.         result = init
  79.         for i in xrange(nbbit):
  80.             result = (result << 1)  + self.read_bit()
  81.         return result
  82.  
  83.     def read_variablenumber(self):
  84.         """return a variable bit-length number x, x >= 2
  85.        reads a bit until the next bit in the pair is not set"""
  86.         result = 1
  87.         result = (result << 1) + self.read_bit()
  88.         while self.read_bit():
  89.             result = (result << 1) + self.read_bit()
  90.         return result
  91.  
  92.     def read_setbits(self, max_, set_=1):
  93.         """read bits as long as their set or a maximum is reached"""
  94.         result = 0
  95.         while result < max_ and self.read_bit() == set_:
  96.             result += 1
  97.         return result
  98.  
  99.     def back_copy(self, offset, length=1):
  100.         for i in xrange(length):
  101.             self.out += self.out[-offset]
  102.         return
  103.  
  104.     def read_literal(self, value=None):
  105.         if value is None:
  106.             self.out += self.read_byte()
  107.         else:
  108.             self.out += value
  109.         return False
  110.  
  111. def lengthdelta(offset):
  112.     if offset < 0x80 or 0x7D00 <= offset:
  113.         return 2
  114.     elif 0x500 <= offset:
  115.         return 1
  116.     return 0
  117.  
  118. class decompress(_bits_decompress):
  119.     def __init__(self, data):
  120.         _bits_decompress.__init__(self, data, tagsize=1)
  121.         self.__pair = True
  122.         self.__lastoffset = 0
  123.         self.__functions = [
  124.             self.__literal,
  125.             self.__block,
  126.             self.__shortblock,
  127.             self.__singlebyte]
  128.         return
  129.  
  130.     def __literal(self):
  131.         self.read_literal()
  132.         self.__pair = True
  133.         return False
  134.  
  135.     def __block(self):
  136.         b = self.read_variablenumber()
  137.         if b == 2 and self.__pair :
  138.             offset = self.__lastoffset
  139.             length = self.read_variablenumber()
  140.         else:
  141.             # Andromeda' aPLib variant doesn't care about self.__pair to
  142.             # decrement high so we don't need to check parity.
  143.             high = b - 3
  144.             offset = (high << 8) + ord(self.read_byte())
  145.             length = self.read_variablenumber()
  146.             length += lengthdelta(offset)
  147.         self.__lastoffset = offset
  148.         self.back_copy(offset, length)
  149.         self.__pair = False
  150.         return False
  151.  
  152.     def __shortblock(self):
  153.         b = ord(self.read_byte())
  154.         if b <= 1:
  155.             return True
  156.         length = 2 + (b & 0x01)
  157.         offset = b >> 1
  158.         self.back_copy(offset, length)
  159.         self.__lastoffset = offset
  160.         self.__pair = False
  161.         return False
  162.  
  163.     def __singlebyte(self):
  164.         offset = self.read_fixednumber(4)
  165.         if offset:
  166.             self.back_copy(offset)
  167.         else:
  168.             self.read_literal('\x00')
  169.         self.__pair = True
  170.         return False
  171.  
  172.     def do(self):
  173.         """returns decompressed buffer and consumed bytes counter"""
  174.         self.read_literal()
  175.         while True:
  176.             if self.__functions[self.read_setbits(3)]():
  177.                 break
  178.         return self.out
  179.        
  180. # End of aPLib variant
  181.  
  182. # RC4 cryptoshit routines
  183.  
  184. def initialize(strkey):
  185.     """Produce a 256-entry list based on `key` (a sequence of numbers)
  186. as the first step in RC4.
  187. Note: indices in key greater than 255 will be ignored.
  188. """
  189.     key = [ord(i) for i in strkey]
  190.     k = range(256)
  191.     j = 0
  192.     for i in range(256):
  193.         j = (j + k[i] + key[i % len(key)]) % 256
  194.         k[i], k[j] = k[j], k[i]
  195.     return k
  196.  
  197. def initialize_variant(strkey):
  198.     """Produce a 256-entry list based on `key` (a sequence of numbers)
  199. as the first step in RC4.
  200. Note: indices in key greater than 255 will be ignored.
  201. """
  202.     key = [ord(i) for i in strkey]
  203.     k = range(256)
  204.     j = 0
  205.     for i in range(256):
  206.         j = (j + k[i] - key[i % len(key)]) % 256
  207.         k[i], k[j] = k[j], k[i]
  208.     return k
  209.    
  210. def gen_random_bytes(k):
  211.     """Yield a pseudo-random stream of bytes based on 256-byte array `k`."""
  212.     i = 0
  213.     j = 0
  214.     while True:
  215.         i = (i + 1) % 256
  216.         j = (j + k[i]) % 256
  217.         k[i], k[j] = k[j], k[i]
  218.         yield k[(k[i] + k[j]) % 256]
  219.  
  220. def run_rc4(k, text):
  221.     cipher_chars = []
  222.     random_byte_gen = gen_random_bytes(k)
  223.     for char in text:
  224.         byte = ord(char)
  225.         cipher_byte = byte ^ random_byte_gen.next()
  226.         cipher_chars.append(chr(cipher_byte))
  227.     return ''.join(cipher_chars)
  228.  
  229. # End of RC4 cryptoshit routines
  230.  
  231. #Finds a str by its null byte
  232. def find_str(s): return s[:s.find('\x00')]
  233.  
  234. offset = 0x4B8  # String table is stored at this address.
  235.    
  236. #Load the PE and grabs the offset of the payload
  237.  
  238. def load (args):
  239.     global payload
  240.     f = pefile.PE(args[0])
  241.     mem = f.get_memory_mapped_image()
  242.     payload_offset = struct.unpack("L", mem[0x1AAE:0x1AAE+4])[0] - 0x400000
  243.  
  244.     # Grabs the size of data
  245.     size = struct.unpack("L", mem[payload_offset+0x10:payload_offset+0x14])[0]
  246.     rc4_key = mem[payload_offset:payload_offset+0x10]
  247.     cyphered = mem[payload_offset+0x28:payload_offset+0x28+size]
  248.     #Do decypher
  249.     k = initialize(rc4_key)
  250.     decyphered = run_rc4(k, cyphered)
  251.  
  252.     try:
  253.         payload = decompress(decyphered).do()
  254.     except Exception:
  255.         return False
  256.     return True
  257.  
  258.    
  259. def run():
  260.     global offset, payload
  261.     print "RC4 bot key: " + payload[0x4:0x24]
  262.     rcKey = payload[5: 0x15]
  263.     size = struct.unpack("I",payload[offset:offset+4])[0]
  264.     while (size != 0):
  265.         size = (~size) & 0x0000ffff
  266.         table = initialize_variant(rcKey)
  267.         print run_rc4(table, payload[offset+4:offset+4+size])
  268.         offset += 5 + size
  269.         size = struct.unpack("I",payload[offset:offset+4])[0]
  270.        
  271. if __name__=="__main__":
  272.     if (len(sys.argv) != 2):
  273.         print "Usage: " + sys.argv[0] + " <malware path>"
  274.         exit()
  275.     load(sys.argv[1:])
  276.     run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement