---
# System prepended metadata

title: Waldschrein VGU CTF - Official Write-ups

---

# Waldschrein VGU CTF - Official Write-ups

Xin chào! (｡･∀･)ﾉﾞ 

Mình là ==@Khavid==, người viết hầu hết các challenge của cuộc thi **"Waldschrein VGU CTF"** đợt này. Chúc mừng các team đã hoàn thành xuất sắc và nỗ lực hết mình cho đến cuối cuộc thi. Nếu các bạn đã hoàn thành tất cả các challenge, xin chúc mừng! Còn nếu bạn chưa hoàn thành hết, cũng không sao cả, bởi vì mình ở đây để giúp các bạn giải đáp những thắc mắc, những câu hỏi về các challenge, hay nói đúng hơn - những cuộc hành trình còn gian dở của tất cả các team.

## Challenges
### General skills
- [Hide and Seek](#General-skills-/-Hide-and-Seek)
- [Seek or Hide](#General-skills-/-Seek-or-Hide)
- [Head](#General-skills-/-Head)
- [Basic cryptography](#General-skills-/-Basic-cryptography)
- [The Github Organization (by @Qân)](#General-skills-/-The-Github-Organization)
### Forensics
- [Weird sound](#Forensics-/-Weird-sound)
- [Reach for that sky (by @moonsolo)](#Forensics-/-Reach-for-that-sky)
- [Congratulations](#Forensics-/-Congratulations)
- [A letter from the present, to the past](#Forensics-/-A-letter-from-the-present,-to-the-past)
### Reverse engineering
- [Doll](#Reverse-engineering-/-Doll)
- [reez_revenge](#Reverse-engineering-/-reez_revenge)
- [Waldbild (by @Qân)](#Reverse-engineering-/-Waldbild)
### Binary exploitation
- [math (by @Lunaere)](#Binary-exploitation-/-math)
### Cryptography
- [Easy XOR](#Cryptography-/-Easy-XOR)
- [Erénegiv cipher](#Cryptography-/-Erénegiv-cipher)
- [GCDDDD (by @Qân)](#Cryptography-/-GCDDDDD)
- [Prime EEEEEEEEE (by @Qân)](#Cryptography-/-Prime-EEEEEEEEE)
- [Peak Prime (by @Qân)](#Cryptography-/-Peak-Prime)
- [Witch's Broadcast (by @Qân)](#Cryptography-/-Witch's-Broadcast)
### Miscellaneous
- [Lingo Linging](#Miscellaneous-/-Lingo-Linging)
- [Coding Skill](#Miscellaneous-/-Coding-Skill)
- [Draw](#Miscellaneous-/-Draw)
- [Guess my number (by @Qân)](#Miscellaneous-/-Guess-my-number)
- [A Gift from Valedictorian (by @Hồ Nguyễn Phú)](#Miscellaneous-/-A-Gift-from-Valedictorian)
### OSINT
- [Between those trees](#OSINT-/-Between-those-trees)
- [The girl](#OSINT-/-The-girl)
- [The girl v2](#OSINT-/-The-girl-v2)
- [Gotta go fast!](#OSINT-/-Gotta-go-fast!)
- [Resistance (by @moonsolo)](#OSINT-/-Resistance)
- [Otter](#OSINT-/-Otter)
### [Các challenge khác](#Other-challenges)

## General skills / Hide and Seek

> *A large dense forest including a wide variety of tree species. Wherever you look, I leave a little bit of what you want. Soon, I hope, it will be revealed.*

Thoạt đầu khi mở file lên, bạn sẽ thấy một đống chữ chằng chịt như thế này. 

![image](https://hackmd.io/_uploads/r141EJdTxg.jpg)

Thế nó có ý nghĩa gì không? Hoàn toàn có :D
Lúc này, cách tốt nhất là sử dụng lệnh [`strings` của Linux](https://linux.die.net/man/1/strings) để có thể tìm kiếm xem có một ký tự nào có nghĩa không (a.k.a printable character, ASCII, Unicode, [Graphic character](https://en.wikipedia.org/wiki/Graphic_character)).

```powershell
$ strings hideandseek.txt
$
```

Lạ nhỉ, không có ký tự nào được in ra. Mình có làm sai ở đâu không? 
Hoàn toàn không. Vì khi `strings`, lệnh chỉ execute ra các sequence chứa tối thiểu 4 ký tự. Thế nếu bây giờ mình giảm số lượng cần tìm lại thì sao nhỉ?

Thử tìm thử một ký tự ASCII bất kỳ nào trong file text đó nhỉ. Giả sử như `v` đi.

![image](https://hackmd.io/_uploads/r1nUjyOpxl.jpg)

Vậy là vẫn có ký tự ASCII trong file. Nhưng xung quanh chữ `v` này là các ký tự non-ASCII, nghĩa là chúng ta cần phải tìm những sequence nào chỉ chứa có 1 ký tự duy nhất.

```powershell
$ strings -n 1 hideandseek.txt
v
g
u
c
y
p
h
e
r
{
y
0
u
_
f
0
u
n
d
_
m
3
}
```

Thế là xong, đã có flag. (✿◡‿◡)

## General skills / Seek or Hide

> *Your discovery of me in the midst of the wilderness is quite remarkable. Now come to that "place", because I have something important to tell you.*

Chắc chắn khi đọc file, bạn sẽ nghĩ rằng đây sẽ là một challenge dễ hơn "Hide and Seek", nhưng mình thì nghĩ ngược lại. (・ω・)

```json
{
    "hypergraphless2219": "bQ1JCWtkosBeQXptf",
    "ultralinktic9614": "Pq6iMOeAzqdesbT",
    "autocloudion3148": "k1e6J7DHXj",
    "intrasenseware8864": "bsvmm2dkw",
    "transsensement3016": "plDojce5BWtSD",
    "multicloudware8326": "qbY0FNwz",
    "transflower9111": "DwTixb0ph5dsQJZYFKfd",
    "hyperbyteful2627": "qxr81GhO4m8OZnZFmd9",
    ...
```

>[!Tip]
> Với đại đa số các challenge có yêu cầu về tra cứu string như thế này, thường chúng ta sẽ tra xem có những thứ liên quan đến flag hay không. Ví dụ như `flag`, `fl4g`, ... hay thậm chí có thể tra xem có string chứa flag format hay không.

```powershell
$ strings seekorhide.json | grep "vgu"
$
$ strings seekorhide.json | grep "flag"
    "flagpart001": "dWx0cmFkYXRhaW9uODAwMw",
    "flagpart002": "bWljcm9zZW5zZWlmeTc5ODE",
    "flagpart003": "YXV0b3N0cmVhbWluZzE4NzI",
    "flagpart004": "YXV0b2Zvcm1pZnk5MDE1",
    "flagpart005": "YmlvZmxvd21vZGU1MjAy",
```

Thử decode xem các đoạn string này có ý nghĩa gì.

```powershell
$ echo dWx0cmFkYXRhaW9uODAwMw | base64 --decode
ultradataion8003
$ echo bWljcm9zZW5zZWlmeTc5ODE | base64 --decode
microsenseify7981
$ echo YXV0b3N0cmVhbWluZzE4NzI | base64 --decode
autostreaming1872
$ echo YXV0b2Zvcm1pZnk5MDE1 | base64 --decode
autoformify9015
$ echo YmlvZmxvd21vZGU1MjAy | base64 --decode
bioflowmode5202
```

Thế nó liệu có phải là flag cuối cùng chưa? Chưa, bởi vì nó không phải là format của flag chúng ta đang tìm. Thế nên làm gì tiếp nhỉ?

Nếu các bạn có để ý trong description của challenge, có một thứ được đề cập tới là `"place"`. Thế thử tìm xem nó có ý nghĩa gì trong file trên.

```powershell
$ strings seekorhide.json | grep "place"
    "place": "d2hhdCB1IHNlZSBpcyBub3Qgd2hhdCB1IHRoaW5r",
$ echo d2hhdCB1IHNlZSBpcyBub3Qgd2hhdCB1IHRoaW5r | base64 --decode
what u see is not what u think
```

Có nghĩa rằng, thứ các bạn decode ở trên chưa phải là flag cuối cùng. Chúng ta cần phải tiếp tục tìm hiểu xem những thứ trên là gì.

```powershell
$ strings seekorhide.json | grep "ultradataion8003"
    "ultradataion8003": "dmd1Y3lwaGVye20",
$ echo dmd1Y3lwaGVye20 | base64 --decode
vgucypher{m
```

Một phần của flag đã hiện ra rồi, thế bạn biết phải làm gì tiếp theo rồi đấy. :P

## General skills / Head

> *I imagine this train hasn't been in service for quite some time since it is deserted. But the "head" engine isn't there.*

Chắc chắn khi bạn mở file này lên theo dạng text, bạn sẽ không có thông tin gì hữu ích như 2 bài ở trên. Thế nhưng, nếu để ý ở đầu file, bạn sẽ thấy có dòng chữ `JFIF`, có nghĩa rằng file này là [file ảnh JPEG](https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format).
Thế nếu đổi file extensions từ `head_flag` thành `head_flag.jpg`, tức là chúng ta đã thành công rồi đúng không? 

![image](https://hackmd.io/_uploads/HJ4fHfdplg.jpg)

Có điều gì đó không đúng thì phải, có lẽ chúng ta nên xem file này dưới dạng hex.

![image](https://hackmd.io/_uploads/SySzUz_aex.jpg)

Dễ để ý thấy rằng ở các hex đầu của file không đúng với định dạng của một file JPEG, tức là [JPEG signature](https://www.file-recovery.com/jpg-signature-format.htm) của nó đang bị sai. Thế nên chúng ta phải sửa giá trị của 3 hex đầu thành `FF D8 FF`, rồi sau đó extract file lại về với định dạng `head_flag.jpg`, trong hình chắc chắn sẽ có flag. (☆▽☆)

## General skills / Basic cryptography

> *Apparently, that's not someone writing the next decimal point of pi. Surely no one would write such an uncommon integer number.*

```
26167599601202230462589152249086831380238632493330412679586241293311579297120605734797280172357355455361591424597157681263830020178698183548763620916598830196347226788265296949469309525419423372557981959232646813461361323336166408249521959148343525592998477183643151941591094008755520669532917514474212048101888758204427306343750377813362597841027504103295592397560298771916502693249569298887230862625647588213778116161446171159562798507472922250765209088555108355172021691917404780480131989670251461495038316963027728711324291992963159633091468106620224303477999316135252738264439129832036256851641236353807261063562562816987517062668773838952937210828078688914574199480470363127786650526801406387068891049362971904446292162034656837463666300161221369057535378281002366051494881763372482400008231380455560174651430153512194109855682616781110311344760425355078557012808558909818334084085284401193999298896139655375987505021198389795311547860103504585161733343685178086253447438095627737086355515110879818972663214598113206585414261548940069665512462971088476630582768394885217316366063269288087248418996511415434083754528566162734105548569814690246032229725735856004417031217914085027629482906453091813999664443372217635168327339632253103152125021215700323274387719476628046806329892740376110564366434277367036163807721453874320467140258690198655234402598199210610066613303424389259795106874518838627433848635706387312390232118597433892688348767305301655194383575507343384219908708192232877350015451121769199185257870715128631602396593282530195978972700276536468625048361644854737217285038459853254291557455965951153388608357714299455171591723502537290514308258898550952872327673349000071422627158389792732344294227734962723879388734236971167156671438838198002748183336609292935988118774162791142025947260974515381422149472015047092761475326452000257378483473004838234067231832736592221153141160729816720523553832174705570014822316250077935631545395645511437458711031082809854119928861032158746143106154394272388234674919344450072431194652379759354231096412985720380075356264771349898624375638617932767810595629652852573110828622269306027719673604332868604781774468474398534864306325060470875192038869931649843996087313782223466022360800388340160008212879807667316725256993040686932291759730926934610396307911985542445521544343171858169176222583322805211637597064310819298340048030603515428967177095419145249789491104228708213449370342279299039486543750644731090113114315192682847462572932083262747994858083792555771518852189286239720822917715545101637737236446091349915301753934035825287981590180184549747017663975298956360453482673277377861996687703841282520006513253639558894587561026496154778008979821519981604499492225084331607689103789803079606377587518886741274632833015027199340860779028916461634790644749747788630721193128998701849894161579792971129931480476956350454093041012877820926460088262838332966522880307253379533611704589863076480603372706932177482844859503419990250767208954482062165997808340891379981451507238465895419695666536038941154157632137113009526880003136574250698482225319847398624874928452119691068211908183527621958892190625281526620632311645936294814013663287655121556042420892265131960149539358953475006113948303994970620836469619390274002225157197236972737653753293347077252747750892454178586491324413691120363669010671799207661700155111569109949351948951636672565438138330116305256133017535761467762374849423435536786849281093669257794310565643615581336953138957601596522849486753709086052739048404122282091685077900467156926428461763764085543419354821287369262866544630585589232939082419200546063321734336164653586181323049300399540771813114743736340562099658007889979600348257593038734435166869650776303632841653859068646432629023407532745725848319044721808333495663019220001610585873048301215270932569308379714603113506856164755001618946889351938640799978792731220951787058148430571022495506093373951453364437858906593541242722420999912514892084931077346915030212978656375048633053747155476410302804978606831923398711010003789756105622819700029723481836753924090409463721922775349683996774086586400426534288925523147232447846331644992773215835816815349641551338550157627235783112880591799791744775625016074003626233931629213655724356038142768830800770913285624797184119583292297845330563862744018350264276846022841410988190968413052538927613465165646274202196908305341849107382673935897300717504438650430896136989644101365054203584127373206979860030690032008815166757204338407449369466274082707070719445961508464154457029399414152923938143976489170145172058567682733848934290727854318767045985005276999272059166407314904034547386829534805349248032062256759259019451814735320239032211282890037389315058873626727150249486533679757512190171249638924801623965457485882022186484459776067088599013727708452970607980831062288951547803811229785342767108970532501283804606593652983325683550333709786579033467912934109957571663274364652298163351567669568622619077485904542305195031636903386350199685336357554109970547299963082888418222849582956437578149739158110004849236757654021645934175200101075914795349761636352412890807304133166264080965009420045855919136722707309768913913840175050045529348686831459144952702649375971991706962824363712927871075724994439611160285419922836462457691601324315395497000539727621756923236269703755554312216147974410471448247622003803619350254634077628874633770068369316093348201754598436455361075231586265602870666344737324119263484090749319934387037951539488760577606473781453039401056000226351785105521693317267444594135610073695764332070857912893000951345606199994068279997868201302823523097359697436181163558385129581301042255856614572020440175569504518588784035765596625660683757777070420403366429113561121335574763341410120648338026264625645000236015184229440091700440054924424733733822121306997187631666315200085528967878364622290882416512093787504275968062960163329953885529569821837793546826288894032798594874767021972309873154795689554191933514307505398296419193547807353087917790163338769239476446028577306908375087338139735972874189107502522416891945198873716407417522432206391808175510050453372258220924501947493881715850544004027893483494432760980220550910067359847946962655641710489735256369129895458392189192017529306582756274564109887582991235334106242817187183325858848735899649309280222248043226251361134962607619983713287445491761
```

>[!Note]
>Ý tưởng khi mình muốn làm bài này chính là cho bạn làm quen với những dạng cơ bản của encrypt/encode. Có rất nhiều tool để xử lý việc này, nhưng ở trong write-ups này mình sẽ làm bằng [kt.gy tools](https://kt.gy/)

Giờ mình sẽ thử input số này dưới dạng `INT` xem thử nó đang muốn dẫn mình tới dạng encrypt nào.

![image](https://hackmd.io/_uploads/B1xrQcMd6gg.jpg)

Vậy là mình có một dãy nhị phân ở dạng `ASCII`, tiếp tục lấy dãy nhị phân này input ở ô `BIN`.

![image](https://hackmd.io/_uploads/B1LisGu6lx.jpg)

Chúng ta tiếp tục được một dãy ký tự ở dạng `ASCII`.

>[!Tip]
> 
>|          | Base32 | Base64 |
>| -------- | -------- | -------- |
>| Số lượng ký tự     | 32     | 64     |
>| Ký tự | `A-Z`, `2-7` | `A-Z`, `a-z`, `0-9`, `+`, `/` |

Tiếp tục đưa dãy Base32 này vào ô `B32`, chúng ta sẽ được dãy Base64 ở dạng `ASCII`. Và đưa dãy Base64 vào ô `B64`, ta sẽ được dãy Hex.

![image](https://hackmd.io/_uploads/Sk9R0M_6xe.jpg)
![image](https://hackmd.io/_uploads/BJffy7u6el.jpg)

Và cuối cùng, đưa dãy Hex này vào ô `HEX`, chúng ta sẽ được flag hoàn chỉnh. Thế là bạn đã biết hết tất cả các dạng cơ bản nhất của encrypt rồi đấy. o((>ω< ))o

## General skills / The Github Organization

Vào [link Github](https://github.com/VGU-Cypher-Club/demo-repository) trong đề bài, chúng ta có thể thấy rằng trong source code trong commit này hoàn toàn không có gì để nghi ngờ cả. 

Nhưng nếu check trong history commit của trang này có thể thấy toàn bộ history của nó. Đặc biệt có thể thấy một số commit xóa/thêm một đoạn kì lạ.

![image](https://hackmd.io/_uploads/r1N2Kg4SWx.png)

Thử decode đoạn này ở dạng Base64. Thế là chúng ta đã có flag.

## Forensics / Weird sound

> *Bird language? Fox language? Or maybe human language? I don't know, that's weird.*

Đây là một file âm thanh rất cơ bản, khi kiểm tra metadata của file này thì cũng không thấy điểm gì bất thường. Ở đây chúng ta có thể dùng các phương pháp của [Audio Steganography](https://ctf-wiki.mahaloz.re/misc/audio/introduction) để có thể giải quyết bài này.

Có rất nhiều cách để có thể xem thử waveform/spectrum của file này ra sao. Một cách nhanh nhất là dùng [Audacity](https://www.audacityteam.org) để có thể xem các dạng audio như này.

![image](https://hackmd.io/_uploads/BJ77VzK0xe.jpg)

Ok xong, có flag. (´。＿。｀)

## Forensics / Reach for that sky

Kudos tới ==@moonsolo==, author của challenge này!

> *A big blue sky, where all my goals and aspirations are written in the stars. I just want to reach it... Or maybe I'm hungry now, heh.*

Tương tự như bài [Head](#General-skills-/-Head), nhưng bây giờ chúng ta đã có một bức ảnh hoàn thiện. Việc bây giờ ở đây là xem rằng vấn đề của challenge này đang nằm ở chỗ nào. File này bây giờ đang ở định dạng `PNG`, nên chúng ta cần tìm những document có đề cập đến [file signature và chunks của PNG](https://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html).

![image](https://hackmd.io/_uploads/rkLUdvhRgg.jpg)

Có thể nhận thấy rằng PNG file signature hoạt động bình thường. Chúng ta có thể kiểm tra tiếp đến chunk của IHDR, có các thông tin như độ dài rộng của ảnh, color type, độ sâu của bit... Chúng ta có thể dựa vào thông tin này để có thể chỉnh thử xem độ dài - rộng của ảnh xem như thế nào.

Trước tiên, hãy tăng thử 1 hex của width.

![image](https://hackmd.io/_uploads/H1OwcDhCel.jpg)
![image](https://hackmd.io/_uploads/ByA5cDnRxl.jpg)

Tuy tăng 1 giá trị của width nhưng file ảnh đã bị corrupted. Thế nên đây không phải là phương án mà chúng ta cần. Tiếp theo, hãy tăng thử 1 hex của height.

![image](https://hackmd.io/_uploads/ryRmivn0ee.jpg)
![image](https://hackmd.io/_uploads/BkrujwnAgl.jpg)

Bức ảnh hoạt động bình thường, ngoài ra size của bức ảnh cũng được tăng thêm. Thế nên chúng ta có thể điều chỉnh height của bức ảnh này để xem có xuất hiện gì trong bức ảnh không.

![image](https://hackmd.io/_uploads/BkxYAP20ee.jpg)
![image](https://hackmd.io/_uploads/HkToAv3Axx.png)

Thế là đã có flag. (。・ω・。)

## Forensics / Congratulations

> *We are excited to inform you that you have been selected to join VGU Cypher as a member, following a successful performance in the interview process. Your skills, creativity, and passion for hacking and cybersecurity stood out, and we are thrilled to welcome you to our community. Are there any questions you would like to ask us?*

Đây là một challenge giúp bạn làm quen với việc đọc và phân tích network traffic. Thường đối với những dạng challenge như này, chúng ta có thể dùng [Wireshark](https://ctf101.org/forensics/what-is-wireshark) để có thể đọc các dạng traffic như TCP/DNS/HTTP/...

![image](https://hackmd.io/_uploads/BJ8mWoaAxx.jpg)

Vì biết đây là một bức thư được giao tiếp bởi 2 bên, nên chúng ta có thể filter các traffic này bằng các giao thức `SMTP` để có thể dễ tìm hiểu hơn thông tin đang được giấu ở đâu.

![image](https://hackmd.io/_uploads/Bk4hfiTCgx.jpg)

Ở protocol `SMTP` sẽ là các thông tin cơ bản nhất của một bức thư, như tiêu đều, địa chỉ người nhận và người gửi, ... Còn ở protocol `SMTP/IMF` là data những gì trong bức thư.

![image](https://hackmd.io/_uploads/SJnl4oTRel.jpg)

Tại đây bạn có thể để ý một dòng string Base64, thử decode nó ra những gì.

![image](https://hackmd.io/_uploads/Hks8VipRxg.jpg)
![image](https://hackmd.io/_uploads/ByQY4iaCgl.jpg)

Xong, flag đã có, submit thôi. ( •̀ ω •́ )✧

## Forensics / A letter from the present, to the past

> *A short diary of my life, my "little piece of my story".*

Mình nghĩ đây là một bài khá "khó xơi" với các team vì phải qua nhiều bước (2 bước) mới có thể tìm ra được flag có gì.

![image](https://hackmd.io/_uploads/SyFCUi6Axg.jpg)

Như đã đề cập ở bài [Weird sound](#Forensics-/-Weird-sound), ngoài việc có thể giấu thông tin trong audio, thì stegnography có thể áp dụng cho tất cả mọi loại file, trong đó có cả hình ảnh. Và có nhiều cách để có thể giấu, ví dụ như giấu trong metadata, giấu bằng `binwalk`, `zsteg`, `steghide`, `foremost`, ...

Nếu bạn check bằng `strings` trong Linux hay check trong metadata của bức ảnh này, bạn sẽ không nhận ra một điểm gì bất thường cả. Thế nên chúng ta có thể dùng các phương pháp khác đã được nêu ở trên, để tìm ra trong bức ảnh này có gì. Một trong những cách đó là [check `binwalk` của file này](https://www.unroll.ing).

![image](https://hackmd.io/_uploads/SktJjjaClg.jpg)

Có thể nhận ra rằng ngoài bức ảnh này thì vẫn còn 1 file zip ở trong, thử extract zip từ file ảnh này xem chúng ta sẽ có file gì.

![image](https://hackmd.io/_uploads/r16YijT0eg.jpg)
![image](https://hackmd.io/_uploads/ry10ospRll.jpg)

Có thể thấy đây là một đoạn text bình thường, thế nhưng dòng gần cuối cùng là dòng khiến các bạn khó khăn trong việc giải nghĩa nhiều nhất. Chúng ta có thể thử tìm hiểu xem các ký hiệu này có ý nghĩa gì.

![image](https://hackmd.io/_uploads/ry-MajTCxe.jpg)

Nếu bạn bật file này bằng Visual Studio Code, có thể nhận ra rằng các ký tự này là các ký tự non-ASCII. Và cũng có thể nhận ra rằng các ký tự này không hề xuất hiện nếu chúng ta chỉ đọc bằng mắt thường. Có thể kiểm tra lại một lần nữa bằng cách copy các ký tự này và search thử trên mạng.

![image](https://hackmd.io/_uploads/Bk0TRi6Rex.jpg)
![image](https://hackmd.io/_uploads/ByqLJ360le.jpg)

Vậy ở đây, các ký tự này được gọi chung là [zero-width space characters](https://en.wikipedia.org/wiki/Whitespace_character#NQSP). Giờ chúng ta đã có dữ kiện, hãy thử tìm một tool nào đó có thể decode được những ký tự này. Một trong số đó mình hay sử dụng chính là [StegZero](https://stegzero.com).

![image](https://hackmd.io/_uploads/rJ-Ne3p0gx.jpg)

Xong, vậy là chiếc flag cuối cùng của category Forensics đã được giải xong. o(*≧▽≦)ツ┏━┓

> [!Note]
> Fun fact về challenge này:
> 
> Challenge này được mình lên ý tưởng ngay sau 6 tiếng mở giải. Vì thấy các đội giải khá nhanh các bài Forensics, nên mình nghĩ cần phải có một bài gì đó "thật sự khó", để các đội khó có thể giải ra một cách nhanh nhất. Thế nên từ hình ảnh, bức thư được tạo ra là hoàn toàn làm thủ công, không có một sự trợ giúp của AI hay người nào khác. Nên có thể nói, mọi thứ mình làm ra trong challenge này và các challenge khác, đều là tâm tư và tình cảm của mình đặt vào. (＞︿＜)

## Reverse engineering / Doll

> *"Found that doll in front of my house. In this jungle, there is only my house living here, don't know anybody else put it here.", Khavid said.*

Hãy thử chạy chương trình thử xem chương trình đang muốn yêu cầu chúng ta tìm gì.

```powershell
$ ./doll
Hello!... Hello? Why dont you say "Hello!" to me?
Bye.
$ ./doll abc
Huh? What do you mean? I just wanted you to say "Hello!".
Bye.
$ ./doll Hello!
Oh hey! You said "Hello!" to me!
But thats not what I wanted to hear... Give me my name instead.
Bye.
$ ./doll Hello! abc
lol, not my name.
Bye.
$
```

Có thể thấy file này cần 2 argument, 1 argument được cho trước là "Hello!", argument thứ 2 là tên của nhân vật này, thứ mà chúng ta chưa biết và cần tìm tới. Thế nên bây giờ chúng ta phải đi tìm tên của nhân vật này. 

Đối với các dạng bài Reverse như thế này, thì chúng ta cần có những tool để đọc. Một trong số đó là [Ghidra](https://github.com/NationalSecurityAgency/ghidra), thứ để convert binary code thành machine code, hoặc có thể thành code C cơ bản nhất.

![image](https://hackmd.io/_uploads/r1eVfBJy-g.png)

Khi bật file, chúng ta có thể để ý một đoạn code ở dưới:

```c
  else if (param_1 < 4) {
    strcpy(&local_50,*(char **)(param_2 + 0x10));
    if (((((local_50 == 'g') && (local_4f == 'a')) && (local_4e == 'r')) &&
        ((local_4d == 'a' && (local_4c == 'k')))) &&
       ((local_4b == 'u' && ((local_4a == 't' && (local_49 == 'a')))))) {
      puts("Oh? You know me? I think I need to show you something...");
    ...
```

Ở đây nó đang cho điều kiện rằng nếu tên nhập vào tương ứng với lại điều kiện ở trên, thì sẽ cho ra một thứ gì đó. Giờ hãy nhập thử tên này vào chương trình để xem nó cho chúng ta ra cái gì.

```powershell
$ ./doll Hello! garakuta
Oh? You know me? I think I need to show you something...
vgucypher{g4r4kut4_d0ll_pl4y}
$
```

Vậy là chúng ta đã có flag. Ngoài ra vẫn còn một cách khác để có thể giải ra flag. Nếu để ý ở dưới đoạn code được nêu trên là một list các ký tự đang bị XOR, và chúng ta cần phải XOR ngược lại để có thể tìm ra flag.

```c
      local_48[0] = 0x66;
      local_48[1] = 0x77;
      local_48[2] = 0x65;
      local_48[3] = 0x73;
      local_48[4] = 0x69;
      local_48[5] = 0x60;
      local_48[6] = 0x78;
      local_48[7] = 0x75;
      local_48[8] = 0x62;
      local_48[9] = 0x6b;
      local_48[10] = 0x77;
      local_48[0xb] = 0x24;
      local_48[0xc] = 0x62;
      local_48[0xd] = 0x24;
      local_48[0xe] = 0x7b;
      local_48[0xf] = 0x65;
      local_48[0x10] = 100;
      local_48[0x11] = 0x24;
      local_48[0x12] = 0x4f;
      local_48[0x13] = 0x74;
      local_48[0x14] = 0x20;
      local_48[0x15] = 0x7c;
      local_48[0x16] = 0x7c;
      local_48[0x17] = 0x4f;
      local_48[0x18] = 0x60;
      local_48[0x19] = 0x7c;
      local_48[0x1a] = 0x24;
      local_48[0x1b] = 0x69;
      local_48[0x1c] = 0x6d;
      local_48[0x1d] = 0;
      local_5c = 0;
      while( true ) {
        sVar3 = strlen((char *)local_48);
        if (sVar3 <= (ulong)(long)local_5c) break;
        putchar(local_48[local_5c] ^ 0x10);
        putchar(10);
        local_5c = local_5c + 1;
      }
```

Chuyển đoạn code từ C thành Python để có thể chạy ra flag.

```python=
local_48 = [0x66, 0x77, 0x65, 0x73, 0x69, 0x60, 0x78, 0x75, 0x62, 0x6b, 0x77, 0x24, 0x62, 0x24, 0x7b, 0x65, 0x64, 0x24, 0x4f, 0x74, 0x20, 0x7c, 0x7c, 0x4f, 0x60, 0x7c, 0x24, 0x69, 0x6d, 0x00]

for b in local_48:
    if b == 0:
        break
    print(chr(b ^ 0x10), end = "")
```
```
vgucypher{g4r4kut4_d0ll_pl4y}
```

> [!Note]
> Fun fact về challenge này:
> 
> *Challenge này được mình dựa trên ý tưởng của bài hát này. Thế nhưng khi search lại ý nghĩa của tên bài hát, thì "garakuta" không phải là tên của con búp bê, mà có nghĩa là "bãi phế liệu", nơi mà con búp bê này được phát hiện. 
> Mà thôi, lỡ làm challenge rồi thì hết đường lui.* ¯\\_(ツ)_/¯
> {%youtube I4MQd751wgM %}

## Reverse engineering / reez_revenge

> *Just came back from CSCV 2025, and I found it's interesting to take revenge on this.*

Nó thật sự là những gì mình đã trải qua trong 8 tiếng ngồi trong phòng thi (và không giải ra một bài Reverse nào cả). Nhưng yên tâm, bài này dễ hơn cực kì nhiều. ♪(´▽｀)

```powershell
$ ./reez_revenge
Enter two integers: 5 10
5 ^ 10 = 15
Thats all, or is it?
$
```

Khi chạy file, bạn chỉ nhận được như thế này thôi. Hãy thử bật file này trong Ghidra để xem nó dẫn tới đâu.

![image](https://hackmd.io/_uploads/HyAi31lyZl.png)

```c
undefined8
main(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4,undefined8 param_5,
    undefined8 param_6)

{
  long in_FS_OFFSET;
  uint local_1c;
  uint local_18;
  uint local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter two integers: ");
  __isoc99_scanf("%d %d",&local_1c,&local_18);
  putchar(10);
  local_14 = local_18 ^ local_1c;
  printf("%d ^ %d = %d",local_1c,local_18,local_14);
  puts("That\'s all, or is it?");
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}
```

Và như trong dự đoán, hàm main chẳng có gì ngoài thứ chúng ta mới output ra cả. Thế flag được giấu chính xác nằm ở đâu? Tìm kiếm thử các functions trong chương trình này mới thấy có 64 functions khác đang nằm rải rác từ `aabbcc` đến `AABBCC`. ＼（〇_ｏ）／

![image](https://hackmd.io/_uploads/rk8EpyxyWe.png)

Thử tìm hiểu trước xem 1 function có hoạt động như thế nào. Ví dụ như function `aabbcc`:

```c
void aabbcc(undefined8 *param_1)

{
  long in_FS_OFFSET;
  uint local_4c;
  byte local_48 [40];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  local_48[0] = 99;
  local_48[1] = 0x7f;
  local_48[2] = 0x62;
  local_48[3] = 0x62;
  local_48[4] = 0x69;
  local_48[5] = 0x4f;
  local_48[6] = 100;
  local_48[7] = 0x78;
  local_48[8] = 0x79;
  local_48[9] = 99;
  local_48[10] = 0x4f;
  local_48[0xb] = 0x79;
  local_48[0xc] = 99;
  local_48[0xd] = 0x4f;
  local_48[0xe] = 0x76;
  local_48[0xf] = 0x71;
  local_48[0x10] = 0x7b;
  local_48[0x11] = 0x75;
  local_48[0x12] = 0x4f;
  local_48[0x13] = 0x76;
  local_48[0x14] = 0x7c;
  local_48[0x15] = 0x71;
  local_48[0x16] = 0x77;
  local_48[0x17] = 0x31;
  local_48[0x18] = 0x31;
  local_48[0x19] = 0x31;
  local_48[0x1a] = 0x31;
  local_48[0x1b] = 0x31;
  *param_1 = 0x78644f6962627f63;
  param_1[1] = 0x71764f63794f6379;
  param_1[2] = 0x3177717c764f757b;
  *(undefined4 *)(param_1 + 3) = 0x31313131;
  for (local_4c = 0; local_4c < 0x1b; local_4c = local_4c + 1) {
    local_48[(int)local_4c] = local_48[(int)local_4c] ^ 0x10;
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
```

Phân tích code này thành dạng code C thường gặp, chúng ta sẽ có đoạn code siêu ngắn như sau:

```c 
void aabbcc(char data[]) {
    char temp[] = {0x63, 0x7f, 0x62, 0x62, 0x69, 0x4f, 0x64, 0x78, 0x79, 0x63, 0x4f, 0x79, 0x63, 0x4f, 0x76, 0x71, 0x7b, 0x75, 0x4f, 0x76, 0x7c, 0x71, 0x77, 0x31, 0x31, 0x31, 0x31, 0x31};
    memcpy(data, temp, sizeof(temp));
    for (int i = 0; i < sizeof(temp) - 1; i++) {
        temp[i] ^= 0x10;
    }
}
```

Thử chạy đoạn này bằng Python:

```py= 
temp = [0x63, 0x7f, 0x62, 0x62, 0x69, 0x4f, 0x64, 0x78, 0x79, 0x63, 0x4f, 0x79, 0x63, 0x4f, 0x76, 0x71, 0x7b, 0x75, 0x4f, 0x76, 0x7c, 0x71, 0x77, 0x31, 0x31, 0x31, 0x31, 0x31]

for i in range(len(temp)):
    temp[i] ^= 0x10

data = bytes(temp)
print(data)
```

```
b'sorry_this_is_fake_flag!!!!!'
```

Hãy thử với một function khác, ví dụ như function `AabBcC`:
```c
void AabBcC(char data[]) {
    char temp[] = {0x7e, 0x7f, 0x64, 0x4f, 0x71, 0x4f, 0x76, 0x7c, 0x71, 0x77};
    memcpy(data, temp, sizeof(temp));
    for (int i = 0; i < sizeof(temp) - 1; i++) {
        temp[i] ^= 0x10;
    }
}
```
```
b'not_a_flag'
```

Một function khác nữa, như `AaBBcC`:
```c
void AaBBcC(char data[]) {
    char temp[] = {0x40, 0x51, 0x43, 0x55, 0x4f, 0x46, 0x5e, 0x53, 0x44, 0x4d, 0x58, 0x57, 0x5b, 0x5f, 0x58, 0x51, 0x69, 0x00, 0x02, 0x69, 0x50};
    memcpy(data, temp, sizeof(temp));
    for (int i = 0; i < sizeof(temp) - 1; i++) {
        temp[i] ^= 0x36;
    }
}
```
```
b'vgucypher{naming_64_f'
```

Vậy chúng ta có thể đưa ra nhận xét rằng: Với những function có chứa `local_48[(int)local_4c] = local_48[(int)local_4c] ^ 0x10;` thì các function này đều chứa flag giả. Ngược lại, nếu nó chứa `local_48[(int)local_4c] = local_48[(int)local_4c] ^ 0x36;` thì nó sẽ chứa các part của flag.

Dựa vào điều này, chúng ta có thể xác định được vị trí của 3 flag trong số 64 function, sắp xếp chúng lại chúng ta sẽ có flag nằm ở các function `AaBBcC` - `AAbbCC` - `ABbcc`.

Thế là chúng ta đã có flag. 〜(￣▽￣〜)

:::danger
:skull: Trong trường hợp có ai hỏi về "Tại sao lại là `0x36`", tôi không biết gì nhé. (°ー°〃)
:::

## Reverse engineering / Waldbild
Có nhiều cách để các bạn có thể decode file `.apk` thành source code để có thể tìm hiểu xem cách hoạt động của file này ở dưới dạng file Java và các file XML. 1 là sử dụng [JADX](https://github.com/skylot/jadx), 2 là sử dụng [apktool](https://apktool.org), cách nào cũng đều cho ra kết quả hết.

Thường thì các challenge về dạng này đa số sẽ giấu flag trong logic của code, hoặc đơn giản hơn là giấu ở `strings.xml` (văn bản có thể dịch được và hiển thị thông qua UI của người dùng).

Để check được, kiểm tra thử `res/values/strings.xml`.

![image](https://hackmd.io/_uploads/S1Gu4lVB-x.png)


## Binary exploitation / math

Kudos tới ==@Lunaere== (FPT EHC), author của challenge này!

> [*math*](https://www.youtube.com/watch?v=y3e2DNXMq1A)

:::warning
:warning: Đây là intended solution đến từ author!
:::

```python=
from pwnaio import *

exe = './chall'
elf = context.binary = ELF(exe, checksec = False)
libc = elf.libc

def GDB(): gdb.attach(p, gdbscript='''


c
''') if not args.REMOTE else None

if args.REMOTE:
    p = remote('host', port)
    context.update(log_level="DEBUG")
else:
    p = process(argv=[exe], aslr=False)
    
def debug():
    if args.GDEBUG: context.update(log_level="DEBUG"); GDB(); input()
    elif args.GDB: GDB(); input()

debug()
#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯#

for i in range(100):
    ru(b'Question')
    ru(b':')
    a = rl()
    m = a[1:-1]
    print(m)
    sla(b'Answer', f"{eval(m)}".encode())

inter()
```

Flag: `vgucypher{S1mpl3_qu1ck_m4th_f0r_5th_gr4d3}`

## Cryptography / Easy XOR

> *Each individual have a unique narrative. Every door has its own key, just like that. The key to that door eludes me; all I can recall is my memory.*

```
38 29 26 3b 2a 3e 26 36 2a 28 36 7e 21 69 3d 77 11 62 6d 0c 20 7e 24 07 3e 37 11 65 6b 66 79 11 35 2a 62 7d 20 37 25
```

Thường khi đề cập đến khái niệm [XOR trong CTF](https://ctf101.org/cryptography/what-is-xor), chúng ta sẽ thường nghĩ việc sẽ có một `key` nhằm chuyển đổi giữa `flag` và `cipher_text`. Cụ thể hơn:
$flag \oplus key = ciphertext$ hoặc $ciphertext \oplus key = flag$
Hoặc nếu nói đơn giản hơn theo khái niệm toán học: $\alpha \oplus \delta = \beta$ hoặc $\beta \oplus \delta = \alpha$

Và nếu chúng ta áp dụng thêm một chút tính chất giao hoán và kết hợp, chúng ta sẽ có công thức chung rằng: $\alpha \oplus \delta \oplus \alpha = \delta$
Có nghĩa rằng, nếu chúng ta XOR một dãy ký tự đã được mã hóa $(\alpha \oplus \delta)$ (flag đã XOR) với dãy ký tự ASCII bình thường $(\alpha)$ (flag cần tìm), chúng ta sẽ ra được `key` dựa trên sự lặp lại của dãy `key` trên đoạn ký tự sử dụng công thức XOR được đề cập ở trên.

![image](https://hackmd.io/_uploads/r1WCdxtTex.jpg)

Bây giờ hãy thử áp dụng lại kiến thức này vào bài trên. Chúng mình đã nói rất rõ ở trong Rules rằng:

> *Flag formart là `vgucypher{<FLAG>}`*

Thế nên chúng ta có thể thử XOR trước vài ký tự đầu của dãy trên bằng `vgucypher{` để xem chúng ta có tìm được sự lặp lại của một `key` nào đó hay không.

![image](https://hackmd.io/_uploads/H1EKP7Kagg.jpg)

Chúng ta có thể thấy dãy `NNSXS` lặp lại 2 lần, và may mắn thay,

```powershell
$ echo NNSXS=== | base32 --decode
key
```

Nghĩa rằng `NNSXS` có nghĩa là `key` dùng để XOR! Từ đây bạn cứ lấy `key` đã tìm được và XOR lại với dãy ban đầu đề bài cho, thế là chúng ta đã có flag. (o゜▽゜)o☆

## Cryptography / Erénegiv cipher

> *Do you know how to solve Vigenére cipher? Wonderful! Now welcome to the reverse one.*
> 
> [*Link to the Erénegiv table*](https://docs.google.com/spreadsheets/d/187dlHOL8DyaucK4rUrF-otvvJrP3zdkSQ82B0WNT24M/edit?rm=minimal&gid=0)
> 
> *Notes:* 
> - *Rows stand for `enc`,* 
> - *Columns stand for `key`,*
> - *`flag = (enc, key)`*

Giải tay cho bài này cho bài này cũng là một ý tưởng hay, nhưng ở đây, chúng tôi không làm thế.（︶^︶）

:::warning
:warning: Đây là intended solution đến từ author!
:::

Nếu các bạn đã quen với [bảng Vigenére cơ bản](https://pages.mtu.edu/~shene/NSF-4/Tutorial/VIG/Vig-Base.html), bạn đều biết rằng khi tra 2 ký tự trong bảng, nếu có đổi chỗ với nhau thì cũng sẽ ra cùng một ký tự, ví dụ `(G,E)` hay `(E,G)` đều cho ra một kết quả là `K`.

Thế nhưng, trong bảng Erénegiv thì là một điều khác, áp dụng thử ví dụ trên thì chúng ta có `(G,E) = B` và `(E,G) = X`, thế nên khó có thể áp dụng được kiến thức cơ bản để giải challenge này. Thế bây giờ chúng ta phải làm sao?

```
enc: JLQHQWWSFEVWPYDAGHUHTNJNLGGSJFJPDGVKPFB
key: NEVERGONNAGIVEYOUUPNEVERGONNALETYOUDOWN
```

Chúng ta hãy thử sử dụng ký tự đầu của mỗi dãy, `J` cho `enc` và `N` cho `key`.

Khi xem bảng Erénegiv, chúng ta có thể nhận thấy một điều rằng: khi tra hai ký tự giống nhau trong bảng, nó cùng cho ra một kết quả là `Z`. Ví dụ `(A,A) = Z`, `(B,B) = Z`, ..., `(Z,Z) = Z`. Và tương tự, `(N,N) = Z`.

Và nếu chỉ dò theo cột `key N`, bạn có thể nhận ra một điều rằng bảng chữ cái vẫn theo thứ tự từ A đến Z, tuy chỉ có thay đổi vị trí bắt đầu và kết thúc của dãy ký tự đó. Ví dụ nếu `(N,N) = Z` thì `(O,N) = A`, `(P,N) = B`, ... `(L,N) = X` và `(M,N) = Y`.

Bây giờ, hãy thử tìm `(J,N)`. Chúng ta đã có `(N,N) = Z`, và khoảng cách từ chữ `J` đến chữ `N` chỉ cách có 4 ký tự, thế nên chúng ta có thể xác định được rằng kết quả cần tìm cách chữ `Z` 4 ký tự theo chiều từ Z về A, và đó là chữ `V`. 

Thế nếu giả sử nó là `(Q,N)` thì sao? `Q` cách `N` 3 ký tự, vậy thì kết quả cần tìm cách `Z` 3 ký tự theo chiều từ A đến Z, và đó là chữ `C`.

Thế nên, chúng ta có thể xác định công thức chung để có thể tìm được cách tìm kết quả đó chính là:

```py
alphabet[(ord(enc) - ord(key) - 1) % 26]
# với alphabet là list ký tự từ 'A' đến 'Z'
```

Áp dụng công thức này, với thêm 1 chút code, bạn đã có thể giải ra được flag của bài này mà không cần dùng đến tay. ╮(╯▽╰)╭

```py=
enc = "JLQHQWWSFEVWPYDAGHUHTNJNLGGSJFJPDGVKPFB"
key = "NEVERGONNAGIVEYOUUPNEVERGONNALETYOUDOWN"
alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
flag = ""

for i in range(0, len(enc)):
    flag += alphabet[(ord(enc[i]) - ord(key[i]) - 1) % 26]
print(flag)
```

## Cryptography / GCDDDDD

**Đề bài**
```python=
from Crypto.Util.number import getPrime, bytes_to_long
from os import urandom

flag = b'vgucypher{fake_flag}'

p = getPrime(512)
q = getPrime(512)
r = getPrime(512)
n1, n2 = p * q, q * r

e = 65537

a = bytes_to_long(urandom(4))

pt = bytes_to_long(flag)
ct = pow(pt, e, n1)

print(f'n1 = {n1}')
print(f'n2 + n1 * a = {n2 + n1 * a}')
print(f'ct = {ct}')

"""
n1 = 109262534857622904114145655888641830198971055019654242910681577370880241032193732728029814587220293760306805025349804408068742067927333471379745197048336261192610591585821192749823776180755306336791758594303243834151841479376490855965974016524562750104787411252940323636507244204000534248693719110822506779257
n2 + n1 * a = 139898130884927694898792040784250990320009766352520377845849395575569564447279203842303128879454475656086293107826564064992140823022094115441929147833947134393985283055563747792037346674958150164724327196754944747725332863144058838926782940447790243127827826862107638108819929820348612074008307351361087404486093410041
ct = 79865661542472620924254206041161247771342667302395676041862481166218710256554892560899522405291855783825048511757220753194057843581624595137399925270124102547814883819640754949946868120982254046086654939543017634799274252961727643721922859810618474531892821534602987969563654566072677406959685210215906088539
"""
```

**Solution**
```python=
from math import gcd
from Crypto.Util.number import long_to_bytes, inverse

n1 = ...
n_total = ...
ct = ...

e = 65537

q = gcd(n1, n_total)
p = n1 // q
phi = (p-1) * (q-1)
d = inverse(e, phi)

pt_calc = pow(ct, d, n1)
m_calc = long_to_bytes(pt_calc)
print(m_calc)
```

## Cryptography / Prime EEEEEEEEE

**Đề bài**
```python=
from Crypto.Util.number import bytes_to_long, getPrime

flag = b"vgucypher{FAKE_FLAG}"
BITS = 512
E_BITS = 15
pt = bytes_to_long(flag)

p = getPrime(BITS)
q = getPrime(BITS)
n = p*q

e_1 = getPrime(E_BITS)
e_2 = getPrime(E_BITS)

ct_1 = pow(pt, e_1, n)
ct_2 = pow(pt, e_2, n)
print("ct_1 = ", ct_1)
print("ct_2 = ", ct_2)

print("e_1 = ", e_1)
print("e_2 = ", e_2)

print("n = ", n)

# ct_1 = 87843886781401420979149250033724833211598126025291438276693366030244270244261926430777234316186311549322076289416989981828717406704369359544915937776521937832915162675566637563032826655916850816398396278228366954601577388505565519148291367376694956770980810028111132089419405554168032369876735740497040808572
# ct_2 = 96441432047296578064088563763558526005171893273374579913866905228598822979165533416236049994053576474907555160815352635817070276438184619576311350761604897784530916287572648867903239177506535797429912836298176689350979957062189737482756371310636755931734810260175684511443238765147102200256670379329821405176
# e_1 = 21799
# e_2 = 31751
# n = 97750699572850109573586372209875405859336462446629766979257661033631566713080872183328096526566619349492350235259181190779902816477268937384439891185562335858264109603287974554591886007079524578808980678665591486327402724087839723873697864495425442930980416339002648644752964506222721999514746948884514461359
```

**Solution**

```python=
from Crypto.Util.number import bytes_to_long, getPrime, long_to_bytes

def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    gcd, x1, y1 = extended_gcd(b % a, a)
    x = y1 - (b // a) * x1
    y = x1
    return gcd, x, y

def find_coefficients(e, d):
    gcd, a, b = extended_gcd(e, d)
    
    if gcd != 1:
        raise ValueError("e and d are not coprime, no solution exists.")
    
    return a, b

e = 21799  # Example value for e
d = 31751  # Example value for d
ct_1 =  87843886781401420979149250033724833211598126025291438276693366030244270244261926430777234316186311549322076289416989981828717406704369359544915937776521937832915162675566637563032826655916850816398396278228366954601577388505565519148291367376694956770980810028111132089419405554168032369876735740497040808572
ct_2 =  96441432047296578064088563763558526005171893273374579913866905228598822979165533416236049994053576474907555160815352635817070276438184619576311350761604897784530916287572648867903239177506535797429912836298176689350979957062189737482756371310636755931734810260175684511443238765147102200256670379329821405176
n =  97750699572850109573586372209875405859336462446629766979257661033631566713080872183328096526566619349492350235259181190779902816477268937384439891185562335858264109603287974554591886007079524578808980678665591486327402724087839723873697864495425442930980416339002648644752964506222721999514746948884514461359

a, b = find_coefficients(e, d)
print(f"The coefficients are: a = {a}, b = {b}")
ans = pow(ct_1,a,n)*pow(ct_2,b,n) % n
print(long_to_bytes(ans))
# b'vgucypher{3xtended_3uclidean_und_finding_co3fficients_4re_fun!}'
```

## Cryptography / Peak Prime

**Đề bài**

```python=
from Crypto.Util.number import getPrime, bytes_to_long

flag = b"vgucypher{fake_flag}"

SIZE= 512
e = 65537
p = getPrime(SIZE)
q = getPrime(SIZE)
n = p*q
c = bytes_to_long(flag)
cypher = pow(c,e,n)
peak = bin(q>>25)

print(f"cypher = {cypher}")
print(f"n = {n}")
print(f"peak = '{peak}'")

"""
cypher = 54542475235946298304895744059835459839757996962840139451571868152159283433377528113776276213114574919856504898944614587402355946688040276521749552690606974662114963126716853767825180301099416456021243380508951649086076356936471503771942124456742735341827554381116980515013638434918044455446725939801559301936
n = 85164716461916154411765778764912592182877871841077273307676207049879017163803398718235334236141728848744729459255847383987751918496290831509336883267410241339747535777342199093301316906118697682261375372859060381406570022862018652011767500144901804230770970657635346842786961155693408304413618396284786917097
peak = '0b1001100111011010011001100101100010001010011000000100101011010111100010010010110110000111010101100110110101100110100000101010111011110100011100001010101011011100011001111101101111101110101010111011000110010111101011111101100010000011000111111101100010100111100101011011101100101001101111101001100101011111110101001111010100011100101001000011011110100100011100011100110100111000010111011010110101111110000111111010001110111110000001010011000011111101110100100110011111000011110000001100001'
"""
```

**Solution**
```python=
from Crypto.Util.number import long_to_bytes

n = ...
cypher = ...
peak = '...'

found = False
for r in range(1<<25):
    q_candidate = (peak << 25) | r
    if n % q_candidate == 0:
        q = q_candidate
        p = n // q
        print("Found q! r =", r)
        print("q =", q)
        print("p =", p)
        found = True
        break

if not found:
    print("No divisor found in range; verify peak and n.")

if found:
    phi = (p-1)*(q-1)
    d = pow(e, -1, phi)
    m = pow(cypher, d, n)
    try:
        flag = long_to_bytes(m)
        print("Recovered plaintext (bytes):", flag)
        print("Recovered plaintext (utf-8):", flag.decode('utf-8', errors='replace'))
    except Exception as ex:
        print("Could not convert plaintext to bytes:", ex)
```

## Cryptography / Witch's Broadcast

**Đề bài**

```python=
from Crypto.Util.number import isPrime, bytes_to_long
import random
import os

FLAG = os.getenv("FLAG", "vgucypher{fake_flag}")

class Broadcast:
    def __init__(self):
        self.e = 3

    def getWYSIprime(self):
        # osu!gaming CTF 2024
        while True:
            digits = [random.choice("727") for _ in range(272)]
            prime = int("".join(digits))
            if isPrime(prime):
                return prime

    def encrypt(self, msg: bytes):
        p = self.getWYSIprime()
        q = self.getWYSIprime()
        while p == q:
            q = self.getWYSIprime()
        
        n = p * q
        cypher = pow(bytes_to_long(msg), self.e, n)
        return cypher, n

def handle_client():
    channel = Broadcast()
    print("Welcome to the witch's loudspeaker in the jungle!", flush=True)
    print("What do you want to hear from loudspeaker?")
    running = True
    while running:
        menu = (
            "1. Use magic on your talk!\n"
            "2. Get special witch spellcast!\n"
            "3. Leave\n"
            "Enter number: "
        )
        print(menu, end="", flush=True)
        
        try:
            usr_inp = input().strip()
            if usr_inp == "1":
                print("Your message: ", end="", flush=True)
                msg_inp = input().strip()
                if len(msg_inp) >= 1:
                    c, n = channel.encrypt(msg_inp.encode())
                    print(f"Your message after being fused with spell: {c}")
                    print(f"The spell name: {n}", flush=True)
                else:
                    print(f"Oops, something wrong with this?", flush = True)
            elif usr_inp == "2":
                c, n = channel.encrypt(FLAG.encode())
                print(f"The witch's secret message but been fused with special spell: {c}")
                print(f"The spell name: {n}", flush=True)
            else:
                print("Thank for joining, did you know osu! is a free-to-play rhythm game?", flush=True)
                running = False
        except EOFError:
            running = False

if __name__ == "__main__":
    handle_client()
    exit()
```

**Solution**
:::warning
:warning: Đây là intended solution đến từ author!
:::
Có hai cách để giải được bài này:
- [Hastad's broadcast attack](https://docs.xanhacks.xyz/crypto/rsa/08-hastad-broadcast-attack/)
- Guess the prime

## Miscellaneous / Lingo Linging

> *Among the pine woods is a massive wooden mansion. There was no way out of the inside labyrinth until someone got the answer right.*
> 
> *The flag will follow the format `VGUCYPHER{<text>}`, with `<text>` consisting of UPPERCASE LETTERS, excluding spaces ` ` and underscores `_`.* 
> 
> [*Link to the question*](https://docs.google.com/spreadsheets/d/1kUnkspCWeps66yLbJHNB8RwwCwiXUgaYFoWbCuAlGt4/edit?gid=1963777285#gid=1963777285)

Mình nghĩ nhiều team sẽ có nhiều hướng đi khác nhau cho bài này. Ở đây mình chỉ cho hint cụ thể cho từng màu, cũng như solution tổng cho toàn bộ challenge này.

|     | WHITE | BLACK |
| --- | --- | --- |
| MID | The answer is the hint. | The answer is the hint spelled in reverse. |
| LOW | The answer is a word with the same meaning as the hint. | The answer is the opposite of the hint. |

|     | RED | PURPLE | BLUE |
| --- | --- | --- | --- |
| TOP | The answer is the hint with syllables removed. | The answer is the hint with syllables replaced. | The answer is the hint with syllables added. |
| MID | The answer is the hint with letters removed. | The answer is the hint with letters replaced. | The answer is the hint with letters added. |
| LOW | The answer is conceptually removing from the hint. | The answer is the hint with part of it being replaced. | The answer is conceptually adding to the hint. |

==[Link to the solution](https://docs.google.com/spreadsheets/d/1Zj5pLI8lREhPN6iN4I0Y0gVbm5TTYBKST4kMx1Of6a4/edit?usp=sharing)==

Flag: `VGUCYPHER{COMEPICKUPYOURPHONE}`

>[!Note]
>Fun fact về challenge này:
>
>![image](https://hackmd.io/_uploads/Hyvuwgqpxg.jpg)
>
>*Challenge này được mình lên ý tưởng từ 3 tháng trước, khi mình tình cờ có chơi được một tựa game tên [Lingo](https://lingothegame.com). Việc mình thấy khá cuốn ở đây đó chính là ngoài việc được giải đố căng não, thì còn được luyện thêm vài từ vựng tiếng Anh mới nữa. Một công, đôi việc.* ♪

## Miscellaneous / Coding Skill

> *As an engineering student, there are 2 things you should know: Math, and Programming.*

Nghe Toán và Lập trình thì khá nặng vậy thôi, chứ nếu bạn hoàn toàn hiểu những gì đề bài cho thì việc reverse lại toàn bộ flag gốc là điều cực kỳ đơn giản. ;)

```python=
flag = "vgucypher{this_is_a_fake_flag_i_just_want_to_make_it_100_characters_for_you_to_test_with_the_numbers}" # fake flag
chars = list(flag)
numbers = # list of 101 random unique numbers from 0 to 100

# flag_values.txt
pairs = sorted(zip(numbers, chars))
for num, ch in pairs:
    print(f"flag[{num}] = {ch}")

# number_values.txt
for i in range(0, 100):
    print("numbers[", i, "] + numbers[", i + 1, "] = ", numbers[i] + numbers[i + 1], sep = "")
print("numbers[", 100, "] + numbers[", 0, "] = ", numbers[100] + numbers[0], sep = "")
```
`flag_values.txt`
```python
flag[0] = h
flag[1] = e
flag[2] = l
flag[3] = e
...
flag[99] = p
flag[100] = !
```

`number_values.txt`
```python 
numbers[0] + numbers[1] = 103
numbers[1] + numbers[2] = 97
numbers[2] + numbers[3] = 112
numbers[3] + numbers[4] = 94
...
numbers[99] + numbers[100] = 153
numbers[100] + numbers[0] = 98
```

Giải thích đơn giản nhất về code để generate ra 2 file `.txt`:
- `numbers` là một dãy 101 số random độc lập từ 0 đến 100 (hoặc đơn giản hơn là `0, 1, 2, ... 100` nhưng vị trí là random);
- `zip(numbers, chars)` có nghĩa rằng gắn từng giá trị của `numbers` với từng giá trị của `chars` ở cùng một ví trị (hoặc `flag[numbers[0]], flags[number[1]], flags[number[2]], ...`);
- `flag_values.txt` sẽ in ra thứ tự từ trên xuống của flag đã được đảo;
- `number_values.txt` sẽ in ra tổng vị trí của 2 ký tự liền kề của flag gốc.

Và bây giờ, thứ chúng ta đang cần là dựa vào tổng vị trí ở trên, suy ra vị trí gốc của từng ký tự trong flag gốc, và in ra flag đó.

Ở đây mình cá chắc có bạn sẽ giải tay ┌(。Д。)┐ nhưng mình nghĩ việc đó sẽ tốn rất nhiều thời gian so với việc chúng ta có thể suy ngược về một bài toán "đố vui tiểu học" như sau:

>Giả sử chúng ta có 3 số $a, b, c$. 
>Chúng ta được biết trước rằng: $a + b = \alpha, b + c = \beta, c + a = \gamma$
>Thế chúng ta có tìm được giá trị của $a, b, c$ hay không?

Cách giải của bài toán trên thì rất đơn giản, chúng ta chỉ cần:
$\implies (a + b) + (b + c) + (c + a) = \alpha + \beta + \gamma$
$\implies 2a + 2(b + c) = \alpha + \beta + \gamma$
$\implies 2a + 2\beta = \alpha + \beta + \gamma$
$\implies a = (\alpha - \beta + \gamma) \div 2$

Từ đó, thế $a$ vào các biểu thức đề bài cho, chúng ta sẽ suy ra được $b, c$. Đơn giản phải không?

Thế từ bài toán 3 số như này, làm sao để có thể xử lý được 101 số của challenge này? Chúng ta có thể suy nghĩ một số bài toán đơn giản, dựa trên bài toán "đố vui" trên như sau:

- Nếu chúng ta có 4 số $a, b, c, d$ và biết tổng của $a + b, b + c, c + d, d + a$; chúng ta sẽ được biểu thức $2(a + b) + 2(c + d)$, nghĩa là bài toán sẽ có vô số solution
- Nếu chúng ta có 5 số $a, b, c, d, e$ và biết tổng của $a + b, b + c, c + d, d + e, e + a$; chúng ta sẽ được biểu thức $2a + 2(b + c) + 2(d + e)$, nghĩa là chúng ta sẽ tìm được $a$ và các số còn lại.
- Tương tự như vậy với 6 số, 7 số, ...

Suy ra, có thể dễ dàng chứng minh được rằng: 

>[!Tip]
>Với $n$ số và $n$ tổng của bộ 2 số liên tiếp, nếu $n$ lẻ thì chỉ tồn tại duy nhất 1 bộ số là solution, còn nếu $n$ chẵn thì bài toán sẽ tồn tại vô số bộ số là solution.

Quay trở lại challenge, việc đầu tiên chúng ta cần làm là phải extract giá trị từ 2 file `flag_values.txt` và `number_values.txt`. Có nhiều cách để extract, nhưng ở đây mình chỉ dùng vỏn vẹn... 2 dòng :)

```python
flag = [line.split('=')[1].strip() for line in open('flag_values.txt') if '=' in line]
values = [int(line.split('=')[1]) for line in open('number_values.txt') if '=' in line]
```

Có được nó xong, chúng ta sẽ dùng giá trị tổng của các biểu thức trên để xử lý và tìm ra giá trị `number[0]` gốc.

```python
total = 0
for value in values:
    total += value
for i in range(0, 101):
    if i % 2 == 1:
        total -= 2 * values[i]
total /= 2
```

Ở lệnh `for` thứ 2, chúng ta có thể nhận thấy trong file `number_values.txt`, những biểu thức như `number[1] + number[2]`, `number[3] + number[4]`, ... `number[99] + number[100]` đều nằm ở các vị trí chẵn (nghĩa là vị trí lẻ khi xét theo index trong Python), nên chúng ta chỉ cần xét những vị trí đó và trừ ra như những biểu thức mà mình đã nêu trước đó. Sau khi làm xong thì chúng ta chỉ cần chia 2 ra, và `total` cuối cùng chính là số `number[0]` cần tìm.

Sau đó, chúng ta chỉ cần tạo một list `numbers` để lưu lại các giá trị của dãy số `numbers` ban đầu, bằng cách duyệt tổng từng biểu thức một và trừ đi cho số vừa thêm vào list. Sau đó chỉ cần in ra `flag[number[i]]` từng cái một, thế là chúng ta sẽ có flag.

```python
numbers = []
numbers.append(int(total))
for i in range(0, 100):
    numbers.append(values[i] - numbers[-1])

for pos in numbers:
    print(flag[pos], end = '')
```

Mình nghĩ vẫn sẽ có nhiều cách approach và nhiều cách để các bạn implement ở những programming language khác. Sự sáng tạo vẫn là của các bạn!

>[!Note]
>Fun fact về challenge này: 
>
>*Lúc đầu chúng mình* (mình và ==@Qân==) *nghĩ sẽ cho nó là một dạng bài của CP (competitve programming). Sau đó tụi mình nghĩ nó quá khó nên chuyển sang dạng bài fix bug của code để code có thể chạy. Sau đó lại thấy nó quá dễ nên mình đã nghĩ ra bài này, và* ==@Qân== *cũng đã đồng ý, thế là đồng thuận đôi bên.* <(")

## Miscellaneous / Draw

> *Look like my nephew just came back from school with his drawing. But it's not on the paper, it's on the computer!*

```
TGB EDCVB YTRFVBNHY TGBHU UYTGBNMHJ REWSDFVCX EWQAZXC EDCTGBF UYTGBNMJU REWSXCVFR RFVBN
```

Đây là challenge khá đơn giản, việc bạn cần làm chỉ là nhìn trên bàn phím của bạn và xem xem các chữ cái trên vẽ thành chữ gì. Ví dụ như `TGB` thành chữ `I`, `TGBHU` thành chữ `V`, ...

Flag: `VGUCYPHER{ILOVESCHOOL}`

## Miscellaneous / Guess my number

```python=
import random

flag = "vgucypher{y0u_just_defeated_the_b1n4ry_search!!!}"
SECRET_MESSAGES = "Contact Qan for bounty!"
n = random.randint(1, 10000)
attempts = 10

print("Welcome to 'Guess My Number'!")
print("I have selected a number between 1 and 10000.")
print("You have 10 attempts to guess it.")
for attempt in range(attempts):
    guess = int(input(f"Attempt {attempt + 1}, Enter your guess: "))
    if guess == n:
        print("Congratulations! You've guessed the number!")
        print(flag)
        break
    elif guess > n:
        print("lower")
    elif guess < n:
        print("higher")
    else:
        print(SECRET_MESSAGES)
        print("Invalid input. Please enter a number.")
```

Đơn giản mà đúng không, chỉ cần tìm số ở giữa trong khoảng cần tìm rồi sau đó xác định trên/dưới thôi. Đến lớp 3 còn tìm được đáp án mà alo. >:D

## Miscellaneous / A Gift from Valedictorian

[**Đề bài**](https://drive.google.com/file/d/1N5M8sjbprOhAnU5gO9zhjS4oxvuKU3Ra/view?usp=sharing)
Một bài về [pyTorch Stegnography](https://github.com/arnoweng/PyTorch-Deep-Image-Steganography), bạn có thể tìm hiểu kiến thức này để giải bài trên.

## OSINT / Between those trees

> *On my way home, I captured this shot. A path, between those trees.*
> 
> *Flag will be the coordinates of the location where the photo was taken, rounded to the 3rd decimal place.*
> *For example: 11.1098132, 106.6134097 -> vgucypher{11.110,106.613}*

Một bài xác định xem vị trí được chụp trong bức ảnh ở đâu. Thường với các dạng bài này, chúng ta sẽ xác định một số điều đặc biệt trong bức ảnh để có thể thu nhỏ khu vực tìm kiếm lại, ví dụ như đặc điểm các căn nhà, khu rừng, cối xay gió, ...

![image](https://hackmd.io/_uploads/HyfgWpTRlg.jpg)

Rất khó để xác định được đây là địa điểm nào nếu bức hình này là 2D, vì có rất nhiều khu rừng có thể nhìn như thế này. Thế nhưng vì đây là bức ảnh 360^o^, nên có thể nhìn được bao quát xem trong bức ảnh này có những đặc điểm gì khác. Trong bức ảnh này, ngoài khu rừng ra thì có một ngôi nhà khá đặc biệt, hãy thử tìm hiểu xem ngôi nhà này có gì.

![image](https://hackmd.io/_uploads/By9lLTaCll.jpg)
![image](https://hackmd.io/_uploads/r1jKI6pRxg.png)

Tìm thử địa điểm này ở trên Google Maps để xem chúng ta có đang ở đúng chỗ hay không.

![image](https://hackmd.io/_uploads/BkRdFpTAxg.png)
![image](https://hackmd.io/_uploads/BJpz9apAee.jpg)

Flag: `vgucypher{60.376,25.743}`

## OSINT / The girl

> *For what seemed like an eternity, she stood there, staring at that artwork. I have no idea what she was contemplating. Hold just a second, I believe I've laid eyes on her before. What did she say? "Slide, tap, hold"?*

![image](https://hackmd.io/_uploads/HyrIhpaAxl.png)

Một challenge khá đơn giản, chỉ cần đưa ảnh này lên Google Search và bạn đã có kết quả, thế nhưng mình muốn đưa ra cách giải khác để các bạn có thể hiểu cách mình muốn challenge được xây dựng lên để giải như thế nào.

:::warning
:warning: Đây là intended solution đến từ author!
:::

Chúng ta vẫn chưa hiểu được ý nghĩa của bức ảnh là gì. Nhưng ở trong description trong challenge có nhắc đến cụm từ *"Slide, tap, hold"*. Thử search cụm này trên Google và kết quả đầu tiên có thể tìm thấy chính là video này:

{%youtube _GNkurYAGPw %}

Chúng ta biết được rằng bài hát này nằm ở trong tựa game [maimai](https://en.wikipedia.org/wiki/Maimai_(video_game_series)), và thường các tựa game như thế này sẽ có một song list cụ thể. Nên ta có thể thử search `maimai song list`, và tìm thử xem có danh sách các bài hát này không. Thật may là có một [danh sách các bài hát](https://arcade-songs.zetaraku.dev/maimai) như vậy.

Một dữ kiện bạn có thể biết thêm ở những tựa game có thể loại như này là, mỗi bài hát đều có 4 (hoặc 5) độ khó, và tất cả là như nhau ở mỗi bài hát. Thế nên bạn có thể filter Difficulty bằng 1 trong 4 độ khó này, cộng thêm dữ kiện trong ảnh nữa (chi tiết của cô gái) là bạn có thể tìm được bài hát này.

{%youtube DIicSsVwnjU %}

Flag: `vgucypher{mystique as iris}`

## OSINT / The girl v2

> *He came to me unexpectedly on a beautiful sunny day.*

![image](https://hackmd.io/_uploads/r1SZA8CRlx.png)

Hint từ bức ảnh không quá nhiều, khi search thử thì cũng không ra được bài hát nào tương tự cả. Tuy nhiên, sau khi mình thả hint về đoạn text thì cũng có nhiều đội giải được hơn. 

Thật sự ra đoạn text ở trên đã được dịch từ tiếng Việt sang tiếng Anh. Và chắc chắn rồi, khi dịch sang tiếng Việt thì lời bài hát khá thô, buộc các team không chỉ dịch word-by-word mà là phải cố gắng sử dụng "vốn tiếng Việt" của mình.

Có khá nhiều team trả lời đáp án `Phượng buồn` nhưng rất tiếc không có chi tiết nào trong MV của bài hát này (hoặc thậm chí là không có MV của bài hát này) được đề cập trong bức ảnh. Tuy nhiên, nếu dịch khá chính xác ra lời của bài hát gốc thì bạn sẽ tìm thấy được chi tiết của bức ảnh trong MV đó.

| Tên bài hát | Lời bài hát |
| --- | --- |
| Phượng Buồn | *Anh đến với em vào một ngày trời đẹp nắng...* |
| **Cho em một lần yêu** | ***Người bỗng đến bên em vào một hôm nắng xanh ngời...*** |

{%youtube 2WpHVRaOsVM %}

Chi tiết trong ảnh xuất hiện từ đoạn [2:43](https://youtu.be/2WpHVRaOsVM?si=1hETkxT6PQx5RqPg&t=163).

Flag: `vgucypher{Cho Em Mot Lan Yeu}`

## OSINT / Gotta go fast!

> *Build fast, think fast, move fast. Everything, need to be FAST!*
> 
> *Flag format: `vgucypher{height, name of the floor}`*
> *e.x. `vgucypher{72.7m, The Skibidi}`*

Đề bài của chúng ta là một file `.ttr`, chúng ta hãy thử tìm hiểu xem file này là file gì.

![image](https://hackmd.io/_uploads/BkTa6wRCxg.png)

Vậy đây là file của game, thử cho file này vào game xem chúng ta có thông tin gì nổi bật.

![image](https://hackmd.io/_uploads/rkUXRvRCll.png)

Đây là file replay toàn bộ phần chơi với những thông tin (stats) cụ thể ở dưới. Tuy nhiên so với yêu cầu của đề bài là `vgucypher{height, name of the floor}` thì hoàn toàn không có thông tin nào phù hợp cả. Thế nên chúng ta có thể tra lại theo tên người chơi để xem thông tin cụ thể về người này.

![image](https://hackmd.io/_uploads/BkE6AwACee.png)

Nếu để ý thì ở phần Quick Play có format `height` khá giống với ví dụ cho ở đề bài (`882.1m`), thế nhưng chúng ta vẫn chưa biết được `name of the floor` là ở đâu. Chúng ta có thể tra thông tin về phần [Quick play](https://tetrio.wiki.gg/wiki/QUICK_PLAY#Floors) này ở trên mạng, bằng một số tra cứu thì chúng ta đã có đầy đủ thông tin về flag.

Flag: `vgucypher{882.1m, The Laboratory}`

## OSINT / Resistance

Kudos tới ==@moonsolo==, author của challenge này!

> *Have you ever asked yourself "what did foreigners say about Vietnam"?*
> 
> *Flag format: `vgucypher{publisher, publication date}`*
> *e.x. `vgucypher{muong14, 31/02/2025}`*

![image](https://hackmd.io/_uploads/ryfQZd0Ael.jpg)

:::warning
:warning: Đây là intended solution đến từ author!
:::

Dịch thử bài báo này, chúng ta biết được đây là tiếng Hà Lan. Nhìn vào các dữ kiện trong bài báo, chúng ta cũng biết được đây là một bài báo viết từ xưa. Dựa vào đây, chúng ta có thể thử tìm một số website cơ bản có lưu trữ các bài báo của đất nước đó, ví dụ ở đây là [website archive báo Hà Lan](https://www.delpher.nl).

Search theo keyword có trong bức ảnh, chúng ta có thể tìm ra chính xác bài báo này ở đâu. Đây là hình ảnh toàn bộ bài báo khi chưa được cắt:

![image](https://hackmd.io/_uploads/ryoOwdCAex.jpg)

Flag: `vgucypher{De Maasbode, 24/03/1949}`

## OSINT / Otter

> *o o o o o osu? No, nothing in my osu! page.*

Giống như [A letter from the present, to the past](#Forensics-/-A-letter-from-the-present,-to-the-past), một bài để thách thức độ khó ở mảng Forensics, thì bài này cũng tương tự, vận dụng max khả năng giải OSINT của các team.

Bài này không đưa một file gì cả, chỉ đưa một description có nhắc về `my osu! page`, thế nên chúng ta có thể dựa vào đây và tìm thử để có kết quả về [page osu! của author](https://osu.ppy.sh/users/12881880).

![image](https://hackmd.io/_uploads/ByEt3dRAee.png)

Tìm thử trong trang web này có thể nhận thấy trong mục `Pending Beatmaps` có một đường dẫn đến tới [một map](https://osu.ppy.sh/beatmapsets/2411054#osu/5235451) có tựa đề gần giống với tựa đề của challenge.

![image](https://hackmd.io/_uploads/S1ArTOAReg.png)

Có thể để ý ở phần `Mapper Tags` có phần khá giống với lại format của flag. Nhưng rất tiếc, đó là fake flag, nên chúng ta phải tiếp tục tìm xem flag đúng ở đâu.

Thoạt nhìn sơ qua thì không có gì đáng chú ý ở trên trang web này. Thế nhưng, nếu inspect element vào đúng vị trí, có thể thấy một file đang giấu ở dưới đây. Mở thử file ra và nó sẽ là bức ảnh mà ta cần phải xác định đúng vị trí bức ảnh đó được chụp.

![image](https://hackmd.io/_uploads/HyVgCd0Rex.jpg)
![image](https://hackmd.io/_uploads/ByBw0uAAee.jpg)

Đến đây, dựa vào những gì mình đã nói ở bài [Between those trees](#OSINT-/-Between-those-trees), chúng ta sẽ xác định rõ các đặc điểm để thu nhỏ lại chỗ này là ở đâu. 

Dựa vào bức ảnh, chúng ta đang đứng ở 1 ngã ba hoặc ngã tư (vì có biển tên đường), xe hơi đang dừng ở đèn đỏ, và kế bên chiếc xe hơi chính là một cột xanh nào đó mà chúng ta vẫn chưa biết. 

Dựa vào hình dáng của chiếc biển tên đường, chúng ta có thể thử search xem đây là biển ở đâu. Và đây là một trong những biển tên khá phổ biến ở khu vực [Frankfurt, Đức](https://www.dreamstime.com/munchener-street-weserstrasse-street-sign-post-downtown-streets-frankfurt-main-germany-street-name-sign-post-image167986289). Nhưng khó để có thể xác định xem tên đường ở trên biển này là tên gì.

Ngoài ra, cột xanh được mình nhắc tới ở trên chính là [trạm dừng xe tram/buýt](https://commons.wikimedia.org/wiki/File:Lokalbahnhof-Hedderichstraße_bus_stop_in_Frankfurt.jpg) ở Frankfurt. Thế nên chúng ta có thể thu gọn lại thứ cần tìm là các địa điểm "ở gần khu vực có đèn giao thông và có trạm dừng xe tram/buýt (và có các tòa nhà, ...)"

Ở đây, sẽ có một số team đã có thể tìm ra được địa điểm cụ thể, thế nhưng để cho việc giải thích có chính đáng hơn, mình sẽ lấy ra 1 hint trong 3 hint đã đưa cho tất cả các đội:

> *I only go to my university with only 1 bus, or maybe 1 bus and 1 tram. And that place in the (something) is on that route. (of course during my exchange semester)*

Với các bạn học ở CSE đều biết rằng chúng ta sẽ có 1 kỳ trao đổi ở [Frankfurt University of Applied Sciences](https://maps.app.goo.gl/WUqzgvXSzGv3TGFY9), thế nên từ địa điểm này, các bạn có thể kiểm tra các tuyến đường có thể đến vị trí này và phù hợp với điều kiện trên.

![image](https://hackmd.io/_uploads/HyAertCRll.png)
![image](https://hackmd.io/_uploads/rkZ4BKACgl.png)

Dựa vào các thông tin này, có thể kiểm tra hết route của bus/tram để có thể tìm vị trí chính xác trong bức ảnh.

![image](https://hackmd.io/_uploads/r1LwUFCCgl.jpg)
![image](https://hackmd.io/_uploads/S1vRSFRAle.jpg)

Vậy là chúng ta đã tìm ra được vị trí chính xác bức ảnh được chụp. ヾ(＠⌒ー⌒＠)ノ

Flag: `vgucypher{50.106,8.688}`

## Other challenges

Ngoài ra còn một số challenge khác, các bạn có thể tìm thấy ở đây:
- [Web / Hackerpage](https://docs.google.com/document/d/1C7675Ou0F1ZlNJ_PJVNDSZXcvKeUzT8TLzRbMPPbWxU/edit?usp=sharing)
- [Web / Hackershop](https://docs.google.com/document/d/1bP4GLKyE4vtBuMPF5Mq9N7iKTLOxnx-cTD4dQSDOJFE/edit?usp=sharing)
- [Web / VGU student's worst nightmare](https://docs.google.com/document/d/1N8LPsUYXQMHAfV3YVAqQhTjvav6dW-wkOe7hBTiAhao/edit?usp=sharing)
- [Web / SonorousRed](https://docs.google.com/document/d/1HTQE9lgO6pN0tsao_4XS8q1mF8D4cZjlr6H01xMZD0A/edit?usp=sharing)
- [Misc / Who will guard the guards themselves?](https://docs.google.com/document/d/17zfTOnuT8NZDDs3J5WAFKDQ3KZsKlCpM1JVV-XL8kLA/edit?usp=sharing)
- [Pwn / Baby overflow](https://docs.google.com/document/d/1SGlLMzELkq91zhxY00g9QQmKZNRo_L_kV2MLfWRyQR0/edit?usp=sharing)
- [Pwn / Hidden shell](https://docs.google.com/document/d/1KkKFYxH6jn1kZipKlwxmLicFAD_ddPTBwbZKnuERD7A/edit?usp=sharing)
- [Pwn / Hidden shell 2](https://docs.google.com/document/d/1dJL279cQdIwUrp3QXWH9Fm4f-34TcQJDA27DDUQ2QCM/edit?usp=sharing)
- [Rev / Haruki Harshie](https://docs.google.com/document/d/1ZoCbiGRsQkJTFzEjWbjgWmvjQZPshaVj4kGyW_3LN10/edit?usp=sharing)

## Lời kết

Mong các bạn đã có một mùa rất vui với VGU Beginner CTF. Mặc dù việc lên ý tưởng cho tổng cộng trên dưới 20 bài (và viết write-up cho đống này) cũng khá mệt mỏi, nhưng nhìn những đứa con tinh thần của mình được mọi người đón nhận, việc này rất vui. ( •̀ ω •́ )y

Rất mong có thể gặp lại các bạn vào mùa VGU Beginner CTF tiếp theo. 
Xin chào, và hẹn gặp lại! (。・∀・)ノ

Ký tên,
==Khavid==