class documentation

class Trace:

View In Hierarchy

Entry point object for information related to a trace, mostly transitions & contexts.

The trace is represented as a series of Transitions objects (which are CPU instruction most of the time) that take the virtual machine from one state (Context object "before") to the next (Context object "after").

Warnings

This object is not meant to be constructed directly. Use RevenServer.trace instead.

Examples

>>> # From a reven_server
>>> trace = reven_server.trace
>>> print("Trace is {} transitions long".format(trace.transition_count()))
>>> print("From {} to {} via {}".format(trace.context_before(0), trace.context_after(0), trace.transition(0)))
>>> print(trace.transition(trace.transition_count() - 1))
Method __init__ Undocumented
Method __repr__ Undocumented
Method context_after Retrieve the context after a transition from the latter's unique id.
Method context_before Retrieve the context before a transition from the latter's unique id.
Method contexts Get a generator over the contexts in the trace.
Method filter Get all context ranges that are associated to the given processes and given ring policy
Method memory_accesses Get a generator over the previous/next memory accesses to a range of addresses defined by (address, size).
Method strings Get a generator over the strings matching the pattern
Method transition Retrieve a transition from its unique id.
Method transitions Get a generator over the transitions in the trace.
Property first_context Property: First context in this trace.
Property first_transition Property: First transition in this trace.
Property last_context Property: Last context in this trace.
Property last_transition Property: Last transition in this trace.
Property search Property: Get a reven2.search.Search instance, which is the entry point to find interesting points in the trace.
Property transition_count Property: Count of transitions in this trace.
Method _context Undocumented
Instance Variable _data_source Undocumented
Instance Variable _ossi_data_source Undocumented
Property _rvn Undocumented
def __init__(self, _rvn, _ossi_data_source):

Undocumented

Parameters
_rvn:_reven_api.reven_connectionUndocumented
_ossi_data_source:_ossi._DataSourceUndocumented
def __repr__(self):

Undocumented

Returns
strUndocumented
def context_after(self, transition_id):

Retrieve the context after a transition from the latter's unique id.

Information

Parameters
transition_id:intUndocumented
Returns
ContextUndocumented
Raises
IndexErrorif the unique id does not belong to the trace
def context_before(self, transition_id):

Retrieve the context before a transition from the latter's unique id.

Information

Parameters
transition_id:intUndocumented
Returns
ContextUndocumented
Raises
IndexErrorif the unique id does not belong to the trace.
def contexts(self, start=None, stop=None, step=1):

Get a generator over the contexts in the trace.

Examples

Iterates on contexts from the beginning to the end of the trace.

>>> for context in trace.contexts():
...     print(context)
Context before #0
Context before #1
Context before #2
Context before #3
Context before #4
Context before #5
Context before #6
...

Iterates on contexts from the end to the beginning of the trace.

>>> for context in trace.contexts(step=-1):
...     print(context)
Context after #2847570054
Context before #2847570054
Context before #2847570053
Context before #2847570052
Context before #2847570051
Context before #2847570050
...

Iterates on the 5 first contexts in increasing order.

>>> stop = trace.context_before(5)
>>> for context in trace.context(stop=stop):
...     print(context)
Context before #0
Context before #1
Context before #2
Context before #3
Context before #4

Iterates on 10 contexts somewhere in the trace, in increasing order, and 2 by 2.

>>> start = trace.context_before(1000)
>>> stop = start + 10
>>> for context in trace.context(start, stop, 2):
...     print(context)
Context before #1000
Context before #1002
Context before #1004
Context before #1006
Context before #1008

Iterates on the 5 last contexts in decreasing order.

>>> stop = trace.context_after(trace.transition_count - 5)
>>> for context in trace.contexts(stop=stop, step=-1):
...     print(context)
Context after #2847570054
Context before #2847570054
Context before #2847570053
Context before #2847570052
Context before #2847570051

Information

Parameters
start:_Optional[Context]A Context at which to start the generation. If start is None, then: * start at the beginning of the trace if step > 0. * start at the end of the trace if step < 0.
stop:_Optional[Context]A Context at which to stop the generation. The stop is excluded from the generation. If stop is None, then: * stop at the end of the trace if step > 0. * stop at the beginning of the trace if step < 0.
step:intAn int which indicates the increment (or decrement) between generated Contexts. If 'step' > 0, then contexts are yielded in increasing order. If 'step' < 0, then contexts are yielded in decreasing order. Default is set to 1.
Returns
_Iterator[Context]Undocumented
Raises
TypeErrorif any argument is of the wrong type.
ValueErrorif one of the following cases: * step == 0. * step > 0 and start > stop. * step < 0 and start < stop.
def filter(self, processes=None, ring_policy=_filter.RingPolicy.All, from_context=None, to_context=None, is_forward=True):

