4

I want to identify the jump statements due to switch/case in an IDA Pro disassembled binary. My ultimate goal is to read the jump table entries. I am also interested in function table/vtables. For switch/case, I see the jump statements as:

  1. jmp ds:off_20B280CC[ebx*4]

  2. jmp dword ptr ds:loc_6B2A825C[ecx*4] [Q: Is it due to switch/jump?]

The operand types of these jumps, as I see from GetOperandValue(inst.ea, 0), are "Memory Reference" (type value 2). The jump statements like jb short loc_6B2A8154 has operand type "Immediate Near Address" (type value 7). However, the jump statements like jmp ds:__imp_memset in the thunk functions to call imported functions also have the operand type "Memory Reference".

Is there any way I can distinguish between jump statements for switch/case and thunk functions?

3 Answers 3

3

In many cases, IDA already knows that a jump is part of a jump-table and probably the result of a switch. When this is true, you can access it using IDAPython.

The relevant functions are get_switch_info_ex(ea) and get_switch_info_ex(ea). Looking in IDAPython's documentation, we find:

calc_switch_cases(insn_ea, py_swi)

Get information about a switch's cases.

The returned information can be used as follows:

for idx in xrange(len(results.cases)):
    cur_case = results.cases[idx]
    for cidx in xrange(len(cur_case)):
        print "case: %d" % cur_case[cidx]
    print "  goto 0x%x" % results.targets[idx]

@param insn_ea: address of the 'indirect jump' instruction @param si: switch information

@return: a structure with 2 members: 'cases', and 'targets'.

Returns: cases_and_targets_t

To get the results variable from the example, we use the following code:

si = idaapi.get_switch_info_ex(ea)
results = idaapi.calc_switch_cases(ea, si)
if not results:
    print "No switch related jump at 0x{:X}".format(ea)

So to check if an instruction is a switch or not, you can use the following function:

def is_switch(ea):
    si = idaapi.get_switch_info_ex(ea)
    results = idaapi.calc_switch_cases(ea, si)
    return bool(results)

If you wish to use it, I've written a basic wrapper class for IDA's switch in Sark. See here.

0

On the x86 architecture, jump statements due to switch/case statements often follow the pattern:

jmp <offset> [<index-register> * 4]   ;; pointers are 4 bytes

where <index-register> contains the value being switched on and the "4" is the scaling factor. Depending on your compiler may have good results searching for jump instructions with indexed memory operands.

However the compiler is free to compile switch statements into cascades of cmp and jnz statements if it determines that doing so would produce more efficient code. For instance:

switch (foo) {
case 1: do_something(); break;
case 10000: do_something_else(); break;
case 1000000: do_final_thing(); break;
}

would be terribly inefficient implemented as a table lookup. Therefore, just looking for indexed memory operands won't necessarily find all switch/cases in your program.

You need also take into account that the compiler may write the switch differently. It is fully possible that it might determine that it is better to emit the sequence

mov eax,table_offset[eax*4]
jmp eax

or, as in old hand-rolled binaries adapted from hand-written assembler written for 8086 or 80286 real mode:

shl eax,4
jmp table_offset[eax]

To cover all bases, you will be forced to use other techniques like backward slicing to determine the destinations of a compiled switch/case statement.

2
  • Thanks for explaining other switch/case scenarios. I'll have to take care of those. However, I am taking figuring out the indexed memory operands for switch/case jumps as a first step. I am using IDA Python scripting to get the instruction types, operand types and values, etc. Using IDA Python, I still cannot distinguish between switch/case jumps to indexed memory references and other memory references due to thunk functions. Any pointer would be appreciated.
    – ssultana
    Commented Apr 1, 2016 at 18:38
  • I'm not familiar with IDA but could you, after identifying the indirect jmp, then compare that its opcode startis with FF 24 85 / 8D / 95 / 9D etc. I.e mask out the i part of the sib byte of the indirect jump? Commented Apr 1, 2016 at 18:50
0

Similar to Sark's solution, minsc at https://github.com/arizvisa/ida-minsc provides a wrapper around the same switch_info_t that is fetched in IDAPython using the get_switch_info function from nalt.hpp. It is worth noting that these solutions use the disassembler exclusively (as opposed to the decompiler).

Disclaimer: I'm the maintainer of the ida-minsc plugin.

To get to the minsc wrapper, the database.get.switch function is used to fetch a switch_t. The following python disassembles a branch instruction and then uses its address to get the "switch" identified by the disassembler. The switch_t that is returned contains 0x10 total cases and exposes numerous properties that can be used to extract information from the "switch". As per the question, the database.type.switch function can be used to detect if an address branches to a table used by the "switch" (or belongs to a handler) that was detected by the disassembler.

Python>db.disasm()
'277ef564: jmp ds:jpt_277EF564[eax*4]'

