Skip to content

Commit

Permalink
(feat) Added memory replace command. (#656)
Browse files Browse the repository at this point in the history
* (feat) Added memory replace command.

* refactor duplicate code.
  • Loading branch information
IPMegladon authored May 23, 2024
1 parent 08df3fb commit d983234
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
7 changes: 7 additions & 0 deletions agent/src/generic/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export const search = (pattern: string, onlyOffsets: boolean = false): string[]
return addresses.reduce((a, b) => a.concat(b));
};

export const replace = (pattern: string, replace: number[]): string[] => {
return search(pattern, true).map((match) => {
write(match, replace);
return match;
})
};

export const write = (address: string, value: number[]): void => {
new NativePointer(address).writeByteArray(value);
};
2 changes: 2 additions & 0 deletions agent/src/rpc/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ export const memory = {
memoryListModules: (): Module[] => m.listModules(),
memoryListRanges: (protection: string): RangeDetails[] => m.listRanges(protection),
memorySearch: (pattern: string, onlyOffsets: boolean): string[] => m.search(pattern, onlyOffsets),
memoryReplace: (pattern: string, replace: number[]): string[] => m.replace(pattern, replace),
memoryWrite: (address: string, value: number[]): void => m.write(address, value),

};
63 changes: 63 additions & 0 deletions objection/commands/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ def _should_only_dump_offsets(args: list) -> bool:
return '--offsets-only' in args


def _is_string_pattern(args: list) -> bool:
"""
Checks if --string-pattern is in the list of tokens received form the
command line.
:param args:
:return:
"""

return len(args) > 0 and '--string-pattern' in args


def _is_string_replace(args: list) -> bool:
"""
Checks if --string-replace is in the list of tokens received form the
command line.
:param args:
:return:
"""

return len(args) > 0 and '--string-replace' in args


def _should_output_json(args: list) -> bool:
"""
Checks if --json is in the list of tokens received from the command line.
Expand Down Expand Up @@ -296,6 +320,45 @@ def find_pattern(args: list) -> None:
click.secho('Unable to find the pattern in any memory region')


def replace_pattern(args: list) -> None:
"""
Searches the current processes accessible memory for a specific pattern and replaces it with given bytes or string.
:param args:
:return:
"""

if len(clean_argument_flags(args)) < 2:
click.secho('Usage: memory replace "<search pattern eg: 41 41 ?? 41>" "<replace value eg: 41 50>" (--string-pattern) (--string-replace)', bold=True)
return

# if we got a string as search pattern input, convert it to hex
if _is_string_pattern(args):
pattern = ' '.join(hex(ord(x))[2:] for x in args[0])
else:
pattern = args[0]

# if we got a string as replace input, convert it to int[], otherwise convert hex to int[]
replace = args[1]
if _is_string_replace(args):
replace = [ord(x) for x in replace]
else:
replace = [int(x, 16) for x in replace.split(' ')]

click.secho('Searching for: {0}, replacing with: {1}'.format(pattern, args[1]), dim=True)

api = state_connection.get_api()
data = api.memory_replace(pattern, replace)

if len(data) > 0:
click.secho('Pattern replaced at {0} addresses'.format(len(data)), fg='green')
for address in data:
click.secho(address)

else:
click.secho('Unable to find the pattern in any memory region')


def write(args: list) -> None:
"""
Write an arbitrary amount of bytes to an arbitrary memory address.
Expand Down
6 changes: 6 additions & 0 deletions objection/console/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@
'exec': memory.find_pattern
},

'replace': {
'meta': 'Search and replace pattern in the applications memory',
'flags': ['--string-pattern', '--string-replace'],
'exec': memory.replace_pattern
},

'write': {
'meta': 'Write raw bytes to a memory address. Use with caution!',
'flags': ['--string'],
Expand Down
51 changes: 50 additions & 1 deletion tests/commands/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest import mock

from objection.commands.memory import _is_string_input, dump_all, dump_from_base, list_modules, list_exports, \
find_pattern
find_pattern, replace_pattern
from ..helpers import capture


Expand Down Expand Up @@ -207,6 +207,55 @@ def test_find_pattern_without_string_argument_with_offets_only(self, mock_api):
expected_output = """Searching for: 41 41 41
Pattern matched at 1 addresses
0x08000000
"""

self.assertEqual(output, expected_output)


def test_replace_pattern_validates_arguments(self):
with capture(replace_pattern, []) as o:
output = o

self.assertEqual(output, 'Usage: memory replace "<search pattern eg: 41 41 ?? 41>" "<replace value eg: 41 50>" (--string-pattern) (--string-replace)\n')

@mock.patch('objection.state.connection.state_connection.get_api')
def test_replace_pattern_without_string_argument(self, mock_api):
mock_api.return_value.memory_replace.return_value = ['0x08000000']

with capture(replace_pattern, ['41 41 41','41 42']) as o:
output = o

expected_output = """Searching for: 41 41 41, replacing with: 41 42
Pattern replaced at 1 addresses
0x08000000
"""

self.assertEqual(output, expected_output)

@mock.patch('objection.state.connection.state_connection.get_api')
def test_replace_pattern_with_string_argument(self, mock_api):
mock_api.return_value.memory_replace.return_value = ['0x08000000']

with capture(replace_pattern, ['foo-bar-baz', '41 41', '--string-pattern']) as o:
output = o

expected_output = """Searching for: 66 6f 6f 2d 62 61 72 2d 62 61 7a, replacing with: 41 41
Pattern replaced at 1 addresses
0x08000000
"""

self.assertEqual(output, expected_output)

@mock.patch('objection.state.connection.state_connection.get_api')
def test_replace_pattern_without_string_argument_with_offets_only(self, mock_api):
mock_api.return_value.memory_replace.return_value = ['0x08000000']

with capture(replace_pattern, ['41 41 41', 'ABC', '--string-replace']) as o:
output = o

expected_output = """Searching for: 41 41 41, replacing with: ABC
Pattern replaced at 1 addresses
0x08000000
"""

self.assertEqual(output, expected_output)

0 comments on commit d983234

Please sign in to comment.