徒然ネル

LinuxカーネルとかeBPFとか

itmediaの「main()関数の前には何があるのか」を読んでみる(第五回)

# はじめに main()関数の前には何があるのかの連載を読んでわからなかったことや調べたことメモ 今回は第五回 printf()のソースコードで、ソースコードリーディングのコツを身に付ける

メモ

24:/* Write formatted output to stdout from the format string FORMAT. */
25:/* VARARGS1 */
26:int
27:__printf (const char *format, ...)
28:{
29: va_list arg; va_list: 可変個の実引数を扱うための情報を保持するための型。
30: int done;
31:
32: va_start (arg, format);
33: done = vfprintf (stdout, format, arg);
34: va_end (arg);
35:
36: return done;
37:}
38:
39:#undef _IO_printf
40:ldbl_strong_alias (__printf, printf);
41:/* This is for libg++. */
42:ldbl_strong_alias (__printf, _IO_printf);

l.29: va_list: 可変個の実引数を扱うための情報を保持するための型。

l.32: va_start: va_list を初期化し、可変個引数の使用を開始する。

l.34: va_end: 可変個引数の処理を終えるときに呼び出す。

関数名が__printf()になっているが、これはldbl_strong_alias()というマクロによってprintf()というエイリアスが定義されるようだ。

言ってることがよくわからなかった。 マクロによってエイリアスが定義されているがわからない

拾い読みしてみると、マクロの中ではva_arg()によって引数を得ているようだ。

va_arg: 可変個実引数の値を返し、次の引数へ進む。

つまり出力先がTTYのときに、行単位の出力になるようだ。

TTYはディスプレイのことみたい

AFL++動かしてみた

はじめに

AFL++ってファザーを動かしてみたので、動かした過程と結果のメモ

目次

参考

IPAのAFL資料 qiitaの記事:AFLでファジングやってみた

動かしてみた

準備

AFL++インストール

aptでAFL++をインストール

$ sudo apt update
$ sudo apt install afl++ afl++-clang afl++-doc

RAMディスク作成

$ mkdir -p /tmp/afl-ramdisk && chmod 777 /tmp/afl-ramdisk/
$ sudo mount -t tmpfs -o size=512M tmpfs /tmp/afl-ramdisk/
$ cd /tmp/afl-ramdisk/

テスト対象コード

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

int main(int argc, char** argv)
{
  char buf[8];

  if(read(0, buf, 8) < 1)
  {
     exit(1);
  }

  printf(buf);
  exit(0);
}

コンパイル

example.cをafl-gccでコンパイル

$ afl-gcc -o example example.c

ファジング

テストケースダウンロード

$ wget -O - https://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz | tar zxvf -

ファジングテスト実行

$ afl-fuzz -i afl-2.52b/testcases/others/text/ -o out/ ./example

afl++実行時の画面 IPAの資料にあるAFLの実行時の項表示項目の説明と項目名が多少違うが内容は同じだと思われる。 実行時のシステムモニターをみると、CPU1の実行が100%に貼り付いてる。

HACKING: 美しき策謀勉強メモ #1

0x200のプログラミングの基礎は手を動かさず読むだけにした。

0x300のプログラムの脆弱性攻撃から実際に動かしていく。

 

overflow_example.cについて見ていく。

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

int main(int argc, char *argv) {
    int value = 5;
    char buffer_one[8], buffer_two[8];

    strcpy(buffer_one, "one"); /* "one"をbuffer_oneに設定 */
    strcpy(buffer_two, "two"); /* "two"をbuffer_twoに設定 */

    printf("[前] buffer_two は %p にあり、その値は \'%s\' です\n", buffer_two, buffer_two);
    printf("[前] buffer_one は %p にあり、その値は \'%s\' です\n", buffer_one, buffer_one);
    printf("[前] value は %p にあり、その値は %d (0x%08x) です\n", &value, value, value);

    printf("\n[STRCPY] %ld バイトを buffer_two にコピーします\n\n", strlen(argv[1]));
    strcpy(buffer_two, argv[1]); /* 最初の引数をbuffer_twoにコピーする */

    printf("[後] buffer_two は %p にあり、その値は \'%s\' です\n", buffer_two, buffer_two);
    printf("[後] buffer_one は %p にあり、その値は \'%s\' です\n", buffer_one, buffer_one);
    printf("[後] value は %p にあり、その値は %d (0x%08x) です\n", &value, value, value);    
}
 
-gオプションをつけてコンパイル、gdb起動、20行目にブレイク貼って、run
$ gcc -g -o overflow_example overflow_example.c 
$ gdb -q ./overflow_example a
(gdb) b 20
(gdb) run
 
ASLRの有効、無効でアドレス配置が変わるのか気になったので試す。
 
何もせず(ASLR無効)アドレス配置を表示
 