Python>sw=db.get.switch()
Python>sw
<class 'switch_t{16}' at 0x277ef54f> default:0x277ef699 branch[8]:0x277ef6b3 indirect[16]:0x277ef6d3 register:%eax

Python>db.t.switch()
True

The switch_t.range property returns the entire range of cases for the switch that was determined by the disassembler. Similarly, the switch_t.cases property is used to return only the cases that do not dispatch to the "default" handler for the fetched "switch". Also, if one wants to create a breakpoint logging the case being dispatched to by the "switch", the switch_t.ea property can be combined with the switch_t.register property in order to identify the register needing to be captured.

# Return the full range of cases supported by the "switch".
Python>sw.range
(0x10000000, 0x10000001, 0x10000002, 0x10000003, 0x10000004, 0x10000005, 0x10000006, 0x10000007, 0x10000008, 0x10000009, 0x1000000a, 0x1000000b, 0x1000000c, 0x1000000d, 0x1000000e, 0x1000000f)

# Return the cases of the "switch" that dispatch to a non-default case.
Python>sw.cases
(0x10000000, 0x10000001, 0x10000002, 0x10000003, 0x10000004, 0x10000005, 0x10000007, 0x10000008, 0x10000009, 0x1000000a, 0x1000000b, 0x1000000c, 0x1000000d, 0x1000000e, 0x1000000f)

# Other properties that could be useful...
Python>db.disasm(sw.ea)
'277ef54f: add eax, 0F0000000h'

Python>sw.register
<class 'register_t' index=0 dtype=dt_dword name='eax' position=0+32>

Python>database.up(sw.branch_ea)
[ref_t(address=0x277ef564, access=access_t(r))]

Python>[db.disasm(ea) for ea in database.up(sw.branch_ea)]
['277ef564: jmp ds:jpt_277EF564[eax*4]']

# Display operand information that might be used for filtering.
Python>ea, = database.up(sw.branch_ea)
Python>ins.type.branch(ea)
True

Python>instruction.ops_access(ea)
(opref_t(address=0x277ef564, opnum=0, access=access_t(rx)))

Python>ins.ops(ea)
(SegmentOffsetBaseIndexScale(segment=%ds, offset=0x277ef6b3, base=None, index=%eax, scale=4))

For addresses being dispatched to by the "switch", the switch_t.handlers property can be used to return the address of each handler, with the switch_t.default property being responsible for returning the address of the "default" handler. To return the handler for a specific case, the switch_t.case() function can be used as in the following python.

# Return all of the available handlers used by the "switch".
Python>sw.handlers
(0x277ef67e, 0x277ef56b, 0x277ef644, 0x277ef685, 0x277ef699)

# Return the default handler for the "switch".
Python>sw.default
0x277ef699

# Return the handler for a specific case.
Python>sw.case(0x10000000)
0x277ef67e

By combining the handler-related properties, one can create a python dictionary for looking up the dispatcher for a specific case as in the following code. However, it is probably more useful to determine the number of cases that are implemented by each handler in order to distinguish what is actually worth reversing. This can be done by inverting the dictionary so that its key references the handler, with its value containing the list of cases being supported.

Python>{case : sw.case(case) for case in sw.range}
{0x10000000: 0x277ef67e, 0x10000001: 0x277ef67e, 0x10000002: 0x277ef67e, 0x10000003: 0x277ef67e, 0x10000004: 0x277ef67e, 0x10000005: 0x277ef67e, 0x10000006: 0x277ef699, 0x10000007: 0x277ef67e, 0x10000008: 0x277ef56b, 0x10000009: 0x277ef67e, 0x1000000a: 0x277ef67e, 0x1000000b: 0x277ef644, 0x1000000c: 0x277ef67e, 0x1000000d: 0x277ef67e, 0x1000000e: 0x277ef67e, 0x1000000f: 0x277ef685}

# Invert the table of case<->handlers.
Python>handlers={}
Python>[ handlers.setdefault(sw.case(case), []).append(case) for case in sw.cases ]
[None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]

Python>handlers
{0x277ef67e: [0x10000000, 0x10000001, 0x10000002, 0x10000003, 0x10000004, 0x10000005, 0x10000007, 0x10000009, 0x1000000a, 0x1000000c, 0x1000000d, 0x1000000e], 0x277ef699: [0x10000006], 0x277ef56b: [0x10000008], 0x277ef644: [0x1000000b], 0x277ef685: [0x1000000f]} 

This dictionary can be used for operations such as notating the handler address with a descriptive name, color, etc. The following python produces a name following the format "case(number1,number2,number3,...)" and applies it as a label for each handler. Afterwards, the database.names namespace is used with the "bounds" matcher and function.chunk to list all the labels within the current function chunk...showing the labels we just applied.

