Problem 3: loginme
Overview
Section titled “Overview”Field: Symbolic execution, argv modeling, path exploration
loginme is a stripped binary that checks one command-line token and prints
either Access granted or Access denied. The exercise is to model the argv
input symbolically and search for a path to the success block.
Use the loginme/ directory from
tutorial.zip.
It has this layout:
loginme/ Makefile src/ loginme.c bin/ loginme strip/ loginmeThe stripped challenge binary is at strip/loginme. The unstripped rebuild is
at bin/loginme. The source file and unstripped binary are provided for
verification; use the stripped binary for the exercise.
Recover the password accepted by loginme.
Problem Description
Section titled “Problem Description”loginme expects exactly one argv token. On success it prints:
Access grantedand exits with status 0. On failure it prints:
Access deniedand exits with status 1.
The binary first calls imported strlen and then checks byte-level constraints.
Solve it with symbolic execution rather than manual string reading or branch
patching.
Suggested Workflow
Section titled “Suggested Workflow”- Inspect the binary’s functions and PLT entries with B2R2.
- Find the main-like function.
- In the main-like function, locate the code that checks
argcand loadsargv[1]. - From there, find the internal function call that receives the
argv[1]buffer as an argument. Use that callee’s entry address asstartAddr. - Inspect the function at
startAddr. Find the importedstrlencall throughstrlen@pltand record that PLT address asstrlenPltAddr. - In the same function, identify which return path means the input was
accepted. Use the basic block or instruction address on that path as
endAddr. - Write the symbolic execution script: create the symbolic state, model the
callee’s first argument as a symbolic input buffer, register the
strlenhook, and search fromstartAddrtoendAddr. - Extract a concrete token from the solver model and verify it by running the binary normally.
Starter Code
Section titled “Starter Code”Save this as loginme.fsx in the extracted loginme/ directory:
The script below uses the Windows/NuGet Z3 reference. On Linux or macOS, replace
the Microsoft.Z3 reference with the matching local Z3 reference from the Z3
setup note above.
#r "nuget: B2R2.MiddleEnd.SymbEval"#r "nuget: B2R2.FrontEnd.Intel"// Windows:#r "nuget: Microsoft.Z3, 4.12.2"// Linux x64 (Ubuntu 24.04):// #I "z3-4.16.0-x64-glibc-2.39/bin/"// #r "Microsoft.Z3.dll"// Linux x64 (Ubuntu 22.04):// #I "z3-4.12.2-x64-glibc-2.35/bin/"// #r "Microsoft.Z3.dll"// macOS ARM64:// #I "z3-4.16.0-arm64-osx-15.7.3/bin/"// #r "Microsoft.Z3.dll"// macOS Intel:// #I "z3-4.16.0-x64-osx-15.7.3/bin/"// #r "Microsoft.Z3.dll"
open System.Collections.Genericopen System.IOopen Microsoft.Z3open B2R2open B2R2.FrontEndopen B2R2.MiddleEnd.SymbEval
let binaryPath = Path.Combine(__SOURCE_DIRECTORY__, "strip", "loginme")
let startAddr =let strlenPltAddr =let endAddr =
let hdl = BinHandle binaryPathlet executor = SymbExecutor hdl
let state =let accessor =
// TODO: initialize the stack and frame pointer.
// TODO: model the chosen function's first argument as a symbolic string.// The buffer returned here is later used as the value to solve and print.let buf =
let callHooks = SymbCallHookRegistry().Register(strlenPltAddr, SymbCallHooks.strlen)
let z3 = { new ISolver with member _.CheckSat smt2 = try let cfg = Dictionary<string, string>() cfg.Add("model", "true") use ctx = new Context(cfg) use solver = ctx.MkSolver() solver.FromString smt2 solver.Check() |> string |> Ok with ex -> SolverFailure(SolverCommunicationFailure ex.Message) |> Error
member _.GetModels smt2 = try let cfg = Dictionary<string, string>() cfg.Add("model", "true") use ctx = new Context(cfg) use solver = ctx.MkSolver() solver.FromString smt2 if solver.Check() = Status.SATISFIABLE then solver.Model.ToString() |> Ok else Ok "" with ex -> SolverFailure(SolverCommunicationFailure ex.Message) |> Error }
let query =
let result = executor.Run(startAddr, state, UseCallHooks callHooks, query, CustomSolver z3)let answer = result.GetSatisfiabilityAnswer()let password =printfn "Recovered password: %s" passwordRecommended References
Section titled “Recommended References”BinHandlefor loading the target binary.SymbExecutorfor creating symbolic states and running a query.SymbQueryRequest,Query, andQueryValuesfor the{ Query = SatisfyAddress endAddr; QueryValues = buf }record.SymbStateAccessor,AllocateSymbolicString,SetArgument, andSymbByteBufferfor stack setup and symbolic argument buffers.ISolver,GetSatisfiabilityAnswer,SymbModel, andReadCStringfor Z3 integration and model extraction.SymbCallHookRegistryandSymbCallHooks.strlenfor modeling the importedstrlencall.
Run the script from the extracted loginme/ directory:
dotnet fsi loginme.fsxOn Unix-like systems, if the extracted binary is not executable, run:
chmod +x strip/loginmeIf you are using Linux or macOS, make sure the Z3 #I path in
loginme.fsx matches the directory you unpacked before running the script.
Solution
Section titled “Solution”Show solution
This solution uses the Windows/NuGet Z3 reference. On Linux or macOS, replace
the Microsoft.Z3 reference with the matching local Z3 reference from the Z3
setup note above.
#r "nuget: B2R2.MiddleEnd.SymbEval"#r "nuget: B2R2.FrontEnd.Intel"// Windows:#r "nuget: Microsoft.Z3, 4.12.2"// Linux x64 (Ubuntu 24.04):// #I "z3-4.16.0-x64-glibc-2.39/bin/"// #r "Microsoft.Z3.dll"// Linux x64 (Ubuntu 22.04):// #I "z3-4.12.2-x64-glibc-2.35/bin/"// #r "Microsoft.Z3.dll"// macOS ARM64:// #I "z3-4.16.0-arm64-osx-15.7.3/bin/"// #r "Microsoft.Z3.dll"// macOS Intel:// #I "z3-4.16.0-x64-osx-15.7.3/bin/"// #r "Microsoft.Z3.dll"
open System.Collections.Genericopen System.IOopen Microsoft.Z3open B2R2open B2R2.FrontEndopen B2R2.MiddleEnd.SymbEval
let binaryPath = Path.Combine(__SOURCE_DIRECTORY__, "strip", "loginme")
let startAddr = 0x401136ULlet strlenPltAddr = 0x401040ULlet endAddr = 0x401300UL
let hdl = BinHandle binaryPathlet executor = SymbExecutor hdl
let state = executor.CreateState()let accessor = SymbStateAccessor(hdl, state)
accessor.InitializeDefaultStack()accessor.InitializeFramePointer()
let buf = accessor.AllocateSymbolicString "password"accessor.SetArgument(0, accessor.WordValue buf.Address)
let callHooks = SymbCallHookRegistry().Register(strlenPltAddr, SymbCallHooks.strlen)
let z3 = { new ISolver with member _.CheckSat smt2 = try let cfg = Dictionary<string, string>() cfg.Add("model", "true") use ctx = new Context(cfg) use solver = ctx.MkSolver() solver.FromString smt2 solver.Check() |> string |> Ok with ex -> SolverFailure(SolverCommunicationFailure ex.Message) |> Error
member _.GetModels smt2 = try let cfg = Dictionary<string, string>() cfg.Add("model", "true") use ctx = new Context(cfg) use solver = ctx.MkSolver() solver.FromString smt2 if solver.Check() = Status.SATISFIABLE then solver.Model.ToString() |> Ok else Ok "" with ex -> SolverFailure(SolverCommunicationFailure ex.Message) |> Error }
let query = { Query = SatisfyAddress endAddr QueryValues = buf }
let result = executor.Run(startAddr, state, UseCallHooks callHooks, query, CustomSolver z3)let answer = result.GetSatisfiabilityAnswer()let password = answer.Model.ReadCString bufprintfn "Recovered password: %s" passwordScript output:
Recovered password: icst-b2r2-tutorialBinary run:
./strip/loginme icst-b2r2-tutorialAccess granted