ASLRを有効にしてからアドレス配置を表示
(gdb) set disable-randomization off

[heap], [vvar], [vdso], [stack]の配置のされ方が違うな
vvar, vdsoが何か知らんので調べた

dump[vvar]セグメントは、Linuxユーザー空間プログラムにおいて、関数呼び出しにまたがって保存する必要のあるデータを格納するために使用されるメモリ領域である。このセグメントは通常、読み取り専用メモリ(ROM)領域、またはフラッシュ・メモリやEEPROMなどの不揮発性メモリ(NVM)デバイスにマッピングされる。

Understanding Dump[vvar]Segment in Linux User Space Programs (devcodef1.com)より

 vDSO (virtual dynamic shared object) は、注意深く選択された kernel space ルーチン群を、user space 上のアプリケーションにエクスポートするための、カーネルメカニズムである。これにより、アプリケーションは、それらカーネル空間ルーチンを、システムコール インタフェースを使用して、それらの同じカーネル空間ルーチンを呼び出す時に固有の、user mode から カーネルモード への コンテキストスイッチ によるパフォーマンス低下なしに、プロセス内で呼び出せる。[1][2]
vvarが関数呼び出しに使うデータの保存領域で、vdsoがシステムコール呼び出しのための何か
 
heapやstackの中身の配置のされ方は同じなんかな?
と思って、実行結果を見たんだけど、

配置のされ方は同じみたい
ただ、書籍で書かれてる配置のされ方と違った。
書籍ではbuffer_two, buffer_one, valueの順にアドレスが大きくなっていたんだけど、手元の環境は逆になってる。
考えられる理由としては、
・スタックのアドレスの進む方向(番地が大きい方に進む or 小さい方に進む)はCPUによってことなる
・異なる関数の変数のプッシュ順はOSによって異なる 
・同じ関数内の変数のプッシュ順はコンパイラによって異なる 
 
以降、ASLR有効、無効はスタックオーバーフローに関係しなさそうなのでASLR無効の方を見ていく(スタックオーバーフローを利用して任意のコードを実行しようとする際はASLR関係しそうだけど)
 
8バイト突っ込んだときのアドレスが以下で、

9バイト突っ込んだときが以下、

スタックのアドレスが全部-16されてる
9バイト突っ込んだ方は、オーバーフローを検知されて異常終了してるけど、領域的にはスタック領域内だし他のスタックを書き換えたりはしてなさそう
 
さらに、バイト数を大きくすると、スタックのアドレスが小さい方にシフトして、結局オーバーフローを検知して異常終了

OSかCPUで、オーバーフローで任意のコードを実行できないように、スタックのアドレスを動的に格納するみたいやな
 
書籍にあったこっちも動かしてみたけど、書籍通りパスワード間違っててもオーバーフローでauth_flag書き換えはできんやった
password_bufferのほうが大きいアドレスに配置されてオーバーフローしてもauth_flagを書き換えられん
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int check_authentication(char *password) {
   int auth_flag = 0;
   char password_buffer[16];

   strcpy(password_buffer, password);

   if(strcmp(password_buffer, "brillig") == 0)
      auth_flag = 1;
   if(strcmp(password_buffer, "outgrabe") == 0)
      auth_flag = 1;

   return auth_flag;
}

int main(int argc, char *argv) {
   if(argc < 2) {
      printf("使用方法: %s <パスワード>\n", argv[0]);
      exit(0);
   }
   if(check_authentication(argv[1])) {
      printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      printf("   アクセスを許可します。\n");
      printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
   } else {
      printf("\nアクセスを拒否しました。\n");
   }
}
 
 
うーん、書籍に書いてあることと現実が違うと読み進める気失せるな…
この本の続きやるかどうかは、また考えよう。

面白そうな記事を見つけたので機械学習環境を整備

気になる記事を見つけたので、winデスクトップに機械学習環境を用意してみた

zenn.dev

 

一点、記事ではUbuntu24.04を入れていたけどaptのレポジトリとかでエラー踏みそうなので、Ubuntu-22.04にした

 

Dockerを常時起動状態にするとこまでやった

メモリサイズやディスクサイズの増加はやってない

itmediaの「main()関数の前には何があるのか」を読んでみる(第四回)

itmediaの「main()関数の前には何があるのか」の連載を読んでわからなかったことや、調べたことをメモする

 

第四回は" OSのシステムコールの呼び出しとは&バイナリエディタの使い方"

 

いきなり前回の記事の途中から始まってて、どうやったら逆アセンブルのこの行にたどりつくのか忘れてた。

以下、思い出しながら実行したら、第四回の頭のところに行けた

  • ```bash
  • gdb hello
  • layout asm
  • break write
  • ```

 

先頭からアセンブリ追ってみたいな

図2.30: write()の内部

https://image.itmedia.co.jp/l/im/ait/articles/1703/01/l_r20_Hello04-01.PNG

 

