Vector35 / Vector35/binaryninja-api

Empty class declarations don't correctly propagate inherited fields to subclasses

Aperta
#5,982 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Component: Type Parser Effort: Medium Impact: Low
Lingua principale
C++
Stelle
1.3k
Fork
298
Merge medio
5g 5h
PR unite (30g)
19

Descrizione

Version and Platform (required):

  • Binary Ninja Version: 4.1.5902 (f2165c5d) and 4.2.6168-dev (cb0f14d8)
  • OS: macOS
  • OS Version: 15.0
  • CPU Architecture: M1

Bug Description:
Wasn't sure how exactly to title this issue.

Given 3 classes; foo, bar and baz, where baz is a subclass of bar and bar is a subclass of foo. If bar is empty, aside from fields inherited from foo, those inherited fields will be overwritten by fields declared in baz.

Steps To Reproduce:
Use the Create Types from C Source window and paste in the following type definitions all together:

class foo {
	int64_t field1;
	int64_t field2;
};

class __base(foo, 0) bar {
};

class __base(bar, 0) baz {
	int64_t field3;
};

The following 3 types should be created in the user types section:

class foo
{
    int64_t field1;
    int64_t field2;
};

class __base(foo, 0) bar
{
    __inherited int64_t foo::field1;
    __inherited int64_t foo::field2;
};

class __base(bar, 0) baz
{
    int64_t field3;
    __inherited int64_t foo::field2;
};

Notice how baz has field3 at offset 0 and has overwritten foo::field1.

Expected Behavior:
field3 for baz should be after the 2 inherited fields from foo, and not overlap/overwrite foo::field1. These are the types I expect to be created:

class foo
{
    int64_t field1;
    int64_t field2;
};

class __base(foo, 0) bar
{
    __inherited int64_t foo::field1;
    __inherited int64_t foo::field2;
};

class __base(bar, 0) baz
{
    __inherited int64_t foo::field1;
    __inherited int64_t foo::field2;
    int64_t field3;
};

Additional Information:
I've noticed that creating an empty class type like bar, that inherits from a class with fields, using the API StructureBuilder, results in types like bar showing as having a 0 size in the user types list. Also inspecting the StructureBuilder.width using the API will report a width of 0. As soon as a field is added to the bar StructureBuilder its size will suddenly jump from 0 to the actual size it should be (size of inherited fields plus the field just added). So it seems the issue lies with the fact that internally Binary Ninja seems to think empty classes that inherit from another class are 0 sized even if they inherit fields.

Running the following Python code in the Binary Ninja intepreter should show this:

# Create a base type `foo`
foo_builder = binaryninja.StructureBuilder.create()
foo_builder.append(binaryninja.Type.int(8), "field1")
foo_builder.append(binaryninja.Type.int(8), "field2")
bv.define_user_type("foo", foo_builder.immutable_copy())

# Create a new type `bar` that inherits from `foo`
bar_builder = binaryninja.StructureBuilder.create()
bar_builder.base_structures = [ binaryninja.BaseStructure(bv.types["foo"], 0) ]
print("bar_builder.width =", bar_builder.width)
bv.define_user_type("bar", bar_builder.immutable_copy())
print("sizeof(bar) =",bv.types["bar"].width)

# Show that the width updates correctly once a field is added
bar_builder.append(binaryninja.Type.int(8), "field3")
print("bar_builder.width =", bar_builder.width)
bv.define_user_type("bar", bar_builder.immutable_copy())
print("sizeof(bar) =",bv.types["bar"].width)

Some Workarounds:
If creating problematic types like these using the API the following can be a temporary workaround:

# Create a base type `foo`
foo_builder = binaryninja.StructureBuilder.create()
foo_builder.append(binaryninja.Type.int(8), "field1")
foo_builder.append(binaryninja.Type.int(8), "field2")
bv.define_user_type("foo", foo_builder.immutable_copy())

# Create a new type `bar` that inherits from `foo`
bar_builder = binaryninja.StructureBuilder.create()
bar_builder.base_structures = [ binaryninja.BaseStructure(bv.types["foo"], 0) ]
print("bar_builder.width =", bar_builder.width)
bv.define_user_type("bar", bar_builder.immutable_copy())
print("sizeof(bar) =",bv.types["bar"].width)

# Add and remove a field for `bar` to be correctly sized without declaring any fields of its own
bar_builder.append(binaryninja.Type.int(8), "field3")
bar_builder.remove(0)
bar_builder.width -= 8 # required to shrink the size otherwise padding is left
print("bar_builder.width =", bar_builder.width)
bv.define_user_type("bar", bar_builder.immutable_copy())
print("sizeof(bar) =",bv.types["bar"].width)

# Creating the type `baz` that inherits from `bar` and declares a field now works correctly
baz_builder = binaryninja.StructureBuilder.create()
baz_builder.base_structures = [ binaryninja.BaseStructure(bv.types["bar"], 0) ]
baz_builder.append(binaryninja.Type.int(8), "field3")
print("baz_builder.width =", baz_builder.width)
bv.define_user_type("baz", baz_builder.immutable_copy())
print("sizeof(baz) =",bv.types["baz"].width)

The 3 types; foo, bar and baz should now exist in the user types list with the following definitions:

struct foo
{
    int64_t field1;
    int64_t field2;
};

struct __base(foo, 0) bar
{
    __inherited int64_t foo::field1;
    __inherited int64_t foo::field2;
};

struct __base(bar, 0) baz
{
    __inherited int64_t foo::field1;
    __inherited int64_t foo::field2;
    int64_t field3;
};

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Start by reproducing the issue with the Create Types from C Source window and the Python StructureBuilder example in the report. Trace how base_structures affect StructureBuilder.width for an empty derived class, then verify that bar retains foo's inherited fields and baz places field3 after them. Done means the reported expected definitions and sizes are produced without the workaround.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
cpp, python
Ambito
reverse-engineering
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.