Console Binaries für Android mit GO

wie man mit ein bisschen gefrickel®

Go Binaries auf Android zum laufen bekommt

Dieser Artikel ist eigentlich ein Beiwerk, dass in den Vorbereitungen zu meinem letzten Artikel entstanden ist.

Wenn man sich so viel Mühe macht, tools zu bauen, damit man Apps in Go schreiben kann, ist man doch schnell versucht, mal gar keine App zu schreiben, sondern einfach eine standalone Binary.

1. Motivation

Die Motivation dazu ist relativ einfach zu geben. Wenn man neben den ganzen schicken Apps, mit denen man so täglich umgeht, sich auch noch etwas für Android selbst interessiert, dann wird man früher oder später schon mal ein Terminal aufgemacht haben, denn Android ist auch nur ein Linux.
Dieses Linux macht aber nicht gerade Spaß.

Neben dem Ärgernis, zumindest ohne Bluetooth Keyboard mit dieser (auf Smartphones viel zu kleinen) Tastatur arbeiten zu müssen, fehlen einfach zu viele Befehle.
Das Problem mit der Tastatur lässt sich recht schnell mit dem Hackers Keyboard beseitigen. Diese Tastatur hat, wenn man in den Landscape Modus wechselt, alle Tasten die man für ein Terminal benötigt. Es ist immer wieder ein Vergnügen einen Ping einfach mit einem CTRL+C abzubrechen, statt irgendwelche Verrenkungen mit Power und Lautstärke als Workaround zu machen.

Den Mangel an Linux Standardbefehlen kann man mit der BusyBox zwar angehen, aber auch mit ihr fehlt einfach noch zu viel.
Nun könnte man meinen, nachdem wir uns im letzten Artikel die Native Toolchain zusammen gebaut haben, wäre die doch Ideal, um die vermissten Tools einfach selbst zu bauen. Das Führt aber sehr schnell zu Frust.
Android ist eben kein Desktoplinux und so muss man schon für eine simple Bash (wenn man sie denn nicht dank Cyanogenmod schon hat) eine Menge Abhängigkeiten erfüllen. Ehe man sich versieht, hat man quasi eine Minimalinstalltion einer Linuxdistribution neben sein Adroid gebaut.
Das ist allerdings ziemlich unsinnig, weil es dafür diverse Apps gibt, die tatsächlich genau das leisten.

An dieser Stelle wird Go interessant. Was mit C, oder C++ ziemlich nervtötend ist, ist mit Go schnell gemacht. Es bietet sich also an, sich auf die kleinen Probleme zu fokussieren, für die gerade mal wieder die entsprechenden Befehle in der Shell fehlen, bzw. die in der Busybox einfach nicht alle Funktionen haben, die man gerne hätte.

2. An die Arbeit

Wer jetzt vielleicht ein Android in der Hand hat, welches vom Dritthersteller noch keine der neueren Versionen von Android 5 (Lollipop) bekommen hat, kann sich Ausnahmsweise vielleicht freuen: Die Go Version 1.5, die ich im letzten HowTo gebaut habe, erstellt Binaries, welche sich auf älteren Android versionen noch ohne Probleme im Terminal verwenden lassen.

Dazu sei noch einmal gesagt, dass man zum ausführen beliebiger Binaries auf Android immer ein Root Rechte braucht. Die meisten Pfade des Systems sind explizit mit noexec gemountet, Dateien die man ohne Root Rechte irgendwo ablegen kann, sind per se nicht ausführbar.

Deshalb lege ich Dateien für solche Spielereien am System gerne unter /data ab. Dort kann man als Root ohne weiteres schreiben, da es im gegensatz zu /system nicht als readonly eingehängt wird.

Vorrausgesetzt die Umgebungsvariablen sind noch passend gesetzt, können wir ein simples Hello World schnell bauen:

GOOS=android GOARCH=arm GOARM=7 go build hello.go

