Skip to content

Instantly share code, notes, and snippets.

@rklaehn
Last active October 14, 2015 00:28
Show Gist options
  • Save rklaehn/4280139 to your computer and use it in GitHub Desktop.
Save rklaehn/4280139 to your computer and use it in GitHub Desktop.
Flip random bits in an arbitrary process on a 64bit linux system. Works by reading in /proc/<pid>/mem and remote controlling gdb to do the actual writing.
#!/bin/bash
exec scala "$0" "$@"
!#
case class Mapping(from:Long, to:Long, r:Boolean, w:Boolean, fileOpt:Option[String]) {
def size : Long = to - from
}
object Mapping {
def parse(line:String) : Option[Mapping] = {
mappingRegex.unapplySeq(line).map {
case list =>
val from = new java.math.BigInteger(list(0), 16).longValue
val to = new java.math.BigInteger(list(1), 16).longValue
val r = list(2) == "r"
val w = list(3) == "w"
val fileOpt = if(list.size==11) Option(list(10)).map(_.trim) else None
Mapping(from, to, r, w, fileOpt)
}
}
private val mappingRegexText = """
([0-9A-Fa-f]{8,16})-([0-9A-Fa-f]{8,16})
\s([r\-])([w\-])([x\-])([ps\-])\s
([0-9A-Fa-f]{8})\s
([0-9A-Fa-f]{2})\:([0-9A-Fa-f]{2})\s
([0-9]+)\s*
(.+)?
"""
.replace("\n","").replace(" ","")
private val mappingRegex = mappingRegexText.r
}
object MappingSeq {
def parse(pid:Int) : IndexedSeq[Mapping] = {
import scala.io.Source
val maps = "/proc/"+pid+"/maps"
val source = Source.fromFile(maps)
val lines = source.getLines.toIndexedSeq
val ranges = lines.flatMap(Mapping.parse _)
require(lines.size == ranges.size)
ranges
}
def translate(s:Seq[Mapping], offset:Long) : Long = {
require(offset>=0)
def translate0(i:Int, o:Long) : Long = {
if(i >= s.size) -1L
else if(o < s(i).size) s(i).from + o
else translate0(i + 1, o - s(i).size)
}
translate0(0,offset)
}
}
class GDBController private(process:java.lang.Process, verbose:Boolean, attachDelay:Int, normalDelay:Int) {
import scala.io.Source
private val pid = {
val f = process.getClass().getDeclaredField("pid")
f.setAccessible(true)
f.get(process).asInstanceOf[Int]
}
private val writer = new java.io.BufferedWriter(new java.io.OutputStreamWriter(process.getOutputStream()))
async("stdout reader") {
for(line <- Source.fromInputStream(process.getInputStream()).getLines()) {
if(verbose)
println("out>"+line)
stdout :+= line
}
}
async("stderr reader") {
for(line <- Source.fromInputStream(process.getErrorStream()).getLines()) {
if(verbose)
println("err>"+line)
stderr :+= line
}
}
@volatile private var stderr = IndexedSeq.empty[String]
@volatile private var stdout = IndexedSeq.empty[String]
private def async(name:String)(code: =>Unit) {
val r = new java.lang.Runnable {
def run() {
code
}
}
val thread = new java.lang.Thread(r)
thread.setName(name)
thread.start()
}
private def gdbCmd(lines:Seq[String], wait:Int): (Seq[String], Seq[String]) = {
val outSize0 = stdout.size
val errSize0 = stderr.size
for(line<-lines) {
if(verbose)
println(line)
writer.write(line + "\n")
writer.flush()
}
Thread.sleep(wait)
(stdout.drop(outSize0), stderr.drop(errSize0))
}
private def shortCmd(line:String, wait:Int) : Boolean = {
val (out, err) = gdbCmd(Seq(line), wait)
err.isEmpty
}
def set32bit() = shortCmd("set architecture i386", 1000)
def set64bit() = shortCmd("set architecture i386:x86-64", 1000)
def attach(pid:Int) = shortCmd("attach "+pid,1000)
def peek(offset:Long): Option[Byte] = {
val cmd = "p /d *((char*)%s)".format(offset)
val (out, err) = gdbCmd(Seq(cmd), 1000)
if(err.isEmpty)
out.lastOption.flatMap(_.split(" ").lastOption).map(_.toByte)
else
None
}
def poke(offset:Long, value:Byte): Option[Byte] = {
val cmd = "p /d *((char*)%s) = %s".format(offset,value)
val (out, err) = gdbCmd(Seq(cmd), 1000)
if(err.isEmpty)
out.lastOption.flatMap(_.split(" ").lastOption).map(_.toByte)
else
None
}
def setBit(offset:Long, bit:Int, value:Boolean) = {
val cmd =
if(value)
"p /d *((char*)%s) = *((char*)%s) | (1 << %s)".format(offset,offset, bit)
else
"p /d *((char*)%s) = *((char*)%s) & ~(1 << %s)".format(offset,offset, bit)
val (out, err) = gdbCmd(Seq(cmd), 10)
if(err.isEmpty)
out.lastOption.flatMap(_.split(" ").lastOption).map(_.toByte)
else
None
}
def flipBit(offset:Long, bit:Int) = {
val cmd = "p /d *((char*)%s) = *((char*)%s) ^ (1 << %s)".format(offset,offset, bit)
val (out, err) = gdbCmd(Seq(cmd), 10)
if(err.isEmpty)
out.lastOption.flatMap(_.split(" ").lastOption).map(_.toByte)
else
None
}
def continue() = shortCmd("continue", 10)
def interrupt() = {
Runtime.getRuntime().exec("kill -INT " + pid)
Thread.sleep(100)
}
def detach() = shortCmd("detach", 1000)
def quit() = shortCmd("quit", 1000)
def close():(Int,Seq[String], Seq[String]) = {
writer.close()
val result = process.waitFor()
(result, stdout, stderr)
}
}
object GDBController {
def apply(verbose:Boolean = true) = {
val process = java.lang.Runtime.getRuntime.exec("gdb")
new GDBController(process, verbose, 1000, 1000)
}
}
def nextLong(rng: scala.util.Random, n: Long) = {
var bits = 0L
var res = 0L
do {
bits = (rng.nextLong() << 1) >>> 1
res = bits % n
} while (bits-res+(n-1) < 0L)
res
}
class BitFlipper(pid:Int, controller:GDBController, r:scala.util.Random) {
def flipRandomBit() {
val ranges = MappingSeq.parse(pid)
val writable = ranges.filter(x=>x.w && x.fileOpt.isEmpty)
val totalWritable = writable.map(_.size).sum
val byte = nextLong(r, totalWritable)
val bit = r.nextInt(8)
val offset = MappingSeq.translate(writable, byte)
require(offset>=0)
controller.interrupt()
controller.flipBit(offset,bit)
controller.continue()
}
def close() = {
controller.interrupt()
controller.detach()
controller.quit()
controller.close()
}
controller.attach(pid)
controller.set64bit()
}
object BitFlipper {
def apply(pid:Int, verbose:Boolean = true) = {
new BitFlipper(pid, GDBController(verbose = verbose), new scala.util.Random())
}
}
if(args.length<1) {
println("Usage: bitflipper.scala <pid>")
System.exit(1)
}
val pid = args(0).toInt
val flipper = BitFlipper(pid)
while(System.in.available()==0)
flipper.flipRandomBit()
flipper.close()
public class Runner {
public static void main(String[] args) {
long[] data = new long[50000000];
int count=0;
while(true) {
long sum=0;
for(int i=0;i<data.length;i++)
sum+=data[i];
// if(count%20==0)
System.out.println(""+count+" "+sum);
count+=1;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment