Skip to content

Problem 2: evalme

Field: LowUIR lifting, concrete evaluation

evalme is a stripped binary that computes the accepted password at runtime, stores it in a local buffer, and compares the command-line argument against that generated value.

Use the evalme/ directory from tutorial.zip.

It has this layout:

evalme/
Makefile
src/
evalme.c
bin/
evalme
evalme-arm
evalme-mips
strip/
evalme
evalme-arm
evalme-mips

The stripped challenge binary is at strip/evalme. The unstripped rebuild is at bin/evalme. The source file and unstripped binaries are provided for verification; use the stripped binaries for the exercise.

Recover the password accepted by evalme.

evalme expects one argv token. It computes an expected password at runtime, stores it in a local buffer, and compares argv against that buffer.

The program also contains an anti-debugging check before the comparison. The anti-debugging check exits immediately when it detects tracing. You can still bypass it by patching the binary, skipping the check, or neutralizing it with a debugger. However, the intended goal of this tutorial is to use the B2R2 API to concretely execute the password-generation code.

  1. Locate the function that generates the expected password.
  2. Identify the first-argument convention. On System V AMD64, the first argument is passed in RDI.
  3. Prepare a writable output buffer in the concrete evaluation state.
  4. Execute the password-generation function until it returns, following its internal helper calls.
  5. Read the generated C string from the output buffer.
  6. Verify the recovered password by running the binary normally.

Save this as evalme.fsx in the extracted evalme/ directory:

#r "nuget: B2R2.MiddleEnd.ConcEval"
open System.IO
open B2R2
open B2R2.FrontEnd
open B2R2.MiddleEnd.ConcEval
let binaryPath =
Path.Combine(__SOURCE_DIRECTORY__, "strip", "evalme")
let hdl = BinHandle binaryPath
let executor = ConcExecutor hdl
let startAddr =
let endAddr =
let state =
let accessor =
// TODO: initialize the stack and frame pointer.
let buf =
// TODO: pass buf as argument 0.
let result = executor.Run(startAddr, state, StopAtAddress endAddr)
if result.IsStoppedAtAddress endAddr then
let password =
printfn "Recovered password: %s" password
else
printfn "Execution stopped before reaching 0x%x" endAddr

Run the script from the extracted evalme/ directory:

Terminal window
dotnet fsi evalme.fsx

On Unix-like systems, if the extracted binary is not executable, run:

Terminal window
chmod +x strip/evalme
Show solution

Unified solution script. The x86-64 version is active by default. To run ARM or MIPS, comment out the x86-64 lines and uncomment the matching architecture block.

#r "nuget: B2R2.MiddleEnd.ConcEval"
// MIPS: for global pointer set
// #r "nuget: B2R2.FrontEnd.MIPS"
open System.IO
open B2R2
open B2R2.FrontEnd
// ARM: uncomment when using the ARM32 frontend.
// open B2R2.FrontEnd.ARM32
// MIPS: uncomment for ELFBinFile.GlobalPointer.
// open B2R2.FrontEnd.BinFile
open B2R2.MiddleEnd.ConcEval
let stackTop = 0x7fffe000UL
// x86-64
let binaryPath =
Path.Combine(__SOURCE_DIRECTORY__, "strip", "evalme")
let startAddr = 0x40142cUL
let endAddr = 0x401507UL
// ARM:
// let binaryPath =
// Path.Combine(__SOURCE_DIRECTORY__, "strip", "evalme-arm")
// let startAddr = 0x00010a00UL
// let endAddr = 0x10b38UL
// MIPS:
// let binaryPath =
// Path.Combine(__SOURCE_DIRECTORY__, "strip", "evalme-mips")
// let startAddr = 0x400bfcUL
// let endAddr = 0x400d74UL
let hdl = BinHandle binaryPath
let executor = ConcExecutor hdl
let state = executor.CreateState()
let accessor = ConcStateAccessor(hdl, state)
accessor.InitializeStack stackTop
accessor.InitializeFramePointer()
// MIPS only:
// match hdl.File with
// | :? ELFBinFile as elf ->
// match elf.GlobalPointer with
// | Some gp -> accessor.SetRegister("GP", accessor.WordValue gp)
// | None -> ()
// | _ -> ()
let buf = accessor.AllocateStackBuffer 32
accessor.SetArgument(0, accessor.WordValue buf)
// x86-64: You can use RDI instead.
// accessor.SetRegister("RDI", accessor.WordValue buf)
// ARM: You can use R0 instead.
// accessor.SetRegister("R0", accessor.WordValue buf)
// MIPS O32 uses the caller stack area above the callee frame for argument
// spills, so keep extra scratch space between SP and our output buffer.
// accessor.AllocateStackBuffer 128 |> ignore
// MIPS: You can use A0 instead.
// accessor.SetRegister("A0", accessor.WordValue buf)
// accessor.SetRegister("NPC", accessor.WordValue(startAddr + 4UL))
let result = executor.Run(startAddr, state, StopAtAddress endAddr)
if result.IsStoppedAtAddress endAddr then
let password = accessor.ReadCString(buf, 32)
printfn "Recovered password: %s" password

Script output:

Recovered password: b2r2-concrete!

Binary run:

Terminal window
./strip/evalme b2r2-concrete!
accepted