Die resultierende hello Binary ist auf einem Weg deiner Wahl auf das Android Gerät zu übertragen. Dort sollte sie in einem eigens dafür angelegten Verzeichnis unter /data liegen und natürlich mit chmod ausführbar gemacht werden.

Wer jetzt auf seinem Gerät die Binary in einem Terminal ausführen kann, hat Glück gehabt und ist bereit beliebigen anderen Code zu verwenden.

Mit einer aktuellen Version von Android sieht man leider nur noch:

error: only position independent executables (PIE) are supported.

Dabei handelt es sich um ein Sicherheitsfeature, welches in den letzten Versionen ohne großes Aufsehen Einzug erhalten hat. Solcherart Binaries können vom System beim Laden auf beliebige, nicht vorhersagbare Adressen gelegt werden.
Das hat den Vorteil, dass Attacken, die über einen Pufferüberlauf realisiert werden sollen quasi unmöglich werden, weil hinter dem überschriebenen Puffer bei jeder Ausführung anderer Code liegt.

Für meine Go Spielereien ist das aber schlecht. Derzeit gibt es keine Möglichkeit, PIE Binaries mit den Go tools zu bauen.

Dafür kann man aber mit Go eine Shared Library bauen, welche man aus einem winzigen C Programm einbindet. Für Shared Librarys gilt die PIE Einschränkung nämlich nicht, weil sie per Definition positionsunabhängig geladen werden.

Die Recherche dazu war nicht gerade einfach bzw. kurz, weshalb ich sie an dieser Steller gerne Teilen möchte.
Beim generellen Ablauf habe ich mich deutlich von diesem Tutorial inspirieren lassen.

Als Erstes brauchen wir eine leicht modifizierte hello.go.

package main

import "fmt"
import "C"

//export hello
func hello() {
    fmt.Printf("hello, world\n")
}

//empty stub to satisfy buildmode
func main() {}

Diese lässt sich mit einer etwa umständlichen Zeile als Shared Library bauen.

GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 \
go build -buildmode=c-shared -o libhello.so hello.go

Der buildmode shared, welcher hier eigentlich angebracht wäre, steht für Android leider nicht zur Verfügung. Infolgedessen benötigen wir eine leere main Methode.

Der Aufruf baut uns nicht nur die libhello.so, sondern erstellt auch eine passende libhello.h Header File.

Der C Code um die Library aufzurufen:

#include <stdio.h>
#include "libhello.h"

int main(void)
{
    puts("This is a shared library test...");
    hello();
    return 0;
}

So viel C dürfte gerade noch ohne Schmerzen und schlimme Fehler zu schreiben sein.

Mit der Native Toolchain, die wir zum Bauen von Android benötigt haben, ist auch das schnell gebaut:

$CC_FOR_TARGET -L. -Wl,-pie -Wall -o hello wrap.c -lhello

Jetzt haben wir eine hello Binary und eine libhello.so Shared Library. Beide kopieren wir wieder irgendwohin unter /data.

Wenn wir den Compiler mit -Wl,-rpath aufgerufen hätten und Android damit irgendwas anfangen könnte, wäre der Aufruf im Terminal auf Android jetzt fast schon trivial. Leider ist dem nicht so.

Wir müssen den Ladepfad des Libraries per Hand angeben, da Android rpath ignoriert.

root@deb:/data/exo # export LD_LIBRARY_PATH=/data/exo
root@deb:/data/exo # ./hello
WARNING: linker: libhello.so has text relocations. This is wasting memory and prevents security hardening. Please fix.
This is a shared library test...
hello, world

Naja immerhin – es geht.

Auch wenn Android von der internen Struktur der Library nicht sonderlich begeistert ist. Vermutlich steht diese Meldung aber auch irgendwo in den Logs, wenn man derzeit eine mit Go gebaute Native App ausführt. Die kommen auch als Shared Library daher.
Daraus kann man wohl schließen, dass es noch sehr lange dauern wird, bis jemand sich dafür interessiert, _echte _PIE Binaries mit Go für Android zu bauen.