![]() |
| ![]() |
Multi Plattform CodeDie Idee zu binärem Code, der auf mehreren Hardwarearchitekturen und Betriebssystemen ausgeführt werden kann, basiert auf dem Wunsch, mit einem exploit möglichst viele Plattformen attackieren zu können. Als gängigere bezeichung wird auch 'architecture spanning shellcode' verwendet. Anwenden ließe sich derartigen shellcode, wenn einem angreifer die ausführung
von eigenem code möglich wäre, er aber keine kenntnis darüber erlangen
kann, für welche architektur der code geschrieben sein müsste. einem gezielten
angriff geht in der praxis wohl ein port-scan mit os-fingerprinting oder
die suche nach aussagekräftigen service-bannern voraus, bringt das jedoch
nicht die notwendigen informationen, muss tendenziell mehr 'probiert'
werden. i386 und 68k--------------------------------------------- #!/bin/sh
# simple hack to convert
# bytes to instructions
GCC=gcc2
if [ ! "$1" ] ; then
echo usage: instruction_babel.sh '<asm-statement>'
echo e.g. '.byte 0x40'
echo '.long 0x40404040'
echo 'b 4'
exit 1
fi
cat << ASM > _tmp.c
int main(void) {
__asm__("mylabel: $1");
return 1;
}
ASM
rm _tmp.bin 2> /dev/null
$GCC -ggdb -o _tmp.bin _tmp.c && \
cat << GDB > _gdb_cmd
disassemble mylabel mylabel+4
x/4x mylabel
quit
GDB
if [ -f _tmp.bin ]; then
gdb -x _gdb_cmd _tmp.bin |
perl -ne 'if(m!<mylabel(\+\d+)?>!){print}'
fi
---------------------------------------------
idee #1
idee #2
idee #3
idee #4
versuchen wir jetzt den umgekehrten weg - eine sprunganweisung für intel-cpus, die harmlose instruktionen auf 68k-prozessoren darstellt. ferner wird der 2-byte umfassende relative sprung auf i386 geeignet auf 4 bytes gepadded. idee #5
idee #6
idee #7
somit wir haben eine sprungtabelle für die beiden prozessoren 68k und i386:
'or $0x66,%al; push %cs;addr16 mov $0x1,%eax'. ulrasparc2beim versuch eine weitere cpu zu unterstützen wird klar, wie haarig das
ganze vorhaben ist, denn die bisher ermittelten bytesequenzen werden auf
ultrasparc zu call-anweisungen. ----------------------------|------------------------|------------------
31 30 |29|28 27 26 25|24|23 22|21 20 19 18 17 16 | 15 .. 0
instr. |af| condition |condit. |<--------address---------->
class | | | code |
----------------------------|------------------------|------------------
0 0 | 1|.......... | x| x x| 0 0 0 0 0 0 | .................
----------------------------|------------------------|------------------
| | möglichst klein
erstes byte muss |
0x2? oder 0x3? sein |
|
0| 0 0 => 0 schlecht, weil '\0'
0| 0 1 => 4 ungültige instruktion
0| 1 0 => 8 b,a
0| 1 1 => c ungültige instruktion
1| 0 0 => 0 schlecht, weil '\0'
1| 0 1 => 4 ungültige instruktion
1| 1 0 => 8 f-branch
1| 1 1 => c cb???
die höheren bits vom displacement setzen wir auf 0, da wir uns den luxus,
über 64k zu springen, nicht leisten wollen. die bits 15 bis 0 stellen den
anderen teil der sprungweite dar. an der stelle sind wir bzgl. einer konkreten
wahl flexibel. um ein '\0'-byte in der instruktion zu vermeiden, müssen
wir mindestens 0x104 * 4 bytes springen. 0x0104 wird auf 68k als 'btst %d0,%d4'
und auf i386 als 'add $0x1,%al' interpretiert. der sparc-sprung 0x3080????
wird zwar unter 68k zu 'movew %d0,%a0@', allerdings hält intel das für einen
impliziten speicherzugriff 'xorb $0xb8,(%eax)'. eine sprungweitenangabe,
die von intel als harmlose 4-byte-instruktion gewertet wird und wertmäßig
nicht zu hoch ist, lässt sich trotz viel probieren nicht finden. gehen wir
nocheinmal einen schritt zurück und überlegen uns einen alternativen weg,
indem wir die reihenfolge der sprunginstruktionen veränden:
den intel-sprung ganz oben anzuordnen, hätte folgenden vorteil: im gegensatz zu 68k/ultrasparc2/ppc gibt es harmlose 1-byte instruktionen und wir haben mehr spielraum bezüglich der gestaltung. in eugenes artikel für die phrack wird die 1-byte intel-instruktion 'aaa' (bcd-korrektur nach addition, opcode: 0x37) zum padden benutzt. der befehl ist ideal, wenn man intel und ultrasparc2-code mischen möchte, denn man kann damit einen validen sethi-befehl konstruieren. um auf ein 2-byte padding zu kommen, kann man jedoch nicht zweifach 'aaa' verwenden, da diese kombination auf 68k zu problemen führt. als workaround suchen wir für '??' in 0x37??41eb einen geeigneten ersatz und mit etwas probieren stoßen wir auf 0x27: #8
zuletzt versuchen wir noch eine 4-byte-sequenz zu finden, die auf allen zu unterstützenden prozessoren einen harmlosen befehl darstellt. mit dieser bytefolge können wir alle undefinierten stellen im bisher konstruierten code-gerüst auffüllen. mit minimalem probieren findet man z.b. 0x21040104
xxxxx: 0x21040104 ... xxxxx: 0x21040104 0x000: 0x372741eb 0x004: 0x30800101 0x008: 0x670e660c 0x00c: 0x41820104 0x010: 0x40820104 0x014: 0x018: <68k_code> 0x040: 0x90909090 0x044: <intel_code> 0x110: <ppc_code> 0x114: 0x408: <ultrasparc2_code>für den architekturspezifischen teil bietet sich an, zuerst zu überprüfen, gegen welche betriebssystem-api programmiert werden muss. da mir gerade nur ein eingeschränktes set an plattformen zur verfügung stehen, betrachten wir das exemplarisch bei intel-typischen systemen. die generelle idee ist, die registerbelegung auszuwerten. hängt man sich mit einem debugger an verschiedene prozesse, so stellt man fest, dass die segmentregister typische werte speichern. anhand dieser merkmale lässt sich keine eindeutige unterscheidung treffen, und somit stellt dieser ansatz nur eine heuristik dar.
mov %es,%eax cmp $0x2f,%eax je fbsd cmp $0x23,%eax je win ...die sprünge verzweigen dann zum code, der os-spezifisch ist. die konventionen für systemaufrufe unterscheiden sich von betriebssystem zu betriebssystem. unter linux auf x86 werden parameter über allzweckregister übergeben, unter bsd über den stack. die syscall-nummern unter aktuellem net-/open-/freebsd und auch darwin sind für gängige funktionen wie open, execve, bind und listen - wahrscheinlich historisch bedingt - gleich. fazitin der praxis findet multi-plattform-shellcode keine richtige anwendung, da i.d.r. vorher das zielsystem bekannt ist. für stack-exploits ist die kenntnis einer ungefähren rücksprungadresse erforderlich, die stark von gesetzten compiler-flags abhängt und mit einem debugger erforscht oder durch probieren gefunden werden muss. ferner erfordert kombinierter shellcode mehr 'platz', besonders durch die angepassten sprungweiten. links
|
![]() |
| ![]() |