Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ follow_imports = silent
ignore_missing_imports = True
show_column_numbers = True
pretty = True
strict = True
strict = True
mypy_path=$MYPY_CONFIG_FILE_DIR/typings
69 changes: 69 additions & 0 deletions Go-features-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# LLEF Experimental Go Features Readme

LLEF now has experimental support for stripped Go binaries. This document contains examples of LLEF's Go features, including screenshots of what LLEF's analysis output displays compared with previous versions of LLEF. These features are tested across a range of Go versions since 1.7, which was released in 2016.

## Resolving and displaying Go types

Go binaries store metadata on complex data types which are used in the program, even when the binary has been stripped. From this metadata, field names and types can be recovered for `struct`, `map`, `array` and `slice` types. As an example, consider the following `struct` definition in a Go source file:

```go
type Tool struct {
Name string `json:"name"`
Developer string `json:"developer"`
Score int `json:"score"`
}
func demo() {
tools := []Tool{
{Name: "LLEF", Developer: "Foundry Zero", Score: 10},
{Name: "Binder Trace", Developer: "Foundry Zero", Score: 10},
}
jsonData, _ := json.Marshal(tools)
fmt.Println(string(jsonData))
}
```

In the above program listing, `tools` is an `array` type of `Tool` objects, which is passed into `json.Marshal`. The definition of `json.Marshal` includes an `any` type for the input argument. To keep track of the input data type, the Go compiler loads a pointer to read-only metadata for the `[]Tool` data type at runtime, followed by a pointer to the raw object data in memory.

In previous versions of LLEF, when a breakpoint is set on the call to `json.Marshal`, the register view shows the following:

![Previous LLEF analysis output.](./assets/go-examples/json-mashal-old.png)

With Go features enabled, LLEF can now identify the pointer `0x4c3300` points to a data type definition for `[]Tool`, and used that definition to display the contents of the data pointer in `ebx`:

![New LLEF analysis output.](./assets/go-examples/json-marshal-new.png)

A new command `go get-type` has been added to find Go data type definitions present within the binary. When an argument is given to the command, LLEF will to match the name of the type to the definition present in the Go binary. If no match can be found, LLEF will display similar entries:

![Go get-type command.](./assets/go-examples/go-get-type.png)

When no arguments are given, all data types are displayed.

If a data type pointer is not passed into a called function alongside the raw data type pointer, LLEF will not be able to determine the matching data type. In this case, an analyst can manually use the command `go unpack-type` to attempt to display a data pointer using a given data type definition.

![Manually unpacking types.](./assets/go-examples/go-unpack-type-command.png)

## Resolving Go Function Names

Go binaries store function symbols which are used during exception handling, even for stripped binaries. LLEF can now recover these function symbols and display them:

![Display of function symbols in listing view.](./assets/go-examples/function-name-recovery.png)

A new command `go find-func` has been added to search through function symbols, which is especially useful for finding where to set breakpoints. When no argument is given, all function symbols are displayed.

![Finding functions by name.](./assets/go-examples/go-find-func-command.png)

## Go call stack unwinding

LLEF can now display the Go call stack on x86 and x86_64 architectures. This is displayed in the listing view, but can also be displayed with the command `go backtrace`.

![Go call stack display.](./assets/go-examples/go-backtrace-command.png)

## Before and After

### Before

![GIF Before](./assets/go-examples/go-mode-disabled.gif)

### After

![GIF After](./assets/go-examples/go-mode-enabled.gif)
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ Settings are stored in a file `.llef` located in your home directory formatted a
| rebase_addresses | Boolean | Enable/disable address rebase output |
| rebase_offset | Int | Set the rebase offset (default 0x100000) |
| show_all_registers | Boolean | Enable/disable extended register output |
| enable_darwin_heap_scan | Boolean | Enable/disable more accurate heap scanning for Darwin-based platforms. Uses the Darwin malloc introspection API, executing code in the address space of the target application using LLDB's evaluation engine. |
| enable_darwin_heap_scan | Boolean | Enable/disable more accurate heap scanning for Darwin-based platforms. Uses the Darwin malloc introspection API, executing code in the address space of the target application using LLDB's evaluation engine |
| max_trace_length | Int | Set the maximum length of the call stack backtrace to display |
| stack_view_size | Int | Set the number of entries in the stack read to display |
| max_disassembly_length | Int | Set the maximum number of instructions to disassemble and display around the current PC |
| go_support_level | String | Control Golang-specific analysis. `disable` / `auto` (default) / `force`. For performance reasons, Go support in Windows binaries requires `force`. |
| go_confidence_threshold | String | Set the confidence threshold (`low` / `medium` / `high`) for Go objects to be shown in the context view |

