Debugging EDA Binary License Checks: Tools and Methodology
This document captures the step-by-step process used to reverse-engineer and bypass license checks in a Cadence Innovus EDA binary. The same methodology applies to any ELF binary on Linux where you need to understand internal control flow without source code.Process Overview (Mermaid Diagrams)
Diagram 1: High-Level Workflow
Diagram 2: Invocation Chain Trace (Innovus Example)
Diagram 3: VA to File Offset Calculation
Diagram 4: License Check Call Stack (Innovus)
Diagram 5: Binary Patching Decision Tree
Diagram 6: EMS Error Code → Patch Site Pipeline
Table of Contents
- Overview and Goal
- Phase 1: Understand the Invocation Chain
- Phase 2: Locate the Real Binary
- Phase 3: Static Analysis with nm and objdump
- Phase 4: String Hunting
- Phase 5: Disassembly with GDB (offline mode)
- Phase 6: Understanding ELF Layout for Patching
- Phase 7: Crafting Binary Patches
- Phase 8: Applying Patches with Python
- Phase 9: Iterative Testing and Log Analysis
- Quick Reference: Essential Commands
- Tool Summary Table
1. Overview and Goal
The Problem: A commercial EDA tool (Cadence Innovus) checks out licenses from a FLEXlm/Cadence license server at startup and at various command invocations. If no server is available, the tool refuses to run. The Approach: Patch the ELF binary in place so license-check functions return “success” unconditionally, without modifying any source code. High-Level Steps:2. Phase 1: Understand the Invocation Chain
Before touching any binary, trace exactly what happens when you typeinnovus at the shell.
Tools: file, readlink, ls -la, which, type
For shell scripts: read them
3. Phase 2: Locate the Real Binary
Once you know the ELF path, confirm it is truly an ELF executable (not another script):nm. This massively
accelerates reverse engineering compared to a stripped binary.
4. Phase 3: Static Analysis with nm and objdump
nm — Symbol Table Dump
nm prints all symbols (functions, globals, data) from an ELF binary.
<address> <type> <name>
T= text segment (function code)D= initialized dataB= uninitialized (BSS) dataU= undefined (imported from shared library)t= local (static) function
objdump — Binary Disassembler
5. Phase 4: String Hunting
Error messages in EDA tools are often compiled into the binary or loaded from message catalog files. Find them to identify the code paths that trigger failures.strings — Extract Printable Strings
Searching Message Catalogs
Many EDA tools store error strings in separate.msg files:
msg=12), search the binary for calls to
emsIssueMsg with that argument:
6. Phase 5: Disassembly with GDB (Offline Mode)
GDB can load a binary without executing it (useful when you cannot run it, e.g., no license):Key GDB commands for static analysis
GDB Scripting
For bulk analysis, write a GDB script:7. Phase 6: Understanding ELF Layout for Patching
Before you can patch bytes in the file, you must map virtual addresses (VAs) seen in GDB/nm to file offsets in the binary on disk.ELF Segment Headers
file_offset vaddr paddr filesz memsz flags align
For the first LOAD segment:
- File offset
0x000000maps to VA0x0000000000400000 - Therefore:
file_offset = VA - 0x400000
0x400000.
Verify your formula
8. Phase 7: Crafting Binary Patches
Once you know which function to patch and its file offset, decide what to replace it with.Common x86-64 Patch Recipes
Return 0 (false/failure in Innovus convention = “license not needed check passed”)
31 c0 c3 90 90 90 90 90
Return 1 (true/success)
b8 01 00 00 00 c3 90 90
Return specific non-zero value (e.g., license handle = 5)
NOP out a conditional jump (skip an error branch)
If you want to prevent aje error_label from jumping:
jne (2 bytes) you want to always take → replace with eb xx (short jmp):
Verifying assembly bytes
Use Python with thekeystone or capstone library, or an online assembler:
9. Phase 8: Applying Patches with Python
Write a patcher script rather than using a hex editor — it’s reproducible, self-documenting, and verifies original bytes before patching.Patcher Template
Deploy the patched binary
10. Phase 9: Iterative Testing and Log Analysis
After applying patches, run the tool and analyze output logs to find the next failure.Run with output capture
Analyze logs
Map error codes back to binary patches
- Note the error code:
IMPVL-909 - Find the message catalog:
find /apps -name "*.msg" | xargs grep -l "909" - Read the catalog to find the message number:
grep "909\|flatten" licEms.msg - Search binary for
emsIssueMsgcalls with that message number - In GDB:
disassemblethe function that issues the message - Add a patch to that function
The Iterative Cycle
11. Quick Reference: Essential Commands
Discovery Phase
Static Analysis Phase
Offset Calculation
Patching
Log Analysis
12. Tool Summary Table
Appendix A: FLEXlm / Cadence License System Internals
Understanding the license layer helps identify which functions to target.FLEXlm return value convention
Cadence’s inversion layer
Cadence wraps FLEXlm and inverts the return value for its internal API:
This inversion means: when patching
rdaiLicCheckOut, return 1 (not 0).
When patching lower-level feLmCheckOut, return 0 (FLEXlm convention).
Feature license table
EDA tools maintain a static table of all features they may check out:- In Innovus:
rdagLicenseArr(127 entries, struct size0x648) - Each struct has: feature name, feature ID, short name at fixed offsets
- Read the table with GDB:
x/gx 0x495560e0then dereference each pointer
EMS (Error Message System)
Cadence’s error reporting system:- Function:
emsIssueMsg(catalog, msg_number, ...) - Catalogs:
IMPLIC(license),IMPVL(implementation), etc. - Message files:
licEms.msg,implEms.msgin the install tree - Tracing from error code → message number → emsIssueMsg call → calling function → patch site
Appendix B: Common Pitfalls
- Patching a wrapper, not the binary: Always verify
fileshows ELF before patching - Wrong offset formula: Verify by reading known bytes and comparing to GDB
- Return value direction: FLEXlm 0=success, but Innovus wrapper may invert this
- Multiple code paths: A single license function may be called from 5+ places; you may need multiple patches
- Lazy license checkout: Some features only check licenses when first used, not at startup — test the full flow
- Shared library patches: If the license code is in
libslm.so, you must patch that.so, not the main binary - ASLR: When running under ASLR, VAs shift at runtime. For offline patching this doesn’t matter — file offsets are fixed. For runtime debugging (live GDB), disable ASLR:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space - Stripped binaries: If
nmreturns “no symbols”, you must rely onstrings,strace/ltraceoutput, and heuristic analysis to find license functions