Writeup by FloDarPie for babyfuscation

reverse

March 27, 2026

First step - gather intelligence

As the name intend it, there is a bit of obfuscation. Let’s rename some functions and variables.

00401395    int32_t main(int32_t argc, char** argv, char** envp)

004013d6        for (int32_t i = 0; i s<= 0x4f; i += 1)
004013cb            *(sx.q(i) + &buffer1) ^= 0x42
004013cb        
0040140d        for (int32_t i_1 = 0; i_1 s<= 0x4f; i_1 += 1)
00401402            (*"P|aavpg23J|f4ev3u|f}w3g{v3u")[sx.q(i_1)] ^= 0x13
00401402        
00401444        for (int32_t i_2 = 0; i_2 s<= 0x4f; i_2 += 1)
00401439            (*"`EXYP")[sx.q(i_2)] ^= 0x37
00401439        
0040144b        première_fonction()
00401455        deuxieme_fonction()
00401465        return retour_reponse()

We can pursue this work, identifie each operation, follow the path… but is quite fastidious. We can at least have the squeleton of the program :

main()

 - première_fonction()
        puts()
        fgets()
 - deuxieme_fonction()
 - retour_reponse()
    if(__):
        puts()
    else:
        puts()

If we run an example :

$ ./babyfuscation.txt 
Enter the flag: 
toto
Wrong flag. Try again!

We understand tha the binary wrapped the input gived through fgets and print and answer with puts.

We can solve the lines 00401402 and 00401439 to understand what’s under the hood :

input = "P|aavpg23J|f4ev3u|f}w3g{v3u"
unxored = ''.join(chr(ord(lettre) ^0x13) for lettre in input)
print(unxored)
input = "`EXYP"
unxored = ''.join(chr(ord(lettre) ^0x37) for lettre in input)
print(unxored)

There is a Correct! and a Wrong strings.

Second step - find the correct flag

We know, when we execute our binary :

  • if we give a false input, the program will display Wrong
  • else, with the correct flag, we will have Correct!

Synthetizing the executions paths :

      __ puts('Wrong')
main_/
     \__ puts('Correct!')

Thanks to some researchers from CEA (France), there exist a tool named BINSEC who can explore all the paths and then identify how to follow the paths.

It’s not the only symbolic analysis tool available, but it’s very efficient for our situation.

Third step - configure BINSEC

Now that we know that the code prints “Correct!” via puts when feeding it the flag, we are going to analyse the executable to find path where puts takes “Correct!” as an argument.

With the help of the official documentation website we cooked this initialization file :

#Initialize the stack pointer with a concrete value
starting from core

stdin_p<64> := 0

#skip the syscall not supported by Binsec
replace opcode 0f05 by  
    print rax
    cut
end

#replace the function fgets not supported by Binsec and mimic the user input
replace <fgets>(buffer, size, stdin) by
  for i<64> in 0 to size - 1 do
    @[buffer + i] := stdin[stdin_p]
    stdin_p := stdin_p + 1
  end
  return buffer
end

#replace the function puts and detect when it is used with our target "Correct!" then print what input was used to reach that point
replace <puts>(arg1) by
    reach such that @[arg1, 8] = "Correct!" then print c string stdin
        and print c string @[arg1]
    return
end

#stop the analysis at the exit symbol, we don't need to analyze the exit routine
replace <exit> by
    halt
end

Once this configuration file written, we need to make babyfuscation executable :

$ chmod +x babyfuscation

We also need to generate a snapshot of the memory to have a concrete context for Binsec. This context is generated by executing the file until main is reached and therefore gives a proper initialization. This is done automatically by Binsec with the following command :

$ make_coredump.sh babyfuscation_snapshot babyfuscation.txt

Then start the analysis :

$ binsec -sse-script babyfuscation.ini babyfuscation_snapshot -sse-depth 100000 -smt-multi-checks

Binsec outputs that it can reach the desiered message with the following flag : FCSC{e30f46b147e7a25a7c8b865d0d895c7c7315f69582f432e9405b6d093b6fb8d3}.