cesena / ghidra2dwarf

🐉 Export ghidra decompiled code to dwarf sections inside ELF binary
MIT License
179 stars 17 forks source link

BUG: AttributeError: 'NoneType' object has no attribute 'c' #7

Closed kotee4ko closed 2 years ago

kotee4ko commented 3 years ago
ghidra2dwarf.py> Running...
Traceback (most recent call last):
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 499, in <module>
    add_debug_info()
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 138, in add_debug_info
    add_function(cu, f, file_index)
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 318, in add_function
    d = res.decompiledFunction.c
AttributeError: 'NoneType' object has no attribute 'c'
ghidra2dwarf.py> Finished!

I'm new to Ghidra, so idk if my solution is right, but I found in docs that api got getC method.

and try to fix:


def add_function(cu, func, file_index):
    die = dwarf_new_die(dbg, DW_TAG_subprogram, cu, None, None, None)
    loc_expr = dwarf_new_expr(dbg)
    dwarf_add_expr_gen(loc_expr, DW_OP_call_frame_cfa, 0, 0)
    dwarf_add_AT_location_expr(dbg, die, DW_AT_frame_base, loc_expr)
    f_name = func.name
    dwarf_add_AT_name(die, f_name)
    dwarf_add_AT_string(dbg, die, DW_AT_linkage_name, f_name)

    # TODO: Check for multiple ranges
    f_start, f_end = get_function_range(func)

    t = func.returnType
    ret_type_die = add_type(cu, func.returnType)
    dwarf_add_AT_reference(dbg, die, DW_AT_type, ret_type_die)

    dwarf_add_AT_targ_address(dbg, die, DW_AT_low_pc, f_start, 0)
    dwarf_add_AT_targ_address(dbg, die, DW_AT_high_pc, f_end - 1, 0)

    func_line = len(decomp_lines) + 1

    res = get_decompiled_function(func)
    if res:
        try:
            d = res.decompiledFunction.c
        except:
            d = res.decompiledFunction.getC
    else:
        return

    decomp_lines.extend(d.split("\n"))
    dwarf_add_AT_unsigned_const(dbg, die, DW_AT_decl_file, file_index)
    dwarf_add_AT_unsigned_const(dbg, die, DW_AT_decl_line, func_line)
    dwarf_add_line_entry(dbg, file_index, f_start, func_line, 0, True, False)
    add_decompiler_func_info(cu, die, func, file_index, func_line)

    return die

But, no luck:

ghidra2dwarf.py> Running...
Traceback (most recent call last):
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 505, in <module>
    add_debug_info()
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 138, in add_debug_info
    add_function(cu, f, file_index)
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 322, in add_function
    d = res.decompiledFunction.getC
AttributeError: 'NoneType' object has no attribute 'getC'
ghidra2dwarf.py> Finished!

I found this in api docs:


   // Make calls to the decompiler:
   DecompileResults res = ifc.decompileFunction(func,0,taskmonitor);

   // Check for error conditions
   if (!res.decompileCompleted()) {
        system.out.println(res.getErrorMessage());
      return;
   }
kotee4ko commented 3 years ago

Yeah, as I see it working.

def add_function(cu, func, file_index):
    die = dwarf_new_die(dbg, DW_TAG_subprogram, cu, None, None, None)
    loc_expr = dwarf_new_expr(dbg)
    dwarf_add_expr_gen(loc_expr, DW_OP_call_frame_cfa, 0, 0)
    dwarf_add_AT_location_expr(dbg, die, DW_AT_frame_base, loc_expr)
    f_name = func.name
    dwarf_add_AT_name(die, f_name)
    dwarf_add_AT_string(dbg, die, DW_AT_linkage_name, f_name)

    # TODO: Check for multiple ranges
    f_start, f_end = get_function_range(func)

    t = func.returnType
    ret_type_die = add_type(cu, func.returnType)
    dwarf_add_AT_reference(dbg, die, DW_AT_type, ret_type_die)

    dwarf_add_AT_targ_address(dbg, die, DW_AT_low_pc, f_start, 0)
    dwarf_add_AT_targ_address(dbg, die, DW_AT_high_pc, f_end - 1, 0)

    func_line = len(decomp_lines) + 1

    res = get_decompiled_function(func)
    if res.decompileCompleted():
        try:
            d = res.decompiledFunction.c
        except:
            d = res.decompiledFunction.getC
    else:
        return

    decomp_lines.extend(d.split("\n"))
    dwarf_add_AT_unsigned_const(dbg, die, DW_AT_decl_file, file_index)
    dwarf_add_AT_unsigned_const(dbg, die, DW_AT_decl_line, func_line)
    dwarf_add_line_entry(dbg, file_index, f_start, func_line, 0, True, False)
    add_decompiler_func_info(cu, die, func, file_index, func_line)

    return die
NextLight commented 3 years ago

Thank you for opening the issue!

It looks like sometimes ghidra isn't able to decompile a function, so we shouldn't assume that the decompiled code exists.
We never encountered this error in our tests. Would you be able to share the executable?

Btw .c and .getC() are the same thing in Jython. It basically converts all the methods that starts with get and have 0 parameters into python properties.

kotee4ko commented 3 years ago

