Teaching CPR during the contest, so I decided to spend the next few day to sovle these challenges without looking writeups.
Reverse::Elementary
TL;DR
Solve this with angr is quick and easy
import angr |
flag:p4{I_really_hope_you_automated_this_somehow_otherwise_it_might_be_a_bit_frustrating_to_do_this_manually}
details
The binary has lots of trivial functions, some of them return the arg1
directly, the others return arg1^1
, this prevents us from parsing the decompile result directly. The angr help us to solve this challenge without knowing anything about those functions.
The other option to solve this is to laverage idapython to parse the decompile result directly, I used to do so.
Reverse::Old school
pseudo code
|
details
The binary ask for 18 hexadecimal digits to draw a graph. Every two of them will be consider as a number, eg: 1a
= $26_{10}$. Then, for each number, every two bits of them indicate a way to move on a 2D-array on memory. So, at the end of the funciton sub_10148
, the data in 2D-array implys that how much times we walk on to a corresponding position.
What sub_10215
does is replace the number in array to the corresponding character in string p4{krule_ctf}
. And S
and E
in the flag.txt means the start point and the end point. In summary, all we need to do is to find a way to move from S
to E
and also satify all the numbers marked on each position.
Backtracking is a good choice, but I drew it on paper.
2 3211 |
Hexadecimal solution is 50 6b 07 37 07 69 36 67 79
(there should be no space in input)
Reverse::Pudliszki
details
This challange gives us a .jar, after decompile, there is a package call kotlin inside it, it’s another language designed for JVM, I’ve never heard of it …, the result of decompiler looks like a mess …. , and there are some weird class like p.class
,{.class
, }.class
. It’s obviously would be use to verify the flag later.
Take a look on main first :
public final class FlagCheckerKt |
SizeResult localSizeResult = SizeResultFactory.Companion.check($receiver.length, A.class);
, this line checks the number of args equals to the length of the name of class A or not. Then, call validateFlag
to verify the arg.
The decompiled result of validFlag
is a mess, need patience to read it, and try to write a couple lines of kotlin. Basically, what validFlag
does is :
Turn the
flag
into a list of tuple, eg:String "abab" -> List [(a,0),(b,1),(a,2),(b,3)]
Group the list from step 1 according to the
first
in each tuple, eg:List [(a,0),(b,1),(a,2),(b,3)] -> LinkedHashMap {a=[0,2],b=[1,3]}
Call
checksum
to do some caculation on the LinkedHashMap from step 2
Follow into the function checksum
:
Call
compress
to compress the list in LinkedHashMap.LinkedHashMap {a=[0,2],b=[1,3]} -> Collection {(a.class object instance, 0*1+2*32),(b,calss object instance, 1*1+3*32)}
Do some caculate according to the
first
of each tuple and append it to a temp Collection.Apply
sum
on the Collection and return the result, which should be0
The caculate in step 2 is :
localObject3 = Integer.valueOf(($i$a$-map-FlagCheckerKt$checksum$1 instanceof }) ? ((Number)entry.getSecond()).intValue() - 27 : |
Get the flag by factorizing those number.
flag:p4{k0tl1n_1s_p0li5h_ke7chup}
Reverse::Watchmem
The binary fork itself by createprocess, then it would try to act like a debugger. When the children return a singal single step or signal illegal instrution, it will decode the real instructions and put it back, and recover it after execution.
It’s time comsuming to fully understand the mechanism, I try to analyze the pattern by hook WriteProcessMemory,which can be done by dll injection or debugger script. This is the snippet of result :
0x401e69 2 #patch 0x401e69 0x401e69+1 |
It turns out that the second records always be the decoded instruction. So, all we need to do is patch it by IDApython
def go_patch(patch): |
Now we can understand the control flow of the encoded binary.
Anyway, reverse the operation to get the flag
def de_shuffle( decode ): |
Web::My admin panel
TL;DR
Set the value of otadmin
in cookie to {"hash": 389}
flag:p4{wtf_php_comparisons_how_do_they_work}
source code
|
details
The result of md5 (128-bit) would be represented as a sequence of 32 hexadecimal digits. So the meaning of hint is that 0 and 64 are the result of ord(digits)&0xc0
and ord(letters)&0xc0
respectively.
Then, the $session_data['hash'] != strtoupper(MD5($cfg_pass))
is the key point, this is loose comparison. All we need to do is brute force the first three digits to pass this check.
import requests |
flagp4{wtf_php_comparisons_how_do_they_work}
Crypto::Count me in!
TL;DR
There is a bug inside the worker_function
due to share a global variable among multiple workers, which causes that different workers use the same keystream to encrypt the plaintext.
details
The bug is inside the worker_function
. For example, in this scenario, the woker_0
is processing key_stream = aes.encrypt(pad(str(counter)))
, the counter is still 0
, because the counter += 1
is not executed yet, what if the worker_1
is processing key_stream = aes.encrypt(pad(str(counter)))
at the same time ? They are using the same keystream !
... |
Find possible keys to decrypt flag:
from libnum import * |
Flag:p4{at_the_end_of_the_day_you_can_only_count_on_yourself}