Advertisement
aaSSfxxx

Andromeda bot config extractor

Dec 16th, 2012
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.37 KB | None | 0 0
  1. #!/usr/bin/python2
  2. #-*- coding: utf-8 -*-
  3.  
  4. # Andromeda 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 sys
  12. import struct
  13.  
  14. # aPLib variant used by Andromeda
  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 gen_random_bytes(k):
  198.     """Yield a pseudo-random stream of bytes based on 256-byte array `k`."""
  199.     i = 0
  200.     j = 0
  201.     while True:
  202.         i = (i + 1) % 256
  203.         j = (j + k[i]) % 256
  204.         k[i], k[j] = k[j], k[i]
  205.         yield k[(k[i] + k[j]) % 256]
  206.  
  207. def run_rc4(k, text):
  208.     cipher_chars = []
  209.     random_byte_gen = gen_random_bytes(k)
  210.     for char in text:
  211.         byte = ord(char)
  212.         cipher_byte = byte ^ random_byte_gen.next()
  213.         cipher_chars.append(chr(cipher_byte))
  214.     return ''.join(cipher_chars)
  215.  
  216. # End of RC4 cryptoshit routines
  217.  
  218. #Finds a str by its null byte
  219. def find_str(s): return s[:s.find('\x00')]
  220.  
  221. # Let's run baby !
  222. if len(sys.argv) != 2:
  223.     print "Usage: " + sys.argv[0] + " <andromeda_bot.exe>"
  224.     sys.exit(0)
  225.  
  226. #Load the PE and grabs the offset of the payload
  227. f = pefile.PE(sys.argv[1])
  228. mem = f.get_memory_mapped_image()
  229. payload_offset = struct.unpack("L", mem[0x13C5:0x13C9])[0] - 0x400000
  230.  
  231. # Grabs the size of data
  232. size = struct.unpack("L", mem[payload_offset+4:payload_offset+8])[0]
  233. rc4_key = mem[payload_offset+0x1c:payload_offset+0x3c]
  234. cyphered = mem[payload_offset+0x3c:payload_offset+0x3c+size]
  235.  
  236. #Do decypher
  237. k = initialize(rc4_key)
  238. decyphered = run_rc4(k, cyphered)
  239.  
  240. offset = 0x474  # String table is stored at this address.
  241. try:
  242.     payload = decompress(decyphered).do()
  243. except Exception:
  244.     print "Unable do decompress image ! Maybe it's not an andromeda bot..."
  245.    
  246. mem = struct.unpack("L",payload[offset:offset+4])[0]
  247. while mem != 0:
  248.     offset += 4
  249.     mem -= 0x01001000
  250.     print find_str(payload[mem:mem+500])
  251.     mem = struct.unpack("L",payload[offset:offset+4])[0]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement