37
loading...
This website collects cookies to deliver better user experience
I heard it's raining flags somewhere, but forgot where... Thankfully there's this weather database I can use.
$ ./weather
Welcome to our global weather database!
What city are you interested in?
London
Weather for today:
Precipitation: 1337mm of rain
Wind: 5km/h W
Temperature: 10°C
Flag: none
configure_custon_print_functions
) references all of those "unreachable" functions, passing them as a reference to register_prinf_function
. configure_custon_print_functions
is called on init (before main) and by putting a breakpoint at the functions it registers we can see some of them are executed in a normal execution flow.12.13.1 Registering New Conversions
The function to register a new output conversion is register_printf_function
, declared in printf.h.
Function: int register_printf_function (int spec, printf_function handler-function, printf_arginfo_function arginfo-function)
%Y
we would call register_printf_function
int y_custom_handler(FILE *stream, const struct printf_info *info, const void *const *args) {
// here we would implement our custom formatting
}
void main(){
printf_arginfo_function args;
register_printf_function('Y', y_custom_handler, &args);
}
print_info
stores metadata about the format string, for example %52Y
would set print_info->width = 52
and %52.3Y
will set both width and print_info->perc = 3
.main
we can see that the last printf which is responsible for printing Flag: none
is printed using a special printf function which is assigned the letter %F
. To my surprise , the F handler itself calls fprintf
another time, this time with a new format string %52C%s
.fprintf
recursively was CTip: Looking in HexRays decompiled c, rather than assembly helped us understanding which handler is what operation much faster.
M
assignmentS
additionO
subtractionX
multiplyV
divideN
moduluL
shift leftR
shift rightE
xorI
andU
orC
conditional recursive jumpprintf_info
metadata and deduces from it the source and destination memory regions. There are two possible memory regions: one is just a linear memory we called "stack" and the second points to the start of the format string.'%52C%s',0
'%3.1hM%3.0lE%+1.3lM%1.4llS%3.1lM%3.2lO%-7.3C',0
'%0.4096hhM%0.255llI%1.0lM%1.8llL%0.1lU%1.0lM%1.16llL%0.1lU%1.200llM%2.1788llM%7C%-0.0C',0
.data:0000000000005080 ; char a52cS[]
.data:0000000000005080 a52cS db '%52C%s',0
.data:0000000000005087 a31hm30le13lm14 db '%3.1hM%3.0lE%+1.3lM%1.4llS%3.1lM%3.2lO%-7.3C',0
.data:00000000000050B4 a04096hhm0255ll db '%0.4096hhM%0.255llI%1.0lM%1.8llL%0.1lU%1.0lM%1.16llL%0.1lU%1.200l'
.data:00000000000050B4 db 'lM%2.1788llM%7C%-6144.1701736302llM%0.200hhM%0.255llI%0.37llO%020'
.data:00000000000050B4 db '0.0C',0
%C
handler. This instruction has four modes: fprintf
is called again (which makes it recursive) with a new format string. This format string is calculated by taking printf_info->width
as an offset from the original format string. %52C%s
is actually the instructionjump 52
# do 52 stuff...
printf(%s) # the regular %s
========== 0 ==========
C.always 52
printf(%s)
# memsmoc
========== 7 ==========
(M) stack[(int) 3] = a52[stack[(int) 1]]
(E) stack[(int) 3] ^= stack[(int) 0]
(M) a52[stack[(int) 1]] = stack[(int) 3] # decrypt fromat_string[200 + index]
(S) stack[(int) 1] += 4 # increase
(M) stack[(int) 3] = stack[(int) 1]
(O) stack[(int) 3] -= stack[(int) 2] # counter -=
(C) C.lt 7 stack[(int) 3] < 0 # while counter < 0
========== 52 ==========
stack[(int) 0] = a52[4096] # user input
stack[(int) 0] &= 255 # first letter of user input
stack[(int) 1] = stack[(int) 0] # copy the first letter
stack[(int) 1] <<= 8
stack[(int) 0] |= stack[(int) 1]
stack[(int) 1] = stack[(int) 0]
stack[(int) 1] <<= 16
stack[(int) 0] |= stack[(int) 1] # c
stack[(int) 1] = 200 # func_7(c, 200, 1788)
stack[(int) 2] = 1788
C.always 7
at52[6144] = 1701736302 # "none"
stack[(int) 0] = a52[200] # get first char from format_string[200]
stack[(int) 0] &= 255
stack[(int) 0] -= 37 # fromat_string[200] == '%'
C.eq 200 stack[(int) 0] == 0 # check if decrypted stage two successfully
========== 200 ==========
stack[(int) 4] = 5000
stack[(int) 0] = 13200
C.always 337 # this loops 400~ times
stack[(int) 0] = 0
C.always 500 # loop over input and mangle it
C.always 1262 # validate mangled input using xor
C.eq 653 stack[(int) 0] == 0 # check if output of 1262 is zero
# if so, 653 decrypts winning print command (using original input as key)
int func_200() {
func_337(5000, 13200);
mangle_input(); // func_500;
if (validate_input() == 0) { // func_1262
// print_flag is a calculation over input so one
// cannot just execute print_flag without the correct input
print_flag(); // func_653
}
}
stack[0]
.# 500:
def mangle_loop(input_s):
output = [0 for x in range(len(input_s))]
for i in range(len(input_s)):
c = input_s[i]
c = ord(c) ^ (stage_two_main[i * 2] & 0xff)
c += do_470(i + 1) + 1
c = c & 0xff
output[i] = c
return output
def do_470(var_0):
var_1 = var_0 - 1
var_1 -= 1
if var_1 == 0: # 397
return 0
if var_1 < 0:
return var_0 - 2
# var_1 > 0 :428
var_1 = var_0 % 2
if var_1 == 0: # :405
var_0 = var_0 // 2
if var_1 > 0:
var_0 *= 3
var_0 += 1
var_0 = do_470(var_0)
return var_0 + 1
stack[0]
stack[0]
where 0 means the validation was successful========== 1262 ==========
stack[(int) 0] = 0 # result = 0
stack[(int) 1] = 0
stack[(int) 1] += 4500 # mangled user input offset
stack[(int) 1] = a52[stack[(int) 1]] # var1 = mangled[0]
stack[(int) 2] = 0
stack[(int) 2] += 1374542625
stack[(int) 2] += 1686915720
stack[(int) 2] += 1129686860 # var2 = (int) (1374542625 + 1686915720 + 1129686860)
stack[(int) 1] ^= stack[(int) 2]
stack[(int) 0] |= stack[(int) 1] # result |= var1 ^ var2
var2
. We know the first letter is 'T'
so breaking the first 4 letters should be possible using simple brute force on the remaining 3 letters. We did just that and got the first 4 letters: "TheN" 🥳 .mangle_function
and then xor the result with the appropriate value (from 1262). A valid result is one where mangle(input[i:i+4])) ^ magic == 0
import itertools
def brute():
start = '0'
end = 'z'
magic = ctypes.c_int(1374542625 + 1686915720 + 1129686860).value
for i in itertools.product(range(ord(start), ord(end) + 1), repeat=4):
s = ''.join([chr(x) for x in i])
out_mem = mangle_input(s)
result = arr_to_int(out_mem)
if result ^ magic == 0:
print('found input!', s)
Flag: CTF{curs3d_r3curs1ve_pr1ntf}
37