>__write_nocancel()が__kernel_vsyscall()を呼んでるみたいだけど、それぞれが何なのか?

__write_nocancel()はwriteシステムコールを呼ぶAPI内の関数っぽい

nocancel()はスレッドキャンセルポイントでない関数ということらしい

noncancelの前のwriteはキャンセルポイントということかな

pthread_cancelって関数でキャンセルできるみたい

pthreadがPOSIXスレッドっていうPOSIX標準のスレッドとのこと(?)

ここらへんは、マルチスレッドプログラミングを勉強する必要がありそう

(gdb) where
#0  0x00110416 in __kernel_vsyscall ()
#1  0x08053d92 in __write_nocancel ()
#2  0x08067671 in _IO_new_file_write ()
#3  0x0806819b in _IO_new_do_write ()
#4  0x080683ea in _IO_new_file_overflow ()
#5  0x080673f4 in _IO_new_file_xsputn ()
#6  0x08059738 in vfprintf ()
#7  0x08049381 in printf ()
#8  0x080482e2 in main (argc=1, argv=0xbffffc14) at hello.c:5
(gdb)

 

hexeditの/使った検索は、全文検索じゃなくてカーソル以降の検索なんやね

検索したい箇所通り過ぎた状態で検索してもnot foundって言われて謎だった

 

0x80d6750はどこだ?

objdump -d hello | less で検索してもこのアドレスは入ってなかった。

ちなみに、0x110414<__kernel_vsyscall>もどこなのか気になる

helloが確保するメモリの範囲外な気がしてる

 

 

itmediaの「main()関数の前には何があるのか」を読んでみる(第三回)

itmediaの「main()関数の前には何があるのか」の連載を読んでわからなかったことや、調べたことをメモする

(作者サポートページ)

 

第三回は"試行錯誤のデバッグで探る、printf()内のポインタ経由での関数呼び出しが行き着く先とは"

 

break mainでrunしたあと、breakしてるのが↓の行なのよくわからんな

breakが貼られる箇所ってどういう規則になってるんだろう?

図2.8: layout asmの状態でブレークする

https://image.itmedia.co.jp/ait/articles/1703/01/r20_Hello03-01.PNG

 

gdbについて調べると使い方ばかり出てきて、原理がわからんな

 

break function だとfunctionのエントリーポイントでブレイクするらしい

ということは、スタックポインタやベースポインタ更新後が関数のエントリーポイントってこと?kprobeやuprobeも同じタイミングなのかな?

break function
function のエントリにブレークポイントを設定します。

https://flex.phys.tohoku.ac.jp/texi/gdb-j/gdb-j_18.html#:~:text=break%20function,%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

vfprintfにブレイク貼ったら、今度もsub命令でスタック確保したあとで止まった
スタック確保後がエントリーポイントなのかな
図2.15: vfprintf()でブレークする

https://image.itmedia.co.jp/l/im/ait/articles/1703/01/l_r20_Hello03-08.PNG

 

アセンブラで深いとこ潜っていくのは、break貼って勧めて関数呼び出し見つけてstepiで呼び出し先の関数に飛んではbreak貼ってを繰り返していくみたい。

 

itmediaの「main()関数の前には何があるのか」を読んでみる(第二回)

itmediaの「main()関数の前には何があるのか」の連載を読んでわからなかったことや、調べたことをメモする

(作者サポートページ)

 

第二回は"「Hello World!」の主役printf()の内部動作をデバッガGDBで追う"

 

gdbserverを使う話が出てきた

Remote Debugging using GDB - CodeProject

https://www.codeproject.com/KB/mcpp/remote_debugging/3.jpg

 

 

printfの逆アセンブル結果のニーモニックleaがわからなかった

movはラベルを指定すると、ラベルの先にある中身をコピーする。

leaは、アドレスの先の中身ではなく、アドレスそのものをコピーする。

https://3iz.jp/try-assembly.html#:~:text=mov%E3%81%AF%E3%83%A9%E3%83%99%E3%83%AB%E3%82%92,%E3%81%9D%E3%81%AE%E3%82%82%E3%81%AE%E3%82%92%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%E3%80%82

movは中身の値、leaはアドレスをコピーするみたい

 

ニーモニックleaveも知らないな

調べてみると、

leave命令は、以下の2つの命令を組み合わせたのと同等の処理をします。

mov esp, ebp
pop ebp

https://vanya.jp.net/os/x86call/#enterleave:~:text=leave%E5%91%BD%E4%BB%A4%E3%81%AF,ebp%0Apop%20ebp

とのこと。

retの前に必要な処理みたい

retはスタックに積まれてるアドレスから呼び出しもとに戻る命令

上でも参照した以下のサイトが関数呼び出しについてわかりやすかった

x86アセンブリ言語での関数コール (vanya.jp.net)

 

Â