2023.01
eShard acquired Tetrane in July 2022! You can read more about this on the blog: eShard takes a step into Reverse Engineering by acquiring Tetrane.
Highlights
Summary
esReven version 2023.01 is the first version released by eShard, and is a major step forward in the life of the product:
- esReven is the new product name for Reven. The release versioning scheme will now be "Year.Month" like 2023.01, as opposed to 2.11.0 which was the last version of Reven by Tetrane.
- The Reven engine is now integrated in eShard's platform to become esReven, bringing JupyterLab & other features.
- Following eShard's strengths and practices, two knowledge-base modules now come bundled with esReven: "Getting Started" and "Vulnerability Analysis" - this is a first step in providing more knowledge & practical know-how to our users about the product, its usage and use cases.
- To even better support the JupyterLab environment, and following the trend set by the previous 2.11.0 version, multiple API objects have been introduced: you can now easily parse the call tree, or display the framebuffer, among others.
In more details
- Reven is now esReven: this is of course the most visible element. Reven has been integrated in the more generic eShard framework, which is now the top-most UI and includes the existing Project Manager. It also comes along with JupyterLab, which is the evolution of the regular Jupyter we know and love. Moreover, we will be able to better handle separate users from now on, and this is already working for the JupyterLab side of things. This is only the beginning, expect more work on this side in the future!
- Knowledge-base modules: eShard has a history of providing both the platform for studying targets (hardware & software) and the knowledge & experience for doing so. The latter does not only come from selling services but more importantly from knowledge modules. Each module is a set of interactive JupyterLab notebooks produced by experts, that provides theoretical knowledge and practical know-how about a subject, such as a specific attack or vulnerability. This approach has been very successful, and we are working on bringing it to esReven as well. The result of this work so far is a set of two modules that we decided to include with this version of esReven for free: the "Getting started" module to on-board new (or existing but rusty) users, and a "Vulnerability Analysis" module, which takes the user on the ride of recording and analyzing a CVE in Google Chrome. We want to continue on this path: expect more work on this side as well in the future!
- New APIs: Following the trend set by the previous 2.11.0 version, we kept on improving working from the Python API, making algorithms easier to build, and in general making the experience of exploring a trace from Python smoother. For this, we strive to make APIs that provide access to high level objects while keeping the complexity as low as possible.
- We improved the
Type
API that enable parsing typed data in the target memory. - We added access to the "stack event" information, which among other allows parsing the tree of calls from a function, or in general focusing on the code of a function to answer questions such as "What are the memory access made by this function".
- We gave a simple access to the framebuffer, to make illustrating notebooks more convenient.
- We improved the
A focus on the Python API
Type
API
First of all, we have improved our Type
API, which allows a user to parse high-level data in the target's memory such as typed structures, arrays, etc.:
- It is now easier to browse the content of instances of typed object in memory:
- using
StructInstance.fields()
we can now parse all members of a struct without having to fetch member's names first. ArrayInstance
now reliably containsPointerInstance
objects, while they would sometimes contain addresses instead.
- using
- There is now a dedicated
__format__
method forStructInstance
objects. Supported by properformat
methods in all objects that can be contained (FieldInstance
,ArrayInstance
, etc.), this allows for very straightforward formatting:- Here is an example of the default formatting:
>>> print(f"{instance}") struct _RTL_AVL_TREE /* 0x8 */ { /* 0x0 */ Root : _RTL_BALANCED_NODE* = (...)* @ds:0xffffe00013174e80, }
- Alternatively, you can choose to display the inner structures, up to a certain depth (default is 1):
>>> print(f"{instance:max_depth=2}") struct _RTL_AVL_TREE /* 0x8 */ { /* 0x0 */ Root : _RTL_BALANCED_NODE* = (struct _RTL_BALANCED_NODE /* 0x18 */ { /* 0x0 */ Children : [_RTL_BALANCED_NODE*; 2] = [...], /* 0x0 */ Left : _RTL_BALANCED_NODE* = (...)* @ds:0xffffe000138fa510, /* 0x8 */ Right : _RTL_BALANCED_NODE* = (...)* @ds:0xffffe00012818bc0, /* 0x10 */ Red : U8[0..1] = 0, /* 0x10 */ Balance : U8[0..2] = 0, /* 0x10 */ ParentValue : U64 = 0, })* @ds:0xffffe00013174e80, }
- Here is an example of the default formatting:
- If more than one type share an identical name, we can now get them all by using the
Binary.types
method. Notably, this allows fetching the proper_KPCR
structure type on Windows 11.
Stack Events and Call Tree
We have introduced the "Stack Events" data into the Python API under the reven2.stack
objects (accessible through ctx.ossi.stack
). In this API, a Frame represents the time span one function call lives in, and what it does.
stack.frame
gives access to the currentStackFrame
, whilestack.frames()
still gives all parent frames as was the case in previous version.- Each frame's exact time boundaries are explicit, and allow not only a display that is on-par with Axion, but precise control for your algorithms:
first_context
andlast_context
give you the natural start and stop of the frame. However, there are plenty of edge-cases possible, so we also added:first_executed_context
gives the first time a frame appears if it has started before the beginning of the tracefunction_start
andfunction_location
gives the resolved function start (in time) and location (in memory) (taking everything into account, such as trampolines or out-of-trace starts).
- You can easily parse all calls made in a Frame with
children
, or access the Frame's parents withancestors
. Using this API recursively is possible, so you can imagine displaying a call tree as such:>>> def print_children(ctx: reven2.trace.Context) -> None: ... active_frame: reven2.stack.StackFrame = ctx.stack.frame() ... print(active_frame) ... for child in active_frame.children(): ... print(f"|- {child}") ... >>> print_children(server.trace.first_context + 200_000) #199862 - usbhub!UsbhDecHubBusy |- #199886 - ntoskrnl!KeWaitForSingleObject |- #200032 - ntoskrnl!ExFreePoolWithTag |- #200239 - ntoskrnl!KiAcquireKobjectLockSafe |- #200269 - ntoskrnl!KiExitDispatcher
- Moreover, we need to handle the fact that there can be many different situations when analyzing a function: we might be interested in its own code only, skipping its children. Or we might want the children's code, up to certain binaries. Finally, we often want to skip external code such as kernel code, or of other processes that might get scheduled between
first_context
andlast_context
. In order to handle all these various situations easily, we introduced another call:descendant_events
. It yields all the relevant events from a frame:FrameStart
: a child frame starts. From this point, I can check the child's symbol, binary, etc.FrameEnd
: a child frame end, allowing me to resume my analysis if I skipped over the child call.StackLeave
: I am entering a portion of the trace where I'm no longer executing from on this particular Stack, for instance I've entered kernel code, or another process. I should really ignore everything that is going on.StackEnter
: I am back into my stack, so into my frame or a child.
- We also know that the complexity of this call tree can rapidly grow out of control, so for these situations we ensure the iterator from
descendant_events
could skip entire frames withskip_children()
. For example, you can control the display depth as such:>>> def print_descendants(ctx: reven2.trace.Context) -> None: ... depth = 0 ... it = ctx.stack.frame().descendant_events() ... for event in it: ... if isinstance(event, reven2.stack.FrameStart): ... # [...] Do something ... depth += 1 ... if depth >= 3: ... # Skip any child this call may have and skip to its return ... it.skip_children() ... elif isinstance(event, reven2.stack.FrameEnd): ... depth -= 1
Framebuffer API
There is now a framebuffer entry point in the Context
object! Use it to get a straight PIL.Image
object representing the VM screen at this point in time. For example, fetch the screen at the start of the trace with: server.trace.first_context.framebuffer.image()
. This is also very convenient to use in notebooks, as you can for instance control the resulting image's size with resize((width, height))
.
OSSI API
You can now access information about the OS running in the trace from server.ossi.os()
. For instance:
>>> print(server.ossi.os())
Windows x64 10.0.17763 (Windows 10)
Workflow API
Finally, there have been improvements on the side of managing scenarios and archives:
- You can now open a scenario by uuid or by name, using
ProjectManager.get_server_by
and specifying eithername
oruuid
argument. - You can now upload a scenario archive from the API with
ProjectManager.upload_scenario
Changes & Improvements
Installation
- esReven's package now uses Docker images & Docker Compose. Migrating any previous type of installation and its data is fully supported. See the note about upgrading for more details. The Docker approach unifies the installation procedure, allows to install esReven on various Linux distributions and versions, allows the use of standard tools to operate the server.
esReven web interface
- The eShard's web framework is now used for the top-most user interface. It provides access to the components of esReven:
- The Project Manager, which you may already know from Reven.
- A Knowledge Base, served by a JupyterLab instance, that replaces Reven's Jupyter instance and contains several KB modules.
- The offline documentation - it has now the same form as the online documentation and is fully searchable.
- The Web Interface supports multiple distinct users.
- JupyterLab spaces are individual to each user. There is also a shared space that users can use to exchange notebooks and data.
- On the other hand, all users share a single Reven instance, hence also Reven's VMs, projects, etc.
esReven Project Manager
- The Project Manager is now a part of the more general esReven Web Interface.
- You can now upload a scenario archive directly from the "Scenario" tab.
- The Reven Server ports when opening a scenario will now use a port within a configurable range, to make it easier to route them from the host to the correct docker instance. The default range is 14000 to 14099.
- The same is true for debugger-assisted recording incoming ports (aka VMI). The default range is 14100 to 14199.
- Creating VBox VMs and recording VBox scenarios is no longer possible: this capability's perimeter was very specific, and was seldom used - it was time for us to let it go.
Reven engine
- The OSSI module can now detect the Windows 11 OS.
- The Cap'n Proto library has been bumped to version 0.10.3.
Python API
- Compatibility note:
reven2.stack.StackFrame.first_context
may return a different context in the case of a frame of typeUnknown
. Previously it would return the first context where the frame was directly executed (not one of its children call), now it returns the first context where this frame or one of its child frames is being executed. The old behavior is now provided byreven2.stack.StackFrame.first_executed_context
. - Compatibility note:
reven2.stack.StackFrame.type
andreven2.stack.StackFrame.creation_transition
are now deprecated. Use thereven2.stack.StackFrame.start_event
to retrieve this information. - The
reven2.stack
module has been extended to support thestack event API
:- A
reven2.stack.StackFrame
now gives access to its call subtree and super tree viareven2.stack.StackFrame.ancestors
,reven2.stack.StackFrame.children
,reven2.stack.StackFrame.reversed_children
andreven2.stack.StackFrame.parent
. - A
reven2.stack.StackFrame
now gives access to the location and transition of the function executed in that frame viareven2.stack.StackFrame.function_location
andreven2.stack.StackFrame.function_start
. - A
reven2.stack.StackFrame
now gives access to the relevant event in the stack events, such as itsreven2.stack.StackFrame.start_event
,reven2.stack.StackFrame.end_event
andreven2.stack.StackFrame.descendant_events
(that include stack change events). To support this, thereven2.stack.EventsIterator
,reven2.stack.FrameStart
,reven2.stack.FrameEnd
,reven2.stack.FrameStartType
,reven2.stack.FrameEndType
,reven2.stack.StackEnter
andreven2.stack.StackLeave
,reven2.stack.StackSpace
types have been added. - Added
reven2.stack.StackFrame.last_context
that provides the last context where a frame exists. - Added
reven2.stack.Stack.frame
as a shortcut fornext(stack.frames())
, giving more convenient access to the current frame. - Added
__eq__
and__hash__
methods toreven2.stack.Stack
andreven2.stack.StackFrame
. - For more information, please refer to the updated documentation of the
reven2.stack
module.
- A
- Added
reven2.ossi.Ossi.os
to get the OS family, version, kernel version and architecture of the current scenario. reven2.types.ArrayInstance
s whose element type is a pointer now always contains a list ofreven2.types.PointerInstance
s. Previously, it would sometimes contain a list of addresses.- The instance types in
reven2.types
now have a string representation for printing. - Added
reven2.types.StructInstance.format
,reven2.types.PointerInstance.format
andreven2.types.ArrayInstance.format
. - Added
reven2.types.StructInstance.__format__
,reven2.types.PointerInstance.__format__
andreven2.types.ArrayInstance.__format__
. - Added
reven2.util.parse_colon_separated_key_values
. - Added
reven2.types.StructInstance.fields
to iterate on the field and bitfield instances of a struct instance. - Iterating on fields with
reven2.types.Struct.fields
now always resolve the types of the field. Previously, the fields were resolved only when callingreven2.types.Struct.field
on a specific field. - Fixed a
RecursionError
when attempting to resolving a type containing an array of pointers to that type. - Add
reven2.ossi.Binary.types
to get named types from a debug object for Windows scenarios in case there are multiple types with the same name. - Fix a bug in
preview.windows
when the kernel had twice the structure_KPCR
in its PDB. - Add
reven2.framebuffer.Framebuffer
API to get the current framebuffer as an image. - Add
reven2.trace.Context.framebuffer
to get thereven2.framebuffer.Framebuffer
at a context. - Add
reven2.ossi.Location.__eq__
,reven2.ossi.Location.__ne__
andreven2.ossi.Location.__hash__
. - Add
preview.project_manager.ProjectManager.upload_scenario
to upload a scenario archive. - Add
ProjectManager.get_server_by
scenario name or uuid andProjectManager.close_server
inpreview.project_manager
. reven2.trace.Transition
andreven2.address
are not displayed as a clickable link in Jupyter anymore.
Fixed issues
Reven engine
- The ASM-stub auto record now properly resumes the VM when the recording stops. Moreover, errors encountered by the ASM-stub or the Binary auto-record that would stop the recording will now properly resume the VM as well.
- Trying to generate the OSSI Range resource does not crash when OSSI are not available for the recording, which could be the case of exotic traces such as recording GRUB.
- The Memory History replayer now will not crash when registering a 0-byte access, but instead properly report an error.