Skip to content

Commit 30fe5f8

Browse files
eset-researchmarc-etienne
authored andcommitted
Added Python script from InvisiMole research
1 parent ec71734 commit 30fe5f8

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

invisimole/README.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
= InvisiMole
2+
3+
Copyright (C) 2018 ESET
4+
5+
== Publications
6+
7+
* Blog: https://www.welivesecurity.com/2018/06/07/invisimole-equipped-spyware-undercover/
8+
9+
== Content
10+
11+
link:invisimole_decrypt.py[`invisimole_decrypt.py`]::
12+
A Python script to decrypt the RC2CL and RC2FM modules from the InvisiMole DLL wrapper

invisimole/invisimole_decrypt.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env python
2+
#
3+
# Code related to ESET's InvisiMole research.
4+
# This script can decrypt the RC2CL and RC2FM modules from the InvisiMole DLL wrapper.
5+
#
6+
# For feedback or questions contact us at: [email protected]
7+
# https://github.com/eset/malware-research/
8+
#
9+
# This code is provided to the community under the two-clause BSD license as
10+
# follows:
11+
#
12+
# Copyright (C) 2018 ESET
13+
# All rights reserved.
14+
#
15+
# Redistribution and use in source and binary forms, with or without
16+
# modification, are permitted provided that the following conditions are met:
17+
#
18+
# 1. Redistributions of source code must retain the above copyright notice, this
19+
# list of conditions and the following disclaimer.
20+
#
21+
# 2. Redistributions in binary form must reproduce the above copyright notice,
22+
# this list of conditions and the following disclaimer in the documentation
23+
# and/or other materials provided with the distribution.
24+
#
25+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35+
36+
import os
37+
import sys
38+
import struct
39+
import pefile
40+
41+
def decrypt_payload(data, size):
42+
43+
result = bytearray()
44+
45+
add_value = 0xAD3A019E
46+
for i in range(size//4 - 1):
47+
48+
dword = struct.unpack_from('<L', data, i * 4)[0]
49+
xor_key = ((0xA9 * i * 4) + add_value) & 0xFFFFFFFF
50+
add_value = dword
51+
dword = (dword ^ xor_key) & 0xFFFFFFFF
52+
53+
result += struct.pack('<L', dword)
54+
55+
result += struct.pack('<L', 0)
56+
return result
57+
58+
def save_payload(filename, payload_name, payload_data, payload_size):
59+
60+
decrypted_data = decrypt_payload(payload_data, payload_size)
61+
62+
f = open('{0:s}_{1:s}.bin'.format(os.path.splitext(filename)[0], payload_name), 'wb')
63+
f.write(decrypted_data)
64+
f.close()
65+
66+
fixed_decrypted_data = bytearray([0x4D, 0x5A, 0x90, 0x00]) + decrypted_data[4:]
67+
68+
f = open('{0:s}_{1:s}_fixed.bin'.format(os.path.splitext(filename)[0], payload_name), 'wb')
69+
f.write(fixed_decrypted_data)
70+
f.close()
71+
return
72+
73+
def main():
74+
75+
if len(sys.argv) < 2:
76+
print('Usage: {0:s} input_dll'.format(sys.argv[0]))
77+
return
78+
79+
filename = sys.argv[1]
80+
pe = pefile.PE(filename)
81+
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
82+
83+
RC2CL_offset, RC2CL_size = 0, 0
84+
RC2FM_offset, RC2FM_size = 0, 0
85+
86+
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
87+
for resource_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
88+
89+
if pefile.RESOURCE_TYPE.get(resource_type.struct.Id, '-') == 'RT_RCDATA':
90+
91+
if hasattr(resource_type, 'directory'):
92+
for resource_id in resource_type.directory.entries:
93+
94+
if hasattr(resource_id, 'directory'):
95+
for resource_lang in resource_id.directory.entries:
96+
97+
if hasattr(resource_lang, 'data'):
98+
99+
if str(resource_id.name) == 'RC2CL':
100+
RC2CL_offset = resource_lang.data.struct.OffsetToData
101+
RC2CL_size = resource_lang.data.struct.Size
102+
elif str(resource_id.name) == 'RC2FM':
103+
RC2FM_offset = resource_lang.data.struct.OffsetToData
104+
RC2FM_size = resource_lang.data.struct.Size
105+
106+
if RC2CL_offset != 0 and RC2CL_size != 0:
107+
print('RC2CL resource {0:08X} - {1:08X}'.format(RC2CL_offset, RC2CL_size))
108+
109+
save_payload(filename, 'RC2CL', pe.get_data(RC2CL_offset, RC2CL_size), RC2CL_size)
110+
else:
111+
print('RC2CL resource not found')
112+
113+
if RC2FM_offset != 0 and RC2FM_size != 0:
114+
print('RC2FM resource {0:08X} - {1:08X}'.format(RC2FM_offset, RC2FM_size))
115+
116+
save_payload(filename, 'RC2FM', pe.get_data(RC2FM_offset, RC2FM_size), RC2FM_size)
117+
else:
118+
print('RC2FM resource not found')
119+
120+
if __name__ == "__main__":
121+
main()

0 commit comments

Comments
 (0)