├── README.md
├── athenahttp_decode.py
├── find_byte_strings.py
├── find_interesting_xor.py
└── find_switch_jumps.py
/README.md:
--------------------------------------------------------------------------------
1 | This repository will contain scripts that were created by the ASERT team to aid in reverse engineering malware
2 |
--------------------------------------------------------------------------------
/athenahttp_decode.py:
--------------------------------------------------------------------------------
1 | # Author: Jason Jones, Arbor Networks ASERT
2 | ########################################################################
3 | # Copyright 2013 Arbor Networks
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #
18 | ########################################################################
19 |
20 | import urllib
21 | import string
22 | import base64
23 | import sys
24 | from urlparse import parse_qs
25 |
26 | """
27 | Example usage and output:
28 |
29 | python athenahttp_decode.py "a=%59%32%70%77%64%32%64%75%64%47%46%6F%64%57%4A%76%64%6D%6C%6B%63%57%74%34%5A%58%4A%35%62%48%4E%6D%62%58%6F%36%5A%32%31%30%59%57%35%31%61%47%39%69%61%58%5A%77%5A%6E%70%6A%64%32%70%78%5A%47%74%34%5A%58%4A%35%62%48%4D%3D&b=yHR5gGU6v25yZXbeY3q1oWQ6OGY2NWJbNDBlZlRbNsEqMWUqMWE5MWNjODA2ZDYqNsI2OTZlyHBxoXY6YWRhoW58YXJmoDt4ODZ8Z2ViZDtjZXNkcG9ayGNfglVsOmF8v3M6V19YUHq2ZXI6cmEiMC44yG5ecDp0LmB8vlV3OmF8&c=%70%76%63%6A%70%77%64%6A%71%78%64%6B%72%78%65%6C%72%79%66%6C%73%7A%66%6D" cHZjanB3ZGpxeGRrcnhlbHJ5ZmxzemZtZjcrcWRHVuejvUZsUFRkc2ZBPT0KZjbSoGMxcHBoRDAsZjcOclJXMWbzvVE5SVcKclRHcHBzR3c1YsNSoGNiUmnK
30 |
31 | Substituion Tables: cjpwgntahubovidqkxerylsfmz:gmtanuhobivpfzcwjqdkxeryls
32 | Decoded Phone-Home: |type:on_exec|uid:8f65ba40ffda7111e11a91cd806d6172696f|priv:admin|arch:x86|gend:desktop|cores:1|os:W_XP|ver:v1.0.8|net:4.0|new:1|
33 | Response Data Marker: cHZjanB3ZGpxeGRrcnhlbHJ5ZmxzemZt
34 | Received Command : |interval=90|
35 | Received Command : |taskid=7|command=!botkill.start|
36 | """
37 |
38 | def decode_athena(pdata,response):
39 | pdata = parse_qs(pdata)
40 | set_abc = set(['a','b','c'])
41 | if set(pdata.keys()).intersection(set_abc) != set_abc:
42 | print "POST Data does not contain a,b,c values, exiting."
43 | return
44 | KEY_strtr=base64.b64decode(urllib.unquote(pdata['a'][0])).split(':')
45 | phone_home = urllib.unquote(pdata['b'][0])
46 | phone_home_decoded = base64.b64decode(phone_home.translate(string.maketrans(KEY_strtr[1],KEY_strtr[0])))
47 | OUTDATA_marker = base64.b64encode(urllib.unquote(pdata['c'][0]))
48 | print "Substituion Tables: %s:%s" % tuple(KEY_strtr)
49 | print "Decoded Phone-Home: %s" % phone_home_decoded
50 | print "Response Data Marker: %s" % OUTDATA_marker
51 | response_idx = response.find(OUTDATA_marker)
52 | if response_idx != -1:
53 | command = response[response_idx+len(OUTDATA_marker):]
54 | command = command.translate(string.maketrans(KEY_strtr[1],KEY_strtr[0]))
55 | command = base64.b64decode(command)
56 | for cmd in command.split('\n'):
57 | if cmd != "":
58 | print "Received Command : %s" % base64.b64decode(cmd)
59 |
60 | if __name__ == "__main__":
61 | if len(sys.argv) != 3:
62 | print "Usage: %s "
63 | else:
64 | decode_athena(sys.argv[1],sys.argv[2])
65 |
--------------------------------------------------------------------------------
/find_byte_strings.py:
--------------------------------------------------------------------------------
1 | # Author: Jason Jones, Arbor Networks ASERT
2 | ########################################################################
3 | # Copyright 2013 Arbor Networks
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #
18 | ########################################################################
19 |
20 | from idaapi import *
21 | from idc import *
22 | from idautils import *
23 | import ctypes
24 | from PySide import QtGui, QtCore
25 |
26 |
27 | class ByteStringsViewer_t(PluginForm):
28 | def Show(self):
29 | return PluginForm.Show(self,"Byte Strings",options = PluginForm.FORM_PERSIST)
30 |
31 | def OnCreate(self,form):
32 | self.parent = self.FormToPySideWidget(form)
33 | self.byte_strings = {}
34 | self.table = QtGui.QTableWidget()
35 | self.table.setRowCount(1)
36 | self.table.setColumnCount(3)
37 | self.table.setHorizontalHeaderLabels(("Address","Function","String"))
38 | layout = QtGui.QVBoxLayout()
39 | layout.addWidget(self.table)
40 | self.clipboard = QtGui.QClipboard()
41 | self.Create()
42 | self.parent.setLayout(layout)
43 | def OnClose(self,form):
44 | global ByteStringForm
45 | del ByteStringForm
46 | print "Closed"
47 |
48 | def click_row(self):
49 | i = self.table.item(self.table.currentRow(),0)
50 | bstr = self.table.item(self.table.currentRow(),2)
51 | print self.table.currentRow()
52 | addr = i.text().strip()
53 | bstr = bstr.text()
54 | print bstr
55 | print addr
56 | if not addr.startswith("0x"):
57 | addr = get_name_ea(BADADDR,str(addr))
58 | else:
59 | addr = addr[2:10]
60 | addr= int(addr,16)
61 | Jump(addr)
62 | self.clipboard.setText(bstr)
63 | return
64 |
65 | def Create(self):
66 | title = "Byte Strings"
67 | self.table.clear()
68 | self.table.setColumnCount(3)
69 | self.table.setHorizontalHeaderLabels(("Address","Function","String"))
70 | self.table.itemClicked.connect(self.click_row)
71 | self.find_byte_strings()
72 | self.table.setRowCount(len(self.byte_strings.keys()))
73 | row = 0
74 | for addr,bstr in self.byte_strings.items():
75 | self.table.setItem(row,0,QtGui.QTableWidgetItem(addr))
76 | self.table.setItem(row,1,QtGui.QTableWidgetItem(get_func_name(int(addr[2:],16))))
77 | self.table.setItem(row,2,QtGui.QTableWidgetItem(bstr))
78 | self.table.resizeRowToContents(row)
79 | row += 1
80 | self.table.setSortingEnabled(True)
81 |
82 |
83 |
84 | def find_byte_strings(self):
85 | #chrs = {}
86 | for f in Functions():
87 | func = get_func(f)
88 | chr_vals = {}
89 | eightbit = {}
90 | for head in Heads(func.startEA,func.endEA):
91 | if GetMnem(head) == "mov":
92 | if re.match('[abcd]l',GetOpnd(head,0)) and GetOpType(head,1) == o_imm and ((GetOperandValue(head,1) >= 0x20 and GetOperandValue(head,1) <= 0x7f) or GetOperandValue(head,1) in [0xd,0xa]):
93 | eightbit[GetOpnd(head,0)] = GetOperandValue(head,1)
94 | if (GetOpnd(head,0).startswith('byte ptr') or GetOpnd(head,0).startswith('[e')) and GetOpType(head,1) == o_imm and ((GetOperandValue(head,1) >= 0x20 and GetOperandValue(head,1) <= 0x7f) or GetOperandValue(head,1) in [0xd,0xa]):
95 | reg = GetOpnd(head,0)
96 | reg = reg[reg.find('['):]
97 | if reg.count('+') == 0: offset = 0
98 | else:
99 | ops = reg.split('+')
100 | reg = reg[:reg.find('+')]+']'
101 | offset = ctypes.c_int32(GetOperandValue(head,0)).value
102 | reg_base=0
103 | if len(ops) > 2 and ops[1].endswith('h'):
104 | reg_base = int(ops[1][:-1],16)
105 | offset = offset-reg_base
106 | if reg not in chr_vals: chr_vals[reg] = {}
107 | chr_vals[reg][offset] = (head,chr(GetOperandValue(head,1)))
108 | elif (GetOpnd(head,0).startswith('byte ptr') or GetOpnd(head,0).startswith('[e')) and GetOpType(head,1) == o_reg and GetOpnd(head,1) in eightbit:
109 | reg = GetOpnd(head,0)
110 | reg = reg[reg.find('['):]
111 | if reg.count('+') == 0: offset = 0
112 | else:
113 | ops = reg.split('+')
114 | reg = reg[:reg.find('+')]+']'
115 | offset = ctypes.c_int32(GetOperandValue(head,0)).value
116 | reg_base=0
117 | if len(ops) > 2 and ops[1].endswith('h'):
118 | reg_base = int(ops[1][:-1],16)
119 | offset = offset-reg_base
120 |
121 | if reg not in chr_vals: chr_vals[reg] = {}
122 | chr_vals[reg][offset] = (head,chr(eightbit[GetOpnd(head,1)]))
123 | for reg,c_v in chr_vals.items():
124 | keys = c_v.keys()
125 | keys.sort()
126 | last = None
127 | s = ""
128 | offset = 0
129 | for o in keys:
130 | if last is None:
131 | addr = c_v[o][0]
132 | offset = o
133 | s = c_v[o][1]
134 | elif last + 1 == o and c_v[o] != '\x00':
135 | s += c_v[o][1]
136 | else:
137 | if s != "" and len(s) > 3:
138 | self.byte_strings["0x%X" % addr] = s
139 | func = get_func(addr)
140 | if offset > 0:
141 | s = ""
142 | continue
143 |
144 | s = c_v[o][1]
145 | offset = o
146 | addr = c_v[o][0]
147 | last = o
148 | if s != "" and len(s) > 1:
149 | self.byte_strings["0x%X" % addr] = s
150 | func = get_func(addr)
151 |
152 | def find_all_byte_strings():
153 | global ByteStringForm
154 | ByteStringForm = ByteStringsViewer_t()
155 | ByteStringForm.Show()
156 | ByteStringForm.table.resizeRowsToContents()
157 | ByteStringForm.table.resizeColumnsToContents()
158 |
159 |
160 | find_all_byte_strings()
161 |
--------------------------------------------------------------------------------
/find_interesting_xor.py:
--------------------------------------------------------------------------------
1 | # Author: Jason Jones, Arbor Networks ASERT
2 | ########################################################################
3 | # Copyright 2013 Arbor Networks
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #
18 | ########################################################################
19 |
20 | from idaapi import *
21 | from idautils import *
22 | from idc import *
23 | def find_interesting_xor():
24 | for f in Functions():
25 | func = get_func(f)
26 | heads=Heads(func.startEA,func.endEA)
27 | xors = []
28 | for head in heads:
29 | if GetMnem(head).startswith('xor'):
30 | m = GetDisasm(head)
31 | arg1 = GetOpnd(head,0)
32 | arg2 = GetOpnd(head,1)
33 | if arg1 != arg2 and "0FFFFFFFFh" not in [arg1,arg2]:
34 | print "Interesting in %s XOR %s %s @ 0x%X" % (get_func_name(f),arg1,arg2,head)
35 | xors.append(head)
36 | elif GetMnem(head).startswith('j') and xors!= []:
37 | arg1 = GetOpnd(head,0)
38 | arg1 = get_name_ea(BADADDR,arg1)
39 | for addr in xors:
40 | if addr > arg1:
41 | print "Interesting XOR in a loop %s @ %X: %s" % (get_func_name(addr),addr,GetDisasm(addr))
42 |
43 | if __name__ == "__main__":
44 | find_interesting_xor()
45 |
--------------------------------------------------------------------------------
/find_switch_jumps.py:
--------------------------------------------------------------------------------
1 | """
2 | Switch jump viewer
3 | original code by Aaron Portnoy / Zef Cekaj, Exodus Intelligence
4 | Updated and modified by Jason Jones, Arbor Networks ASERT
5 | """
6 | import idaapi
7 | from idaapi import *
8 | from idc import *
9 | from idautils import *
10 | from PySide import QtGui, QtCore
11 |
12 |
13 | class SwitchViewer_t(PluginForm):
14 |
15 | #def __init__(self, data):
16 | # # data should be a 3-tuple
17 | # #
18 | # # (address, number of cases, list of interesting calls)
19 | # self.switches = data
20 |
21 | def Show(self):
22 | return PluginForm.Show(self,"Switch Jump Viewer",options = PluginForm.FORM_PERSIST)
23 |
24 | def OnCreate(self, form):
25 | """
26 | Called when the plugin form is created
27 | """
28 | # Get parent widget
29 | self.parent = self.FormToPySideWidget(form)
30 | self.calls = {}
31 |
32 | # Create tree control
33 | self.tree = QtGui.QTreeWidget()
34 | self.tree.setHeaderLabels(("Names","# Cases"))
35 | self.tree.setColumnWidth(0, 100)
36 |
37 | # Create layout
38 | layout = QtGui.QVBoxLayout()
39 | layout.addWidget(self.tree)
40 |
41 | self.Create()
42 | self.parent.setLayout(layout)
43 |
44 | def OnClose(self,form):
45 | global SwitchForm
46 | del SwitchForm
47 | print "Closed"
48 |
49 | def click_tree(self):
50 | i = self.tree.currentItem()
51 | addr = i.text(0).strip()
52 | print addr
53 | if not addr.startswith("0x"):
54 | addr = get_name_ea(BADADDR,str(addr))
55 | else:
56 | addr = addr[2:10]
57 | addr= int(addr,16)
58 | Jump(addr)
59 | return
60 |
61 | def Create(self):
62 | title = "Switches"
63 | #root = QtGui.QTreeWidgetItem(self, title)
64 | comment = COLSTR("; Double-click to follow", SCOLOR_BINPREF)
65 | #self.AddLine(comment)
66 | comment = COLSTR("; Hover for preview", SCOLOR_BINPREF)
67 | #self.AddLine(comment)
68 | self.tree.clear()
69 | self.tree.setColumnCount(2)
70 | self.tree.clicked.connect(self.click_tree)
71 | for func in sorted(self.switches.keys()):
72 | func_node = QtGui.QTreeWidgetItem(self.tree)
73 | func_node.setText(0,func)
74 | func_node.setText(1,"")
75 | for item in self.switches[func]:
76 | node = QtGui.QTreeWidgetItem(func_node)
77 | addr = item[0]
78 | cases = item[1]
79 | address_element = "0x%08x" % addr
80 | node.setText(0,address_element)
81 | node.setText(1,"%04s" % cases)
82 | #line = address_element + value_element
83 | self.calls[addr] = item[2]
84 | for c in item[2]:
85 | cnode = QtGui.QTreeWidgetItem(node)
86 | cnode.setText(0,c[0])
87 | cnode.setText(1,c[2])
88 |
89 | #self.AddLine(line)
90 |
91 | return True
92 |
93 |
94 | def OnDblClick(self, shift):
95 | line = self.GetCurrentLine()
96 |
97 | # skip the lines where we say "hover for preview" and so on
98 | if "0x" not in line: return False
99 |
100 | # skip COLSTR formatting bytes, find address
101 | start = line.find("0x")
102 | addr = int(line[2:line.find(":")], 16)
103 |
104 | Jump(addr)
105 | return True
106 |
107 | def OnHint(self, lineno):
108 | # skip the lines where we say "hover for preview" and so on
109 | if lineno < 2: return False
110 | else: lineno -= 2
111 |
112 | line = self.GetCurrentLine()
113 |
114 | if "0x" not in line: return False
115 |
116 | # skip COLSTR formatting bytes, find address
117 | start = line.find("0x")
118 | addr = int(line[2:line.find(":")], 16)
119 |
120 | calls = self.calls[addr]
121 |
122 | res = ""
123 | for line in calls:
124 | res += COLSTR(line + "\n", SCOLOR_DREF)
125 |
126 | return (1, res)
127 |
128 | def look_for_interesting(addr):
129 | #interesting = ["sscanf", "sprintf", "exec", "run", "strcpy"]
130 | f = get_func(addr)
131 | interesting_calls = []
132 | if f is None:
133 | return
134 | for h in Heads(f.startEA,f.endEA):
135 | if GetMnem(h).find("call") != -1:
136 | interesting_calls.append("0x%08x : %s" % (h,GetDisasm(h)))
137 | #print "0x%08x: %s" % (h,GetDisasm(h))
138 | break
139 | return interesting_calls
140 |
141 | def get_jlocs(sw):
142 | jlocs = []
143 | ncases = sw.ncases if sw.jcases == 0 else sw.jcases
144 | for i in range(ncases):
145 | addr = Dword(sw.jumps+i*4)
146 | name = get_name(BADADDR,addr)
147 | comm = GetCommentEx(LocByName(name),1)
148 | comm = comm[comm.find('case'):] if comm is not None and comm.startswith('jumptable') else comm
149 | jlocs.append((name,LocByName(name),comm))
150 | return jlocs
151 |
152 | def get_switch_jumps(ea):
153 | func = get_func(ea)
154 | data = []
155 | heads=Heads(func.startEA,func.endEA)
156 | for head in heads:
157 | ic = []
158 |
159 | if (hex(Word(head)).lower() == "0x24ff" or hex(Word(head)).lower() == "24ff") and GetMnem(head).find("jmp") != -1:
160 | if get_switch_info_ex(head) is None: continue
161 | sw = get_switch_info_ex(head)
162 | print "[*] Switch at 0x%08x has %s jtable elements" % (head,sw.get_jtable_size())
163 | ic.extend(get_jlocs(sw))
164 | """
165 | # useful for xrefing searching, not used by me
166 | refs = list(CodeRefsFrom(head,0))
167 | for ref in refs:
168 | #print "-- Reference 0x%08x" % ref
169 | h = NextHead(ref)
170 | while h != BADADDR:
171 | if GetMnem(h).find("call") != -1:
172 | #print "---- Call to 0x%08x" % GetOperandValue(h,0)
173 | callrefs = list(CodeRefsFrom(h,0))
174 | for cr in callrefs:
175 | if GetFunctionFlags(cr) == -1:
176 | continue
177 | ic.extend(get_jlocs(
178 | break
179 | elif GetMnem(h).startswith("j"):
180 | break
181 | h = NextHead(h)
182 | """
183 | data.append((head,sw.ncases,ic))
184 | return data
185 |
186 | def find_all_switch_jumps():
187 | global SwitchForm
188 | funcs = Functions()
189 | data = {}
190 | for func in Functions():
191 | d = get_switch_jumps(func)
192 | if d!= []:
193 | data[get_func_name(func)] = d
194 | SwitchForm = SwitchViewer_t()
195 | SwitchForm.switches = data
196 | SwitchForm.Show()
197 |
198 | find_all_switch_jumps()
199 |
--------------------------------------------------------------------------------