- I'm feeling pretty JUMPY today. Can you give me a nice flag to JUMP on?
- There's a lot of code, but most of it seems pretty similar. Try to look at the bigger picture.
Reverse engineering challenge obfuscated with the setjmp
and longjmp
functions
The structure of the binary is essentially a long list of blocks initialized with setjmp
,
followed by a longjmp
to the one called start
.
Pretty much all of the variables in this program are global, and have their names exported with the binary.
Most of the blocks fall into one of two categories:
- "State" blocks, which set the global
nxstate
, callpermute
, andlongjmp
to a block calledcontrol
- "Transform" blocks, which apply some transformation to the global buffer
flags
, thenlongjmp
to one of the state blocks (determined bynxstate
)
There are also a few other special blocks:
start
prints out the initial prompt, then copiesargv[1]
intoflag
and its length intoflag_len
, ensuring that it exists and isn't an empty string, then finally jumps to the first state blockaccept
prints a message indicating that the input was accepted, then exitsreject
prints a message indicating that the input was rejected, then exitscontrol
incrementsflag
andflag_len
by 1 (effectively cutting off the first character), thenlongjmp
s to one of the transform blocks if the buffer isn't empty, or to eitheraccept
orreject
if it is
Basically, it's a finite state machine that also transforms the flag as it examines it. The control flow goes something like this:
start
reads in the input and jumps to the first state- Each state checks the first character of the current buffer, then sets
nxstate
based on the current state and character, based on a global array calledjumps
- Each state also calls
permute
on the global arraytransform_inds
, with a parameter from theperms
array based on the current state- Each
perm
is a permutation of the numbers 0-7;permute
applies the same permutation to its first argument
- Each
- Then, each state
longjmp
s tocontrol
control
first cuts the first character off of the buffer- Then, if the buffer isn't empty:
control
jumps to a transform block, which is chosen bytransforms[transform_inds[0]]
- There are 8 transform blocks, each of which either shuffles the bytes around, adds some value to all of the bytes mod 256, XORs every byte with some value, or some combination of these
- Then, each transform block jumps to the state given by
states[nxstate]
, cycling back to step 2 with the next character
- If the buffer is empty,
control
checksaccepts[nxstate]
to see if the next state is an "accept" state; if it is, it jumps toaccept
, if it isn't, it jumps toreject
There are way too many states for us to reason about this manually. Luckily, most of the relevant information is stored in a few giant global arrays, so we can just dump those and analyze them in Python. I used Ghidra's copy bytes option, the result is in the (solution folder)[sol]. Now, after looking at the data a little bit there are a few things that we can see:
- Not all of the states are reachable from state 0, so we can completely ignore the ones that aren't
- There's only one reachable accept state, which means we have to get to that state for our string to be accepted
- There's only one reachable state that can jump to that state, and only one reachable state that can jump to that one, and so on all the way back to state 0; this means that there's exactly one possible route through the program that ends in an accept state
Now we can recover the flag by doing the following:
- Figure out the route we need to take for the string to be accepted
- Figure out what transformation is applied at each step (since this is independent of the input)
- This involves running through the route forwards to see how the transformations are permuted at each step
- Run through the route backwards to construct the flag; this involves 2 parts:
- Undo the transformation applied at this step
- Prepend the byte that would send us to the next state in the route
The solution folder includes a commented Python script with more details.
- Flag:
jctf{h1PpiTy_H0PP1tY_a95c4603}
- Developed by ContronThePanda, part of RUSEC.