Btw .c and .getC() are the same thing in Jython. It basically converts all the methods that starts with get and have 0 parameters into python properties. Thanks, will be know. I'm more familiar with C programming :)

It looks like sometimes ghidra isn't able to decompile a function, so we shouldn't assume that the decompiled code exists. It happens when Ghidra could-not de-compile a function, yes. This is quite often case for large binaries.

I think I can share binary, but I wish to do it privately. May I?

UPD: I found a new bug on my own fix - offsets are gone. I think we need to place check of successfully decomp. to the beginning of the add_function(), or to feel d with dumb output. Like, //sorry can't decompile. I try second variant, now I got:

Traceback (most recent call last):
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 508, in <module>
    add_debug_info()
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 138, in add_debug_info
    add_function(cu, f, file_index)
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 333, in add_function
    add_decompiler_func_info(cu, die, func, file_index, func_line)
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 190, in add_decompiler_func_info
    for name, datatype, addr, storage in get_decompiled_variables(decomp):
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 182, in get_decompiled_variables
    for s in hf.localSymbolMap.symbols:
AttributeError: 'NoneType' object has no attribute 'localSymbolMap'
kotee4ko commented 3 years ago

btw, the same thing happens when just press cancel while plug-in running:

Traceback (most recent call last):
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 509, in <module>
    add_debug_info()
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 138, in add_debug_info
    add_function(cu, f, file_index)
  File "/root/ghidra_scripts/ghidra2dwarf/ghidra2dwarf.py", line 319, in add_function
    if res.decompileCompleted():
AttributeError: 'NoneType' object has no attribute 'decompileCompleted'
ghidra2dwarf.py> User cancelled script.
kotee4ko commented 3 years ago

Offsets are completely gone away :(

in generated name_dbg.c:

216767 undefined4 FUN_005735a0(long *param_1,byte **param_2,uint *param_3)
216768
216769 {
216770   long *plVar1;
216771   char cVar2;
216772   byte bVar3;
216773   char extraout_AL;
216774   undefined4 uVar4;
216775   uint uVar5;
216776   undefined **ppuVar6;
216777   undefined **ppuVar7;
216778   undefined8 uVar8;
216779   uint local_3c [3];

...snipped...

in pwndbg:

pwndbg> list *FUN_005735a0
0x5735a0 is in FUN_005735a0 (./Serv-U_dbg.c:3851).
3846          piVar1 = (int *)(param_1 + 0x60);
3847          uVar3 = (uint)(*(char *)(param_1 + 0x55) == '\0');
3848          FUN_007c3980(uVar3,param_5,8,piVar1,piVar1);
3849          FUN_007c3980(uVar3,param_4,*(int *)(param_1 + 0x38),piVar1,piVar1);
3850          uVar3 = param_5[4];
3851          if ((*(uint *)(param_1 + 0x60) ==
3852               (uVar3 >> 0x18 | (uVar3 & 0xff0000) >> 8 | (uVar3 & 0xff00) << 8 | uVar3 << 0x18)) &&
3853             (uVar3 = param_5[5],
3854             *(uint *)(param_1 + 100) ==
3855             (uVar3 >> 0x18 | (uVar3 & 0xff0000) >> 8 | (uVar3 & 0xff00) << 8 | uVar3 << 0x18))) {
NextLight commented 3 years ago

I think I can share binary, but I wish to do it privately. May I?

Feel free to send it via email at 33e3393c59bed4a77cb2d2ec2cd80e13 AT protonmail.com.

kotee4ko commented 3 years ago

Done.

NextLight commented 3 years ago

I was unable to reproduce your first issue using the binary you sent us.

Are you using the latest version of ghidra2drawf?
What version of Ghidra are you using?
Did you change something (like names or types) in Ghidra before running the script?

kotee4ko commented 3 years ago

Are you using the latest version of ghidra2drawf? Yes.

What version of Ghidra are you using? I check it with builded from scratch ghidra_9.3_DEV_20210406_linux64 on openjdk 13.0.4 2020-07-14 and on the server machine with openjdk 16 2021-03-16.

Did you change something (like names or types) in Ghidra before running the script? nope.

kotee4ko commented 3 years ago

Just test it with latest prebuilded ghidra. It generate reverse-unstripped binary and source file. But, all functions prototypes are undefined (void))

For ex. In ghidra: https://i.ibb.co/K6qStqG/image.png

In generated *.c file: https://i.ibb.co/3vt30HW/image.png

Under GDB: https://i.ibb.co/N7fMdFP/image.png

So, idk, but it's seems that main symptomatic of bug still present.

I can provide more details if needed.

P.S. I try to import data types and names from IDA, via xml file. And generate dwarf.

Ghidra crashed with sigsegv.

kotee4ko commented 3 years ago

just notice, that results of 9.3 with some decompile-code check fixes in plugin is the same as for 9.2 with stock plugin. haha, am I made adaptation for feature versions of ghidra? :)

johnkeates commented 2 years ago

I actually ran in to this issue today, in my case it probably didn't generate sources for all functions so the c source is indeed not available. I can probably add a try/catch to ignore that but I wonder how the dressing and dwarf format will react to that.

NextLight commented 2 years ago

This should have been fixed by #10.

Feel free to re-open this issue if this is not the case.