1
0

16 Commits

15 changed files with 263 additions and 39 deletions

View File

@ -4,7 +4,33 @@
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.1.0] 2019-08-30
### Added
- Global configuration file (~/.out-of-tree/out-of-tree.toml) allow to
set up default values for settings.
- rootfs generator for Ubuntu 14.04.
- Parameter for setting up docker registry server.
- Support for (distro-specific) custom docker commands that will be
executed before the base template.
- Parameter for setting up a reliability threshold for exit code.
- Parameter for setting up global timeout, after which no new tasks
will be started.
### Fixed
- Spelling in output.
- Now kernel generation will not fail if there are no directory
/lib/modules inside the container.
## [1.0.0] 2019-08-20
### Added

View File

@ -1,5 +1,5 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/aba4aad2046b4d1a9a99cf98e22c018b)](https://app.codacy.com/app/jollheef/out-of-tree?utm_source=github.com&utm_medium=referral&utm_content=jollheef/out-of-tree&utm_campaign=Badge_Grade_Dashboard)
[![Build Status](https://travis-ci.org/jollheef/out-of-tree.svg?branch=master)](https://travis-ci.org/jollheef/out-of-tree)
[![Build Status](https://travis-ci.com/jollheef/out-of-tree.svg?branch=master)](https://travis-ci.org/jollheef/out-of-tree)
[![Go Report Card](https://goreportcard.com/badge/code.dumpstack.io/tools/out-of-tree)](https://goreportcard.com/report/code.dumpstack.io/tools/out-of-tree)
[![Documentation Status](https://readthedocs.org/projects/out-of-tree/badge/?version=latest)](https://out-of-tree.readthedocs.io/en/latest/?badge=latest)
[![Donate](https://img.shields.io/badge/donate-paypal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R8W2UQPZ5X5JE&source=url)
@ -9,6 +9,8 @@
out-of-tree kernel {module, exploit} development tool
out-of-tree is for automating some routine actions for creating development environments for debugging kernel modules and exploits, generating reliability statistics for exploits, and also provides the ability to easily integrate into CI (Continuous Integration).
![Screenshot](https://cloudflare-ipfs.com/ipfs/Qmb88fgdDjbWkxz91sWsgmoZZNfVThnCtj37u3mF2s3T3T)
## Requirements

77
config/out-of-tree.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2019 Mikhail Klementev. All rights reserved.
// Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file.
package config
import (
"os/user"
"github.com/naoina/toml"
)
type DockerCommand struct {
DistroType DistroType
Command string
}
type OutOfTree struct {
Kernels string
UserKernels string
Database string
Qemu struct {
Timeout string
}
Docker struct {
Timeout string
Registry string
// Commands that will be executed before
// the base layer of Dockerfile
Commands []DockerCommand
}
}
func ReadOutOfTreeConf(path string) (c OutOfTree, err error) {
buf, err := readFileAll(path)
if err == nil {
err = toml.Unmarshal(buf, &c)
if err != nil {
return
}
} else {
// It's ok if there's no configuration
// then we'll just set default values
err = nil
}
usr, err := user.Current()
if err != nil {
return
}
if c.Kernels == "" {
c.Kernels = usr.HomeDir + "/.out-of-tree/kernels.toml"
}
if c.UserKernels == "" {
c.UserKernels = usr.HomeDir + "/.out-of-tree/kernels.user.toml"
}
if c.Database == "" {
c.Database = usr.HomeDir + "/.out-of-tree/db.sqlite"
}
if c.Qemu.Timeout == "" {
c.Qemu.Timeout = "1m"
}
if c.Docker.Timeout == "" {
c.Docker.Timeout = "1m"
}
return
}

View File

@ -161,7 +161,7 @@ func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
q.Debug(gdb)
coloredGdbAddress := aurora.BgGreen(aurora.Black(gdb))
fmt.Printf("[*] gdb runned on %s\n", coloredGdbAddress)
fmt.Printf("[*] gdb is listening on %s\n", coloredGdbAddress)
err = q.Start()
if err != nil {

View File

@ -54,7 +54,7 @@ Overview
$ out-of-tree debug --kernel 'Ubuntu:4.15.0-58-generic'
[*] KASLR SMEP SMAP
[*] gdb runned on tcp::1234
[*] gdb is listening on tcp::1234
[*] build result copied to /tmp/exploit
ssh -o StrictHostKeyChecking=no -p 29308 root@127.133.45.236

View File

@ -2,7 +2,7 @@
# - KERNEL: kernel headers path
# - TARGET: name of exploit binary that MUST be produced by makefile.
# - $(TARGET)_test: name of test binary that MUST be produced by makefile
# and it's will be runned on a LPE stage. TARGET_TEST MUST accept two argument:
# and it's will be executed on a LPE stage. TARGET_TEST MUST accept two argument:
# - Path to exploit binary
# - File that MUST be created with exploit. It uses for test that exploit works
# correctly.

View File

@ -109,7 +109,9 @@ func vsyscallAvailable() (available bool, err error) {
return
}
func generateBaseDockerImage(sk config.KernelMask) (err error) {
func generateBaseDockerImage(registry string, commands []config.DockerCommand,
sk config.KernelMask) (err error) {
imagePath, err := dockerImagePath(sk)
if err != nil {
return
@ -128,7 +130,12 @@ func generateBaseDockerImage(sk config.KernelMask) (err error) {
sk.DistroType.String(), sk.DistroRelease)
os.MkdirAll(imagePath, os.ModePerm)
d += fmt.Sprintf("FROM %s:%s\n",
d += "FROM "
if registry != "" {
d += registry + "/"
}
d += fmt.Sprintf("%s:%s\n",
strings.ToLower(sk.DistroType.String()),
sk.DistroRelease,
)
@ -138,6 +145,21 @@ func generateBaseDockerImage(sk config.KernelMask) (err error) {
return
}
for _, c := range commands {
switch c.DistroType {
case config.Ubuntu:
d += "RUN " + c.Command + "\n"
case config.CentOS:
d += "RUN " + c.Command + "\n"
case config.Debian:
d += "RUN " + c.Command + "\n"
default:
err = fmt.Errorf("%s not yet supported",
sk.DistroType.String())
return
}
}
switch sk.DistroType {
case config.Ubuntu:
d += "ENV DEBIAN_FRONTEND=noninteractive\n"
@ -147,7 +169,7 @@ func generateBaseDockerImage(sk config.KernelMask) (err error) {
if sk.DistroRelease >= "14.04" {
d += "RUN apt-get install -y libseccomp-dev\n"
}
d += "RUN mkdir /lib/modules\n"
d += "RUN mkdir -p /lib/modules\n"
case config.CentOS:
if sk.DistroRelease < "7" && !vsyscall {
log.Println("Old CentOS requires `vsyscall=emulate` " +
@ -573,7 +595,10 @@ func shuffle(a []string) []string {
return a
}
func generateKernels(km config.KernelMask, max int64, download bool) (err error) {
func generateKernels(km config.KernelMask, registry string,
commands []config.DockerCommand, max int64,
download bool) (err error) {
log.Println("Generating for kernel mask", km)
_, err = genRootfsImage(dockerImageInfo{ContainerName: km.DockerName()},
@ -582,7 +607,7 @@ func generateKernels(km config.KernelMask, max int64, download bool) (err error)
return
}
err = generateBaseDockerImage(km)
err = generateBaseDockerImage(registry, commands, km)
if err != nil {
return
}
@ -632,7 +657,10 @@ func generateKernels(km config.KernelMask, max int64, download bool) (err error)
return
}
func kernelAutogenHandler(workPath string, max int64, host, download bool) (err error) {
func kernelAutogenHandler(workPath, registry string,
commands []config.DockerCommand,
max int64, host, download bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
return
@ -644,7 +672,7 @@ func kernelAutogenHandler(workPath string, max int64, host, download bool) (err
return
}
err = generateKernels(sk, max, download)
err = generateKernels(sk, registry, commands, max, download)
if err != nil {
return
}
@ -695,7 +723,9 @@ func kernelDockerRegenHandler(host, download bool) (err error) {
return updateKernelsCfg(host, download)
}
func kernelGenallHandler(distro, version string, host, download bool) (err error) {
func kernelGenallHandler(distro, version, registry string,
commands []config.DockerCommand, host, download bool) (err error) {
distroType, err := config.NewDistroType(distro)
if err != nil {
return
@ -706,7 +736,7 @@ func kernelGenallHandler(distro, version string, host, download bool) (err error
DistroRelease: version,
ReleaseMask: ".*",
}
err = generateKernels(km, kernelsAll, download)
err = generateKernels(km, registry, commands, kernelsAll, download)
if err != nil {
return
}

45
main.go
View File

@ -84,7 +84,7 @@ func main() {
)
app.Author("Mikhail Klementev <root@dumpstack.io>")
app.Version("0.2.0")
app.Version("1.1.0")
pathFlag := app.Flag("path", "Path to work directory")
path := pathFlag.Default(".").ExistingDir()
@ -95,25 +95,36 @@ func main() {
}
os.MkdirAll(usr.HomeDir+"/.out-of-tree", os.ModePerm)
defaultKcfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml"
confPath := usr.HomeDir + "/.out-of-tree/out-of-tree.toml"
conf, err := config.ReadOutOfTreeConf(confPath)
if err != nil {
return
}
kcfgPathFlag := app.Flag("kernels", "Path to main kernels config")
kcfgPath := kcfgPathFlag.Default(defaultKcfgPath).String()
kcfgPath := kcfgPathFlag.Default(conf.Kernels).String()
defaultDbPath := usr.HomeDir + "/.out-of-tree/db.sqlite"
dbPathFlag := app.Flag("db", "Path to database")
dbPath := dbPathFlag.Default(defaultDbPath).String()
dbPath := dbPathFlag.Default(conf.Database).String()
defaultUserKcfgPath := usr.HomeDir + "/.out-of-tree/kernels.user.toml"
userKcfgPathFlag := app.Flag("user-kernels", "User kernels config")
userKcfgPathEnv := userKcfgPathFlag.Envar("OUT_OF_TREE_KCFG")
userKcfgPath := userKcfgPathEnv.Default(defaultUserKcfgPath).String()
userKcfgPath := userKcfgPathEnv.Default(conf.UserKernels).String()
timeoutFlag := app.Flag("timeout", "Timeout after tool will not spawn new tests")
timeout := timeoutFlag.Duration()
qemuTimeoutFlag := app.Flag("qemu-timeout", "Timeout for qemu")
qemuTimeout := qemuTimeoutFlag.Default("1m").Duration()
qemuTimeout := qemuTimeoutFlag.Default(conf.Qemu.Timeout).Duration()
dockerTimeoutFlag := app.Flag("docker-timeout", "Timeout for docker")
dockerTimeout := dockerTimeoutFlag.Default("1m").Duration()
dockerTimeout := dockerTimeoutFlag.Default(conf.Docker.Timeout).Duration()
dockerRegistryFlag := app.Flag("docker-registry", "Registry for docker")
dockerRegistry := dockerRegistryFlag.Default(conf.Docker.Registry).String()
thresholdFlag := app.Flag("threshold", "Reliablity threshold for exit code")
threshold := thresholdFlag.Default("1.00").Float64()
pewCommand := app.Command("pew", "Build, run and test module/exploit")
@ -279,20 +290,27 @@ func main() {
}
defer db.Close()
stop := time.Time{} // never stop
if *timeout != 0 {
stop = time.Now().Add(*timeout)
}
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
case pewCommand.FullCommand():
err = pewHandler(kcfg, *path, *pewKernel, *pewBinary,
*pewTest, *pewGuess, *qemuTimeout, *dockerTimeout,
*pewTest, *pewGuess, stop, *qemuTimeout, *dockerTimeout,
*pewMax, *pewRuns, *pewDist, *pewTag, *pewThreads, db)
case kernelListCommand.FullCommand():
err = kernelListHandler(kcfg)
case kernelAutogenCommand.FullCommand():
err = kernelAutogenHandler(*path, *kernelAutogenMax,
err = kernelAutogenHandler(*path, *dockerRegistry,
conf.Docker.Commands, *kernelAutogenMax,
*kernelUseHost, !*kernelNoDownload)
case kernelDockerRegenCommand.FullCommand():
err = kernelDockerRegenHandler(*kernelUseHost, !*kernelNoDownload)
case kernelGenallCommand.FullCommand():
err = kernelGenallHandler(*distro, *version,
*dockerRegistry, conf.Docker.Commands,
*kernelUseHost, !*kernelNoDownload)
case genModuleCommand.FullCommand():
err = genConfig(config.KernelModule)
@ -316,7 +334,8 @@ func main() {
case logMarkdownCommand.FullCommand():
err = logMarkdownHandler(db, *path, *logMarkdownTag)
case packCommand.FullCommand():
err = packHandler(db, *path, kcfg, *packAutogen,
err = packHandler(db, *path, *dockerRegistry, stop,
conf.Docker.Commands, kcfg, *packAutogen,
!*packNoDownload, *packExploitRuns, *packKernelRuns)
}
@ -324,7 +343,7 @@ func main() {
log.Fatalln(err)
}
if somethingFailed {
if successRate(state) < *threshold {
os.Exit(1)
}
}

View File

@ -15,7 +15,8 @@ import (
"code.dumpstack.io/tools/out-of-tree/config"
)
func packHandler(db *sql.DB, path string, kcfg config.KernelConfig,
func packHandler(db *sql.DB, path, registry string, stop time.Time,
commands []config.DockerCommand, kcfg config.KernelConfig,
autogen, download bool, exploitRuns, kernelRuns int64) (err error) {
dockerTimeout := time.Minute
@ -39,7 +40,8 @@ func packHandler(db *sql.DB, path string, kcfg config.KernelConfig,
if autogen {
var perRegex int64 = 1
err = kernelAutogenHandler(workPath, perRegex, false, download)
err = kernelAutogenHandler(workPath, registry,
commands, perRegex, false, download)
if err != nil {
return
}
@ -48,7 +50,7 @@ func packHandler(db *sql.DB, path string, kcfg config.KernelConfig,
log.Println(f.Name())
pewHandler(kcfg, workPath, "", "", "", false,
dockerTimeout, qemuTimeout,
stop, dockerTimeout, qemuTimeout,
kernelRuns, exploitRuns, pathDevNull, tag, threads, db)
}

27
pew.go
View File

@ -26,7 +26,17 @@ import (
"code.dumpstack.io/tools/out-of-tree/qemu"
)
var somethingFailed = false
type runstate struct {
Overall, Success float64
}
var (
state runstate
)
func successRate(state runstate) float64 {
return state.Success / state.Overall
}
const pathDevNull = "/dev/null"
@ -134,11 +144,12 @@ func testKernelExploit(q *qemu.System, ka config.Artifact,
}
func genOkFail(name string, ok bool) (aurv aurora.Value) {
state.Overall += 1
if ok {
state.Success += 1
s := " " + name + " SUCCESS "
aurv = aurora.BgGreen(aurora.Black(s))
} else {
somethingFailed = true
s := " " + name + " FAILURE "
aurv = aurora.BgRed(aurora.Gray(aurora.Bold(s)))
}
@ -380,7 +391,8 @@ func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
}
func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
testPath string, qemuTimeout, dockerTimeout time.Duration,
testPath string, stop time.Time,
qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int,
db *sql.DB) (err error) {
@ -402,6 +414,9 @@ func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
found = true
max--
for i := int64(0); i < runs; i++ {
if !stop.IsZero() && time.Now().After(stop) {
break
}
swg.Add()
go whatever(&swg, ka, kernel, binaryPath,
testPath, qemuTimeout, dockerTimeout,
@ -457,9 +472,10 @@ func genAllKernels() (sk []config.KernelMask, err error) {
return
}
// TODO: Now too many parameters, move all of them to some structure
func pewHandler(kcfg config.KernelConfig,
workPath, ovrrdKrnl, binary, test string, guess bool,
qemuTimeout, dockerTimeout time.Duration,
stop time.Time, qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int,
db *sql.DB) (err error) {
@ -489,7 +505,8 @@ func pewHandler(kcfg config.KernelConfig,
}
}
err = performCI(ka, kcfg, binary, test, qemuTimeout, dockerTimeout,
err = performCI(ka, kcfg, binary, test,
stop, qemuTimeout, dockerTimeout,
max, runs, dist, tag, threads, db)
if err != nil {
return

View File

@ -60,7 +60,7 @@ type Kernel struct {
InitrdPath string
}
// System describe qemu parameters and runned process
// System describe qemu parameters and executed process
type System struct {
arch arch
kernel Kernel
@ -86,7 +86,7 @@ type System struct {
Died bool
sshAddrPort string
// accessible while qemu is runned
// accessible while qemu is running
cmd *exec.Cmd
pipe struct {
stdin io.WriteCloser

View File

@ -47,7 +47,7 @@ ENV IMAGE=/shared/out_of_tree_centos_7.img
RUN mkdir $IMAGEDIR
# Must be runned with --privileged because of /dev/loop
# Must be executed with --privileged because of /dev/loop
CMD qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \

View File

@ -0,0 +1,34 @@
# Copyright 2018 Mikhail Klementev. All rights reserved.
# Use of this source code is governed by a AGPLv3 license
# (or later) that can be found in the LICENSE file.
#
# Usage:
#
# $ docker build -t gen-ubuntu1404-image .
# $ docker run --privileged -v $(pwd):/shared -t gen-ubuntu1404-image
#
# ubuntu1404.img will be created in current directory. You can change $(pwd) to
# different directory to use different destination for image.
#
FROM ubuntu:14.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y debootstrap qemu
ENV TMPDIR=/tmp/ubuntu
ENV IMAGEDIR=/tmp/image
ENV IMAGE=/shared/out_of_tree_ubuntu_14__04.img
ENV REPOSITORY=http://archive.ubuntu.com/ubuntu
ENV RELEASE=trusty
RUN mkdir $IMAGEDIR
# Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server $RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \
cp -a $TMPDIR/* $IMAGEDIR/ && \
umount $IMAGEDIR

View File

@ -0,0 +1,17 @@
#!/bin/sh -eux
# Copyright 2018 Mikhail Klementev. All rights reserved.
# Use of this source code is governed by a AGPLv3 license
# (or later) that can be found in the LICENSE file.
TMPDIR=$1
chroot $TMPDIR /bin/sh -c 'useradd -m user'
sed -i 's/root:\*:/root::/' $TMPDIR/etc/shadow
sed -i 's/user:!!:/user::/' $TMPDIR/etc/shadow
echo auth sufficient pam_permit.so > $TMPDIR/etc/pam.d/sshd
sed -i '/PermitEmptyPasswords/d' $TMPDIR/etc/ssh/sshd_config
echo PermitEmptyPasswords yes >> $TMPDIR/etc/ssh/sshd_config
sed -i '/PermitRootLogin/d' $TMPDIR/etc/ssh/sshd_config
echo PermitRootLogin yes >> $TMPDIR/etc/ssh/sshd_config
echo '#!/bin/sh' > $TMPDIR/etc/rc.local
echo 'dhclient eth0' >> $TMPDIR/etc/rc.local
chmod +x $TMPDIR/etc/rc.local

View File

@ -25,7 +25,7 @@ ENV RELEASE=bionic
RUN mkdir $IMAGEDIR
# Must be runned with --privileged because of /dev/loop
# Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server $RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \