class Trace(object):
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").
This object is not meant to be constructed directly. Use RevenServer.trace
instead.
>>> # 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 |
Property | transition_count |
Property: Count of transitions in this trace. |
Property | first_transition |
Property: First transition in this trace. |
Property | last_transition |
Property: Last transition in this trace. |
Property | first_context |
Property: First context in this trace. |
Property | last_context |
Property: Last context in this trace. |
Method | transition |
Retrieve a transition from its unique id. |
Method | transitions |
Get a generator over the transitions in the trace. |
Method | context_before |
Retrieve the context before a transition from the latter's unique id. |
Method | context_after |
Retrieve the context after 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 |
Property | search |
Property: Get a reven2.search.Search instance, which is the entry point to find interesting points in the trace. |
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 | __repr__ |
Undocumented |
Instance Variable | _data_source |
Undocumented |
Instance Variable | _ossi_data_source |
Undocumented |
Property | _rvn |
Undocumented |
Method | _context |
Undocumented |
Property: Count of transitions in this trace.
Returns | an integer. |
Retrieve a transition from its unique id.
Returns | A Transition . | |
Raises | IndexError | if the unique id does not belong to the trace. |
Get a generator over the transitions in the trace.
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
Parameters | start | 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 | 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 | An int which indicates the increment (or decrement) between generated Transition s. 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 | a generator of Transition . | |
Raises | TypeError | if any argument is of the wrong type. |
ValueError | if one of the following cases: * step == 0 . * step > 0 and start > stop . * step < 0 and start < stop . | |
IndexError | if 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]). |
Retrieve the context before a transition from the latter's unique id.
Returns | A Context . | |
Raises | IndexError | if the unique id does not belong to the trace. |
Retrieve the context after a transition from the latter's unique id.
Returns | A Context . | |
Raises | IndexError | if the unique id does not belong to the trace |
Get a generator over the contexts in the trace.
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
Parameters | start | 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 | 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 | An int which indicates the increment (or decrement) between generated Context s. 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 | a generator of Context . | |
Raises | TypeError | if any argument is of the wrong type. |
ValueError | if one of the following cases: * step == 0 . * step > 0 and start > stop . * step < 0 and start < stop . |
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.
Depends on the ossi range resource. If unavailable, this function will raise an error.
>>> # 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 ...
Parameters | processes | the list of process to filter. If None, all processes will be filtered. (type: _Optional[_Iterable[_ossi.Process]] ) |
ring_policy | the rings to filter. Muste be a filter.RingPolicy value. By default, all rings will be filtered. (type: _filter.RingPolicy ) | |
from_context | the first included context where the filter starts. Must be a trace.Context . If None, start at the first context of trace. (type: _Optional[Context] ) | |
to_context | the first excluded context where the filter stops. Must be a trace.Context . If None, stop at the last context of trace (included). `from_context` must be strictly lower than `to_context`. (type: _Optional[Context] ) | |
is_forward | either the results are returned in the forward or backward order. By default, the forward order is used. (type: bool ) | |
Returns | A generator of trace.ContextRange if `is_forward` equal `True`. Otherwise, a generator of trace.BackwardContextRange . (type: _Union[_Iterator[ContextRange], _Iterator[BackwardContextRange]] ) | |
Raises | ValueError | if `to_context` is lower or equal to `from_context`. |
RuntimeError | if the ossi ranges ressource is unavailable. |
Property: Get a reven2.search.Search
instance, which is the entry point to find interesting points in the trace.
Returns | A reven2.search.Search instance. |
Get a generator over the previous/next memory accesses to a range of addresses defined by (address, size).
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.
>>> # 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)
Parameters | 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 | Size of the searched range of addresses. If None and address not None , a default size of 1 will be used. | |
from_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 | 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 | If 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 | Only return accesses whose operation equals the specified reven2.memhist.MemoryAccessOperation . If None , return accesses regardless of their operation. | |
fetch_count | 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:
NOTE: Modifying this parameter does not modify the results of the query | |
Returns | a generator of reven2.memhist.MemoryAccess . | |
Raises | RuntimeError | if the memory history resource has not been generated. |
RuntimeError | if the passed address is virtual, and not mapped at the context before/after the from_transition (depending on the direction). | |
ValueError | if size < 0. | |
ValueError | if from_transition > to_transition in forward, or from_transition < to_transition in backward. | |
ValueError | if fetch_count <= 0. | |
ValueError | if size is not None but address is None . |
Get a generator over the strings matching the pattern
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.
>>> # 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
Parameters | pattern | a 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 | 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:
NOTE: Modifying this parameter does not modify the results of the query. | |
Returns | a generator of reven2.string.String . | |
Raises | RuntimeError | if the strings resource has not been generated. |
ValueError | if fetch_count <= 0. |