#### llefcolorsettings
Allows setting LLEF GUI colors:
Expand Down Expand Up @@ -111,6 +116,7 @@ Supported colors: BLUE, GREEN, YELLOW, RED, PINK, CYAN, GREY
| dereferenced_register_color |
| frame_argument_name_color |
| read_memory_address_color |
| go_type_color |

#### Hexdump
View memory contents with:
Expand Down Expand Up @@ -179,6 +185,36 @@ aabacadaea
[+] Found in $8 at index 0 (little endian)
```

#### (Go) Unpack Type

```
(lldb) go unpack-type 0xc000130000 []main.Country
[{Name:'Japan' Capital:'Tokyo' Continent:'Asia'} {Name:'Germany' Capital:'Berlin' Continent:'Europe'}]
(lldb) go unpack-type 0xc000130000 []main.Country --depth 1
[0xc000142000.. 0xc000142030..]
(lldb) go unpack-type 0xc000142000 main.Country
{Name:'Japan' Capital:'Tokyo' Continent:'Asia'}
(lldb) go unpack-type 0xc000142000 [6]uintptr
[0xc000114140 0x5 0xc000114145 0x5 0xc00011414c 0x4]
```

#### (Go) Find Function
```
(lldb) go find-func main.main
0x55c6894c0280 - main.main (file address = 0x4c0280)
(lldb) go find-func 0x55c689454a3a
0x55c689454a20 - runtime.(*moduledata).textAddr (file address = 0x454a20)
```

#### (Go) Get Type
```
(lldb) go get-type json.mapEncoder --depth 3
json.mapEncoder = struct { elemEnc func(*json.encodeState, struct { typ_ *abi.Type; ptr unsafe.Pointer; flag uintptr }, struct { quoted bool; escapeHTML bool }) }
Size in bytes: 0x8
(lldb) go get-type json.encodeState --depth 1
json.encodeState = struct { Buffer bytes.Buffer; ptrLevel uint; ptrSeen map[interface {}]struct {} }
Size in bytes: 0x38
```

### Breakpoint hook
This is automatic and prints all the currently implemented information at a break point.
Expand Down
13 changes: 8 additions & 5 deletions arch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Arch module __init__.py"""

from typing import Type

from lldb import SBTarget

from arch.aarch64 import Aarch64
Expand All @@ -12,7 +10,12 @@
from arch.x86_64 import X86_64
from common.constants import MSG_TYPE
from common.output_util import print_message
from common.util import extract_arch_from_triple


def extract_arch_from_triple(triple: str) -> str:
"""Extracts the architecture from triple string."""
return triple.split("-")[0]


# macOS devices running arm chips identify as arm64.
# aarch64 and arm64 backends have been merged, so alias arm64 to aarch64.
Expand All @@ -29,13 +32,13 @@
}


def get_arch(target: SBTarget) -> Type[BaseArch]:
def get_arch(target: SBTarget) -> type[BaseArch]:
"""Get the architecture of a given target"""
arch = extract_arch_from_triple(target.triple)
return get_arch_from_str(arch)


def get_arch_from_str(arch: str) -> Type[BaseArch]:
def get_arch_from_str(arch: str) -> type[BaseArch]:
"""Get the architecture class from string"""
if arch in supported_arch:
return supported_arch[arch]
Expand Down
2 changes: 2 additions & 0 deletions arch/aarch64.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Aarch64(BaseArch):

bits = 64

max_instr_size = 4

gpr_registers = [
"x0",
"x1",
Expand Down
2 changes: 2 additions & 0 deletions arch/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Arm(BaseArch):

bits = 32

max_instr_size = 4

gpr_registers = [
"r0",
"r1",
Expand Down
12 changes: 8 additions & 4 deletions arch/base_arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, List


@dataclass
class FlagRegister:
"""FlagRegister dataclass to store register name / bitmask associations"""

name: str
bit_masks: Dict[str, int]
bit_masks: dict[str, int]


class BaseArch(ABC):
Expand All @@ -23,7 +22,12 @@ def bits(self) -> int:

@property
@abstractmethod
def gpr_registers(self) -> List[str]:
def max_instr_size(self) -> int:
"""Max instruction size (bytes) property"""

@property
@abstractmethod
def gpr_registers(self) -> list[str]:
"""GPR register property"""

@property
Expand All @@ -33,5 +37,5 @@ def gpr_key(self) -> str:

@property
@abstractmethod
def flag_registers(self) -> List[FlagRegister]:
def flag_registers(self) -> list[FlagRegister]:
"""List of flag registers with associated bit masks"""
2 changes: 2 additions & 0 deletions arch/i386.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class I386(BaseArch):

bits = 32

max_instr_size = 15

gpr_registers = [
"eax",
"ebx",
Expand Down
2 changes: 2 additions & 0 deletions arch/ppc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class PPC(BaseArch):

bits = 32

max_instr_size = 4

gpr_registers = [
"r0",
"r1",
Expand Down
10 changes: 6 additions & 4 deletions arch/x86_64.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ class X86_64(BaseArch):

bits = 64

max_instr_size = 15

gpr_registers = [
"rax",
"rbx",
"rcx",
"rdx",
"rsp",
"rbp",
"rsi",
"rdi",
"rip",
"rsi",
"r8",
"r9",
"r10",
Expand All @@ -28,6 +27,9 @@ class X86_64(BaseArch):
"r13",
"r14",
"r15",
"rsp",
"rbp",
"rip",
]

gpr_key = "general purpose"
Expand Down
Binary file added assets/go-examples/function-name-recovery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/go-backtrace-command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/go-find-func-command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/go-get-type.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/go-mode-disabled.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/go-mode-enabled.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/go-unpack-type-command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/json-marshal-new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/go-examples/json-mashal-old.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions commands/base_command.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Base command definition."""

from abc import ABC, abstractmethod
from typing import Type
from typing import Any, Union

from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext

Expand All @@ -11,15 +11,15 @@
class BaseCommand(ABC):
"""An abstract base class for all commands."""

alias_set = {}
alias_set: dict[Any, Any] = {}

@abstractmethod
def __init__(self) -> None:
pass

@property
@abstractmethod
def container(self) -> Type[BaseContainer]:
def container(self) -> Union[type[BaseContainer], None]:
"""Container property."""

@property
Expand Down
12 changes: 8 additions & 4 deletions commands/base_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import argparse
import shlex
from abc import ABC, abstractmethod
from typing import Any, Dict
from typing import Any, Union

from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext

from commands.base_command import BaseCommand
from common.base_settings import BaseLLEFSettings
from common.output_util import output_line


Expand All @@ -16,9 +17,9 @@ class BaseSettingsCommand(BaseCommand, ABC):

program: str = ""
container = None
settings = None
settings: Union[BaseLLEFSettings, None] = None

def __init__(self, debugger: SBDebugger, __: Dict[Any, Any]) -> None:
def __init__(self, debugger: SBDebugger, __: dict[Any, Any]) -> None:
super().__init__()
self.parser = self.get_command_parser()

Expand Down Expand Up @@ -51,8 +52,11 @@ def __call__(
output_line(self.__class__.get_long_help())
return

if self.settings is None:
raise AttributeError("Class not properly initialised: self.settings is None")

if args.action == "list":
self.settings.list()
self.settings.list_settings()
elif args.action == "save":
self.settings.save()
elif args.action == "reload":
Expand Down
Loading