Get all context ranges that are associated to the given processes and given ring policy

A valid list of `ossi.Process` object must be given. To get one, use the `Ossi.executed_processes` method.

Warnings

Depends on the ossi range resource. If unavailable, this function will raise an error.

Examples

>>> # iterate on ranges in ring 0
>>> for range in reven_server.trace.filter(ring_policy=reven2.filter.RingPolicy.R0Only):
...     print(range)
[Context before #853196, Context before #857514]
[Context before #5113265, Context before #5118308]
[Context before #8202545, Context before #8208679]
[Context before #16222616, Context before #16229012]
>>> # iterate on any contexts associated to process `foo.exe` with pid 1234
>>> process = next(reven_server.ossi.executed_processes('foo.exe', 1234))
>>> for range in reven_server.trace.filter([process]):
...     for context in range:
...         print(context)
Context before #1000
Context before #1001
Context before #1002
Context before #1003
Context before #1004
Context before #1005
...

Information

Parameters
processes:_Optional[_Iterable[_ossi.Process]]the list of processes to filter. If None, all processes will be filtered.
ring_policy:_filter.RingPolicythe rings to filter. Must be a filter.RingPolicy value. By default, all rings will be filtered.
from_context:_Optional[Context]the first included context where the filter starts. Must be a trace.Context. If None, start at the first context of trace.
to_context:_Optional[Context]the first excluded context where the filter stops. Must be a trace.Context. If None, stop at the last context of the trace (included). `from_context` must be strictly lower than `to_context`.
is_forward:boolwhether the results are returned in the forward or backward order. By default, the forward order is used.
Returns
_Union[_Iterator[ContextRange], _Iterator[BackwardContextRange]]A generator of trace.ContextRange if `is_forward` equal `True`. Otherwise, a generator of trace.BackwardContextRange.
Raises
ValueErrorif `to_context` is lower or equal to `from_context`.
RuntimeErrorif the ossi ranges ressource is unavailable.
def memory_accesses(self, address=None, size=None, from_transition=None, to_transition=None, is_forward=True, operation=None, fetch_count=1000, address_range=None):

Get a generator over the previous/next memory accesses to a range of addresses defined by (address, size).

Performance

Returning a generator rather than a list allow to lazily compute the requested results. Calling next on the generator to get the next individual result should always be fast. Forcing eager computations of the results by converting the iterator to a list might be slow depending on the number of results.

Examples

>>> # Getting the first 10 accesses in the entire trace
>>> # (should always be fast):
>>> for (_, access) in zip(range(0, 10), trace.memory_accesses()):
...     print(access)
[#0 mov qword ptr [rsp+0x10], rdx]Write access at @phy:0x668f4ea8 (virtual address: lin:0x14fea8) of size 8
[#1 mov dword ptr [rsp+0x8], ecx]Write access at @phy:0x668f4ea0 (virtual address: lin:0x14fea0) of size 4
[#3 mov rax, qword ptr [rip+0x3eb1]]Read access at @phy:0x70b8c068 (virtual address: lin:0x140005068) of size 8
[#5 mov qword ptr [rsp+0x240], rax]Write access at @phy:0x668f4e80 (virtual address: lin:0x14fe80) of size 8
[#6 cmp dword ptr [rsp+0x260], 0x2]Read access at @phy:0x668f4ea0 (virtual address: lin:0x14fea0) of size 4
[#10 mov rcx, qword ptr [rsp+0x268]]Read access at @phy:0x668f4ea8 (virtual address: lin:0x14fea8) of size 8
[#11 mov rcx, qword ptr [rcx+rax*1]]Read access at @phy:0x5cf92028 (virtual address: lin:0x503028) of size 8
[#12 call qword ptr [rip+0x1ebe]]Read access at @phy:0x681210b8 (virtual address: lin:0x1400030b8) of size 8
[#12 call qword ptr [rip+0x1ebe]]Write access at @phy:0x668f4c38 (virtual address: lin:0x14fc38) of size 8
[#14 and qword ptr [rsp+0x28], 0x0]Read access at @phy:0x668f4c28 (virtual address: lin:0x14fc28) of size 8
>>> # Getting the accesses on the transition range [1000, 1010[
>>> # (should always be fast):
>>> for access in trace.memory_accesses(from_transition=trace.transition(1000),
...                                     to_transition=trace.transition(1010)):
...     print(access)
[#1005 mov qword ptr [rsp+0x40], r14]Write access at @phy:0x6645a9b0
        (virtual address: lin:0xfffffe0ff31db9b0) of size 8
[#1007 mov qword ptr [rsp+0x98], r14]Write access at @phy:0x6645aa08
        (virtual address: lin:0xfffffe0ff31dba08) of size 8
[#1008 or dword ptr [rsp+0x90], 0x2]Read access at @phy:0x6645aa00
        (virtual address: lin:0xfffffe0ff31dba00) of size 4
[#1008 or dword ptr [rsp+0x90], 0x2]Write access at @phy:0x6645aa00
        (virtual address: lin:0xfffffe0ff31dba00) of size 4
>>> # Getting the first 10 accesses in the trace for range [0xffff88007fc03000, 0xffff88007fc04000)
>>> # (should always be fast):
>>> for (_, access) in zip(range(0, 10), trace.memory_accesses(0xffff88007fc03000, 4096,
... trace.transition(0))):
...     print(access)
[#39 call 0xffffffff81611fe0 ($+0x165133)]Write access at @phy:0x7fc03ec8 (virtual address:
        lin:0xffff88007fc03ec8) of size 8
[#41 call qword ptr [0xffffffff81c24448]]Write access at @phy:0x7fc03ec0 (virtual address:
        lin:0xffff88007fc03ec0) of size 8
[#42 push rdx]Write access at @phy:0x7fc03eb8 (virtual address: lin:0xffff88007fc03eb8) of size 8
[#48 pop rdx]Read access at @phy:0x7fc03eb8 (virtual address: lin:0xffff88007fc03eb8) of size 8
[#49 ret ]Read access at @phy:0x7fc03ec0 (virtual address: lin:0xffff88007fc03ec0) of size 8
[#51 push rdi]Write access at @phy:0x7fc03ec0 (virtual address: lin:0xffff88007fc03ec0) of size 8
[#52 popfq ]Read access at @phy:0x7fc03ec0 (virtual address: lin:0xffff88007fc03ec0) of size 8
[#54 ret ]Read access at @phy:0x7fc03ec8 (virtual address: lin:0xffff88007fc03ec8) of size 8
[#60 call 0xffffffff814abe30 ($-0x108f)]Write access at @phy:0x7fc03ec8 (virtual address:
        lin:0xffff88007fc03ec8) of size 8
[#62 push r14]Write access at @phy:0x7fc03ec0 (virtual address: lin:0xffff88007fc03ec0) of size 8
>>> # Getting the number of accesses in the trace for range [0xffff88007fc03000, 0xffff88007fc04000)
>>> # (can be slow if there are numerous accesses):
>>> len(list(trace.memory_accesses(0xffff88007fc03000, 4096, trace.transition(0))))
28413
>>> # Getting the last access in the trace to phy:0x36f05080 (will always be fast):
>>> last = trace.transition(trace.transition_count - 1)
>>> address = reven2.address.PhysicalAddress(0x36f05080)
>>> next(trace.memory_accesses(address, 1, last, is_forward=False))
MemoryAccess(transition=Transition(id=3515211), physical_address=PhysicalAddress(offset=0x36f05080), size=8,
        operation=MemoryAccessOperation.Write, virtual_address=None)

Information

Parameters
address:_Optional[_address.Address]First address of the searched range of addresses. Can be a class from the reven2.address module, or an integer. If an integer x, it will be interpreted as ds:x. If a virtual address, then it must be mapped (possibly to non-contiguous physical pages) at the context just before/after (depending on the direction) the from_transition. If None, return accesses regardless of their address.
size:_Optional[int]Size of the searched range of addresses. If None and address not None, the address's default formatter size is used.
from_transition:_Optional[Transition]Transition at which to start the search. The from_transition is included in the search results If None, start from the beginning/end of the trace in forward/backward direction, respectively. If specified, must be lower than/greater than to_transition, depending on the direction
to_transition:_Optional[Transition]Transition at which to stop the search. The to_transition is excluded from the search results If None, stop to the end/beginning of the trace in forward/backward direction, respectively. If specified, must be greater than/lower than from_transition, depending on the direction
is_forward:boolIf True, return the accesses at the same transition and after from_transition. Otherwise, return the accesses at the same transition and before from_transition.
operation:_Optional[_memhist.MemoryAccessOperation]Only return accesses whose operation equals the specified reven2.memhist.MemoryAccessOperation. If None, return accesses regardless of their operation.
fetch_count:int

Technical parameter indicating how many accesses should be fetched from the server per query. Modifying this parameter allows to fine-tune performance according to the current use-case:

  • If you intend to make a lot of queries that you expect to return only a few accesses, then you should use a small value (e.g., 100)
  • If you intend to make a few queries that you expect to return a lot of accesses, then you should use a large value (e.g., 10000)
  • The default value should provide sufficient performance in most cases.

NOTE: Modifying this parameter does not modify the results of the query

address_range:_Optional[_MemoryRange]Searched range of addresses. A reven2.MemoryRange. If a virtual range, then it must be mapped (possibly to non-contiguous physical pages) at the context just before/after (depending on the direction) the from_transition. If None and address is None, return accesses regardless of their address. If not None, then address and size must be None.
Returns
_Iterator[_memhist.MemoryAccess]a generator of reven2.memhist.MemoryAccess.
Raises
RuntimeErrorif the memory history resource has not been generated.
RuntimeErrorif the passed address is virtual, and not mapped at the context before/after the from_transition (depending on the direction).
ValueErrorif size <= 0.
ValueErrorif from_transition > to_transition in forward, or from_transition < to_transition in backward.
ValueErrorif fetch_count <= 0.
ValueErrorif size is not None but address is None.
def strings(self, pattern='', fetch_count=200):

Get a generator over the strings matching the pattern

Performance

Returning a generator rather than a list allow to lazily compute the requested results. Calling next on the generator to get the next individual result should always be fast. Forcing eager computations of the results by converting the iterator to a list might be slow depending on the number of results.

Examples

>>> # Getting strings matching '*hello*' in the trace
>>> for string in trace.strings("hello"):
...     print(string)
[#2545492692 - #2545492932] 'D:\>hello' of size 20 at address @lin:0xfffff90140081038
[#2583245618 - #2583245653] 'hello.exe' of size 18 at address @lin:0x15a2de19930
[#2583245992 - #2583246162] 'HELLO' of size 5 at address @lin:0xffffb0000f2331d2
[#2583245995 - #2583246018] 'Hello\0' of size 12 at address @lin:0xffffc000ea501fd4
>>> # Getting accesses of the string:
>>> string = next(strings)
>>> for memory_access in string.memory_accesses():
...     print(memory_access)
[#2594829 movdqu xmm0, xmmword ptr [rdx + rcx]]Write access at @phy:0x5274cfc0
        (virtual address: lin:0xffffc000e9dd2fc0) of size 16
[#2594830 movdqu xmm1, xmmword ptr [rdx + rcx + 0x10]]Read access at @phy:0x5274cfd0
        (virtual address: lin:0xffffc000e9dd2fd0) of size 16
[#2594836 movdqu xmm0, xmmword ptr [rdx + rcx]]Read access at @phy:0x5274cfe0
        (virtual address: lin:0xffffc000e9dd2fe0) of size 16
[#2594837 movdqu xmm1, xmmword ptr [rdx + rcx + 0x10]]Write access at @phy:0x5274cff0
        (virtual address: lin:0xffffc000e9dd2ff0) of size 8

Information

Parameters
pattern:stra string pattern existing in the searched string. If "", match with all strings The search will match strings like *[pattern]* Note: A long pattern may impact on perfomance with large database
fetch_count:int

Technical parameter indicating how many strings should be fetched from the server per query. Modifying this parameter allows to fine-tune performance according to the current use-case:

  • If you intend to make a lot of queries that you expect to return only a few strings, then you should use a small value (e.g., 20)
  • If you intend to make a few queries that you expect to return a lot of strings, then you should use a large value (e.g., 1000)
  • The default value should provide sufficient performance in most cases.

NOTE: Modifying this parameter does not modify the results of the query.

Returns
_Iterator[_string.String]a generator of reven2.string.String.
Raises
RuntimeErrorif the strings resource has not been generated.
ValueErrorif fetch_count <= 0.
def transition(self, transition_id):

Retrieve a transition from its unique id.

Information

Parameters
transition_id:intUndocumented
Returns
TransitionUndocumented
Raises
IndexErrorif the unique id does not belong to the trace.
def transitions(self, start=None, stop=None, step=1):

Get a generator over the transitions in the trace.

Examples

Iterates on transitions from the beginning to the end of the trace.

>>> for transition in trace.transitions():
...     print(transition)
#0 push -0x2f
#1 push rbp
#2 jmp 0xfffff800c99455a0 ($+0x185)
#3 push rsi
#4 sub rsp, 0x150
#5 lea rbp, [rsp + 0x80]
...

Iterates on transitions from the end to the beginning of the trace.

>>> for transition in trace.transitions(step=-1):
...     print(transition)
#2847570054 je 0x7fff599d16d8 ($+0x270)
#2847570053 cmp rdi, rax
#2847570052 mov dword ptr [rbp - 0x61], r14d
#2847570051 mov r13d, r14d
#2847570050 mov dword ptr [rbp - 0x65], ecx
#2847570049 mov ecx, 0x10
...

Iterates on the 5 first transitions in increasing order.

>>> for transition in trace.transitions(stop=5):
...     print(transition)
#0 push -0x2f
#1 push rbp
#2 jmp 0xfffff800c99455a0 ($+0x185)
#3 push rsi
#4 sub rsp, 0x150

Iterates on 10 transitions somewhere in the trace, in increasing order, and 2 by 2.

>>> start = trace.transition(1000)
>>> stop = start + 10
>>> for transition in trace.transitions(start, stop, 2):
...     print(transition)
#1000 mov r12, rax
#1002 jne 0xfffff800c98053fc ($+0x81)
#1004 mov rcx, rbx
#1006 mov qword ptr [rsp + 8], rbx
#1008 sub rsp, 0x20

Iterates on the 5 last transitions in decreasing order.

>>> for transition in trace.transitions(stop=trace.transition_count - 6, step=-1):
...     print(transition)
#2847570054 je 0x7fff599d16d8 ($+0x270)
#2847570053 cmp rdi, rax
#2847570052 mov dword ptr [rbp - 0x61], r14d
#2847570051 mov r13d, r14d
#2847570050 mov dword ptr [rbp - 0x65], ecx

Information

Parameters
start:_Optional[_Union[Transition, int]]A Transition or an int(transition id) at which to start the generation. If start is None, then: * if step > 0, then start at the beginning of the trace. * if step < 0, then start at the end of the trace. start must be in trace (in [0, trace.transition_count - 1]).
stop:_Optional[_Union[Transition, int]]A Transition or an int(transition id) at which to stop the generation. The stop is excluded from the generation. If stop is None, then: * if step > 0, then stop at the end of the trace. * if step < 0, then stop at the beginning of the trace. stop - 1 must be in trace (not in [0, trace.transition_count - 1]).
step:intAn int which indicates the increment (or decrement) between generated Transitions. If 'step' > 0, then transitions are yielded in increasing order. If 'step' < 0, then transitions are yielded in decreasing order. Default is set to 1.
Returns
_Iterator[Transition]Undocumented
Raises
TypeErrorif any argument is of the wrong type.
ValueErrorif one of the following cases: * step == 0. * step > 0 and start > stop. * step < 0 and start < stop.
IndexErrorif one of the following cases: * start is not in the trace (not in [0, trace.transition_count - 1]). * step > 0 and stop - 1 is not in the trace (not in [0, trace.transition_count - 1]). * step < 0 and stop + 1) is not in the trace (not in [0, trace.transition_count - 1]).
@property
first_context: Context =

Property: First context in this trace.

@property
first_transition: Transition =

Property: First transition in this trace.

@property
last_context: Context =

Property: Last context in this trace.

@property
last_transition: Transition =

Property: Last transition in this trace.

@property
search: _search.Search =

Property: Get a reven2.search.Search instance, which is the entry point to find interesting points in the trace.

@property
transition_count: int =

Property: Count of transitions in this trace.

def _context(self, context_id):

Undocumented

Parameters
context_id:intUndocumented
Returns
ContextUndocumented
_data_source =

Undocumented

_ossi_data_source =

Undocumented

@property
_rvn: _reven_api.reven_connection =

Undocumented