# Assign a label to the address of each individual case.
Python>for ea, cases in handlers.items():
Python> db.name(ea, "case({:s})".format(','.join(hex(case) for case in sorted(cases))), listed=True)
Python>

# List all the labels within the current function chunk.
Python>db.names.list(bounds=func.chunk())
[322] 0x277ef53e : ^C* : jsvda_920
[323] 0x277ef56b : ^C* : case(10000008)
[324] 0x277ef644 : ^C* : case(1000000b)
[325] 0x277ef67e : ^C* : case(10000000,10000001,10000002,10000003,10000004,10000005,10000007,10000009,1000000a,1000000c,1000000d,1000000e)
[326] 0x277ef685 : ^C* : case(1000000f)
[327] 0x277ef699 : ^C* : def_277EF564

For enumerating switches, the function.switches function can be used with an address belonging to a function. The next python combines this with the database.functions namespace to identify every function containing 1 or more switches within the database. After all of the candidate functions have been enumerated, the function.tag function is used to store the total number of cases that are handled by the switches within each function.

# Collect all functions that have at least one "switch" within its body.
Python>functions_with_switches = {ea for ea in db.functions() if next(func.switches(ea), None) is not None}
Python>functions_with_switches
set([0x277bd6cd, 0x277d5b8f, 0x2780c071, 0x2780e8b1, 0x277a7833, 0x277beb74, 0x277fef38, 0x277ac0fb, 0x277a2ebd, 0x277ef53e])

# Iterate through all functions and collect the number of "switch" statements detected by the disassembler.
Python>for ea in functions_with_switches:
Python> count = sum(len(sw.cases) for sw in func.switches(ea))
Python> fn.tag(ea, 'switch.cases', count)

Once a function has been tagged and indexed with some value, database.functions can be used with the "tagged" matcher to narrow down the list of functions containing our previously identified switches. To query the index for the number of cases tagged to each function, the database.select function can also be used.

Python>db.functions(tagged='switch.cases')
[0x277a2ebd, 0x277a7833, 0x277ac0fb, 0x277bd6cd, 0x277beb74, 0x277d5b8f, 0x277ef53e, 0x277fef38, 0x2780c071, 0x2780e8b1]

Python>db.functions.list(tagged='switch.cases')
[0]  +0x02ebd : 0x277a2ebd..0x277a3331 : (1) Fvt* : __cdecl jsvda_919                                             : lvars:330 args:5 refs:4 exits:1
[1]  +0x07833 : 0x277a7833..0x277a78ff : (1) Fv-* : __cdecl sub_277A7833                                          : lvars:010 args:4 refs:1 exits:1
[2]  +0x0c0fb : 0x277ac0fb..0x277ac61b : (1) Fvt* : __cdecl sub_277AC0FB                                          : lvars:024 args:7 refs:1 exits:1
[3]  +0x1d6cd : 0x277bd6cd..0x277bd82c : (1) Fvt* : __cdecl jsvda_950                                             : lvars:010 args:3 refs:5 exits:1
[4]  +0x1eb74 : 0x277beb74..0x277beca6 : (1) F-t* : __cdecl sub_277BEB74                                          : lvars:008 args:4 refs:1 exits:1
[5]  +0x35b8f : 0x277d5b8f..0x277d6059 : (1) FvT* : __cdecl JSVDA::204                                            : lvars:02c args:8 refs:2 exits:1
[6]  +0x4f53e : 0x277ef53e..0x277ef6b3 : (1) Fvt* : __cdecl jsvda_920                                             : lvars:014 args:4 refs:3 exits:1
[7]  +0x5ef38 : 0x277fef38..0x277ff219 : (1) Fvt* : __cdecl sub_277FEF38                                          : lvars:040 args:3 refs:1 exits:1
[8]  +0x6c071 : 0x2780c071..0x2780c5cc : (1) FvT* : __cdecl JSVDA::object_6b3a6::getPathAttributeFromBucket_6c071 : lvars:024 args:5 refs:6 exits:1
[9]  +0x6e8b1 : 0x2780e8b1..0x2780eabb : (1) F--* : __cdecl sub_2780E8B1                                          : lvars:00c args:4 refs:1 exits:1

Python>for ea, res in db.select('switch.cases'):
Python> print(res['switch.cases'], hex(ea), func.name(ea), sum(1 for blk in function.blocks(ea)))
Python>
11 277a2ebd jsvda_919 111
8 277a7833 sub_277A7833 25
10 277ac0fb sub_277AC0FB 136
16 277bd6cd jsvda_950 47
15 277beb74 sub_277BEB74 30
9 277d5b8f JSVDA::204 116
15 277ef53e jsvda_920 35
13 277fef38 sub_277FEF38 76
15 2780c071 JSVDA::object_6b3a6::getPathAttributeFromBucket_6c071 120
11 2780e8b1 sub_2780E8B1 81

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.