Umd 2023 Rev - Well Connected
For the past few months, I’ve been learning to use the Binary Ninja api to level up my reversing skills. Here’s a demo of how powerful scripting is.
For this writeup, I will demostrate as much as I can using the binaryninja api. But also so I can have a reference if I ever forget
Initial Findings⌗
Challenge files can be found here
I pulled up to a party but they wouldn’t let me in until I named 40 functions! 😡
Opening this up in binaryninja, we see a very large number of functions.
>>> len(list(bv.functions))
28970
But yet, main does not seem to do anything 🤔
$ ./final
⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿[how am i gonna function]⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⡏⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿
⣿⣿⣿⣿⣿⣿⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠉⠁⠀⣿
⣿⣿⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠙⠿⠿⠿⠻⠿⠿⠟⠿⠛⠉⠀⠀⠀⠀⠀⣸⣿
⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣴⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢰⣹⡆⠀⠀⠀⠀⠀⠀⣭⣷⠀⠀⠀⠸⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠈⠉⠀⠀⠤⠄⠀⠀⠀⠉⠁⠀⠀⠀⠀⢿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⢾⣿⣷⠀⠀⠀⠀⡠⠤⢄⠀⠀⠀⠠⣿⣿⣷⠀⢸⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⡀⠉⠀⠀⠀⠀⠀⢄⠀⢀⠀⠀⠀⠀⠉⠉⠁⠀⠀⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿
and main
seems to only call puts
>>> bv.get_functions_by_name('main')[0].callees
[<func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>, <func: x86_64@0x1050>]
>>> bv.get_functions_by_name('main')[0].callees[0].name
'puts'
Ok. let’s take a look at one of these functions
>>> print(bv.functions[20].hlil)
sub_7cdd1f(0x751, 0x13)
sub_89c82e(0x516, 0x6e)
sub_3bb211(0x3eb, 0x7f)
sub_292aa2(0x7a0, 0x20)
sub_7051d8(0x609, 0x44)
sub_59214d(0x36e, 0x55)
sub_41e778(0x70b, 0x1e)
sub_48006e(0x851, 0x7b)
sub_58d1cf(0x2b2, 0x1c)
sub_6fcd0a(0x752, 0x54)
sub_2f4c47(0x417, 0x5b)
sub_bc41b(0x18a, 0x1f)
sub_63281d(0x6a, 0x6e)
sub_45478c(0x57d, 0x30)
sub_73dbf3(0x59a, 0x41)
return zx.q(arg1 << arg2)
It seems to call a bunch of other functions, and return arg1 << arg2
.
In fact, it seems like this pattern follows for all of these functions. Call a ton of functions and return.
>>> def check(func):
... assert len(list(func.hlil)) == 1
... block = list(func.hlil)[0]
... for hlil in block:
... print(hlil.operation)
...
... check(bv.functions[20])
...
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_CALL
HighLevelILOperation.HLIL_RET
Ok, let’s do a sanity check
>>> def check(func):
... assert len(list(func.hlil)) == 1
... block = list(func.hlil)[0]
... for hlil in block[:-1]:
... if hlil.operation != HighLevelILOperation.HLIL_CALL:
... print(func)
... return False
... if block[-1].operation != HighLevelILOperation.HLIL_RET:
... print(func)
... return block[-1].operation == HighLevelILOperation.HLIL_RET
...
... funcs = list(bv.functions)[11:-1]
... print(all(list(map(check,funcs))))
...
int64_t sub_5e347b()
False
Ok, all but one function seems to follow this behaviour (We will see later this function is important).
What does Well Connected mean?⌗
If you happen to have a background in Computer Science or Competitive Programming, “Well Connected” could refer to a Well Connected Graph (aka Strongly Connected Graph). So then I had the idea
Before constructing the graph, I looked at the callees of every function to see if any of them would be the “start” node.
>>> funcs = list(bv.functions)[11:-1]
... the_func = None
... for func in funcs:
... if len(func.callers) == 0:
... print("Start:", func)
...
Start: int64_t sub_5e347b()
Ok, this function stands out. It is the only function which is not being called, with no arguements. Let’s look at the decompilation.
>>> func = bv.get_function_at(0x5e347b)
... print(func.hlil)
...
int32_t rdi
int32_t var_c = rdi
int32_t rsi
int32_t var_10 = rsi
sub_469bc9(0x4e, 7)
sub_469bc9(0x46, 7)
sub_390418(0x57, 0x13)
sub_469bc9(0x3c, 7)
sub_751551(0x54, 1)
sub_792291(0x40, 6)
sub_154813(0x7b, 0)
sub_469bc9(0x61, 7)
sub_469bc9(0x2c, 7)
sub_390418(0x85, 0x13)
sub_390418(0x46, 0x13)
sub_154813(0x35, 0)
sub_154813(0x5f, 0)
sub_469bc9(0x66, 7)
sub_d31fe(0x790000, 0x10)
sub_154813(0x5f, 0)
sub_390418(0x81, 0x13)
sub_154813(0x75, 0)
sub_751551(0x6d, 1)
sub_154813(0x62, 0)
sub_792291(0x35, 6)
sub_792291(0x74, 6)
sub_d31fe(0x5f000000, 0x18)
sub_390418(0x48, 0x13)
sub_792291(0x16, 0x26)
sub_469bc9(0x58, 7)
sub_469bc9(0x5c, 7)
sub_d31fe(0x34000, 0xc)
sub_469bc9(0x2a, 7)
sub_469bc9(0x2a, 7)
sub_751551(0x5f, 1)
sub_792291(0x6a, 7)
sub_154813(0x33, 0)
sub_d31fe(0x5f10000, 0x14)
sub_792291(0x2b, 0x46)
sub_154813(0x34, 0)
sub_d31fe(0x79000000, 0x18)
sub_751551(0x62, 1)
sub_d31fe(0x3304481, 0x14)
sub_469bc9(0x76, 7)
return 0
Remember those return values in those functions? Let’s see what sub_469bc9(0x4e, 7)
returns
>>> func = bv.get_function_at(0x5e347b)
... func.callees[0].hlil[-1]
...
...
<HLIL_RET: return zx.q(arg2 + arg1)>
>>> chr(0x4e + 7)
'U'
This is likely the flag! Trying out the next few letters gives UMDCTF{
the flag prefix.
What we can do now is loop through all the instructions and calculate the return values
>>> flag_func = bv.get_function_at(0x5e347b)
...
... ans = []
... for instr in list(list(flag_func.hlil)[0]):
... if instr.operation != highlevelil.HighLevelILOperation.HLIL_CALL:
... continue
... args = instr.params
... a, b = int(args[0].value), int(args[1].value)
... func = int(instr.dest.value)
... op = str(bv.get_function_at(func).hlil[-1])
... if '+' in op:
... ans.append(a+b)
... elif '-' in op:
... ans.append(a-b)
... elif '*' in op:
... ans.append(a*b)
... elif '^' in op:
... ans.append(a^b)
... elif '<<' in op:
... ans.append(a<<b)
... elif '>>' in op:
... ans.append(a>>b)
... else:
... print(op)
...
... print(''.join(list(map(chr, ans))))
...
UMDCTF{h3r35_my_numb3r_50_c411_m3_m4yb3}
Flag: UMDCTF{h3r35_my_numb3r_50_c411_m3_m4yb3}