Descent into Darkness: Understanding your system's binary interface is the only way out

  • Upload
    ice799

  • View
    216

  • Download
    0

Embed Size (px)

Citation preview

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    1/75

    Descent into Darkness:Understanding your systems binary

    interface is the only way out.

    joe damato@joedamato

    timetobleed.com

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    2/75

    About Joe Damato

    ex-vmware, cmu alumni

    memprof, ltrace libdl/libunwind patchset,ree/mri thread implementation rewrite

    http://timetobleed.com

    @joedamato

    http://timetobleed.com/http://timetobleed.com/http://timetobleed.com/
  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    3/75

    Only have 30 minutes...welcome to flight school.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    4/75

    No clue why this was

    acce ted.

    This talk will have about 5 lines of Ruby code.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    5/75

    Before we get started

    I need to introduce you a good friend of mine...

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    6/75

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    7/75

    This talk is about how being evil is totally awesome.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    8/75

    Dont do any of this,

    ever.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    9/75

    The problem

    My ruby process is 700 megabytes. Why?

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    10/75

    The problem

    It is very easy to leak references in yourRuby code.

    Leaking a reference to an object causesthat object and all objects it references to

    stick around in memory.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    11/75

    The problem As long as someone, somewhere is

    holding a reference to this instanceof classA, all the objects in thispicture can not be freed

    This could add up to a lot ofmemory very fast.

    GC will scan each object every run

    to see if it is time to free the object.

    This could add up to a lot of CPUburned.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    12/75

    The problem

    But memory is cheap who cares?

    Rubys GC is a nave stop the world markand sweep.

    The more objects that stick around inmemory the longer your GC runs take.

    The longer GC takes, the less time yourapp has to run Ruby code.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    13/75

    The problem

    Eliminate leaked references, reduce the

    length of your GC runs, run more of yourRuby application code.

    Cool. But how can you track downreference leaks?

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    14/75

    Problem Requirements

    I dont want to applypatches and rebuild

    Ruby.

    I want to gem install,require, and done.

    Anything else is toomuch work.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    15/75

    Luckily, we can turn to evil.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    16/75

    Verbiage amd64 is a CPU spec was proposed by AMD

    as a way to add 64bit support to x86.

    Intel Architecture 64 (IA64) spec is acompletely new 64bit instruction set.

    amd64 != IA64 Intel then decided to adopt AMDs 64bit spec. They did and called it IA-32e, EM64T, and

    finally Intel 64.

    Intel 64 ~= amd64

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    17/75

    amd64

    Verbiage

    Intel64

    compilers generate code that uses thesubset of the amd64 spec that both intel and

    amd comply to.

    usually called x86_64 or amd64.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    18/75

    WTF is an ABI?

    Application Binary Interface

    describes the low-level interface between aprogram and the operating system oranother application. (wikipedia)

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    19/75

    WTF is an ABI?

    alignment

    calling conventions

    object file and library formats

    syscalls (how they work, where they live)

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    20/75

    WTF is an ABI?

    System V ABI (271 pages)

    System V ABI AMD64 Architecture ProcessorSupplement (128 pages)

    System V ABI Intel386 Architecture ProcessorSupplement (377 pages)

    MIPS, ARM, PPC, and IA-64 too!

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    21/75

    I brought copies of all three for

    everyone.

    We will now read them together.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    22/75

    No. But lets blaze

    through the importantpieces now.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    23/75

    Evil Devices

    nm - dump symbol table

    objdump - disassemble lots of differentobjects. can do lots, lots more.

    readelf - dump information

    dwarfdump - dump debugging information

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    24/75

    nm

    000000000048ac90 t Balloc

    0000000000491270 T Init_Array

    0000000000497520 T Init_Bignum

    000000000041dc80 T Init_Binding

    000000000049d9b0 T Init_Comparable

    000000000049de30 T Init_Dir

    00000000004a1080 T Init_Enumerable

    00000000004a3720 T Init_Enumerator

    00000000004a4f30 T Init_Exception

    000000000042c2d0 T Init_File

    0000000000434b90 T Init_GC

    % nm /usr/bin/ruby

    symbolvalue

    symbol names

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    25/75

    objdump% objdump -D /usr/bin/ruby

    offsets opcodes instructions helpful metadata

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    26/75

    readelf% readelf -a /usr/bin/ruby

    This is a *tiny* subset of the data available

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    27/75

    dwarfdump% dwarfdump -a /usr/bin/ruby

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    28/75

    Some friends

    Registers are important. They are small, fastpieces of memory on the CPU.

    Some registers have a specific job:

    %rax - holds a return value

    %rip - instruction pointer

    Can refer to pieces of registers.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    29/75

    %rax uncensored

    %rax

    %rax = 64 bits, 8 bytes, 1 quadword

    %eaxlower 32 bits

    %eax = 32 bits, 4 bytes, 1 dword

    %axlower 16 bits

    %ax = 16 bits, 2 bytes, 1 word

    %ah

    %ah = 8 bits, 1 byte, 1 halfword

    upper 8 bits

    %al%al = 8 bits, 1 byte, 1 halfword

    lower 8 bits

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    30/75

    Some x86_64 asm

    notes Two different syntaxes: gas/att and intel.

    GDB disassembly is gas/att by default.

    set disassembly-flavor intel

    objdump is gas/att by default

    objdump -M intel

    I prefer gas/att.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    31/75

    unless otherwise

    noted, everything willbe in att/gas syntax.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    32/75

    Moving stuff

    movsource, destmov$0,%rbx # move immediate (0) to registermov%eax,%rax # mov eax into rax.

    source and dest cannot both be memory.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    33/75

    Calling functions

    Lots of different ways to call functions.

    Two ways we care about (there are more):callq*%rbx # indirect absolutecallq0xdeadbeef # RIP relative with 32bit displacement

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    34/75

    Calling convention

    x86_64 function arguments from left to right live in:

    %rdi, %rsi, %rdx, %rcx, %r8, %r9

    thats for INTEGER class items.

    Other stuff gets passed on the stack (likeon i386).

    end of argument area must be aligned on a16-byte boundary.

    registers can be caller or called saved.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    35/75

    intagain(int amount){ intret = 0; ret = amount + 150;

    returnret;}

    att/gas syntaxintel syntax

    Save the old stack frame base pointer.

    Set the base pointer to the current stack pointer.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    36/75

    intagain(int amount){ intret = 0; ret = amount + 150;

    returnret;}

    att/gas syntaxintel syntax

    *(rbp - 0x14) = amount;

    *(rbp - 0x4) = 0;

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    37/75

    intagain(int amount){ intret = 0; ret = amount + 150;

    returnret;}

    att/gas syntaxintel syntax

    eax = *(rbp - 0x14);

    eax = eax + 0x96; /* 0x96 = 150 :P */

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    38/75

    intagain(int amount){ intret = 0; ret = amount + 150;

    returnret;}

    att/gas syntaxintel syntax

    *(rbp - 0x4) = eax; /* not needed */eax = *(rbp - 0x4); /* not needed */

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    39/75

    intagain(int amount){ intret = 0; ret = amount + 150;

    returnret;}

    att/gas syntaxintel syntax

    restore the stack pointer and old base pointer

    return from the funtion

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    40/75

    ELF Objects

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    41/75

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    42/75

    Sections that matter to

    mem rof

    .text - code lives here

    .plt - stub code that helps to resolve

    absolute function addresses.

    .got.plt - absolute function addresses; used

    by .plt entries.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    43/75

    plt

    Procedure Linkage Table (plt) is used to find

    functions in shared libraries at runtime.

    Shared libraries are position independentand can be mapped anywhere in the

    address space.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    44/75

    Um, what does this haveto do with Rub ?

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    45/75

    The ingredients for evil

    we know the x86_64 ABI

    we know how ELF objects work

    we know ruby calls functions in the VM to

    allocate and free objects (rb_newobj,

    add_freelist)

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    46/75

    You wont.

    Lets combine all of this knowledge and ...

    Rewrite the Ruby VM in memory

    while it is running.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    47/75

    Hook rb_newobj

    The Ruby VM calls rb_newobj to allocate anew object.

    Well need to know when this happens sowe can track objects.

    Lets scan the Ruby binary in memory andrewrite all function calls to rb_newobj tocall a handler function instead.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    48/75

    Hook rb_newobj

    412d16: e8 c1 36 02 00 callq 4363dc #

    412d1b: .....

    address of this instruction

    call opcode*

    32bit displacement to thetarget function from the next

    instruction.

    (objdump output)

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    49/75

    Hook rb_newobj

    412d16: e8 c1 36 02 00 callq 4363dc #

    412d1b: .....

    412d1b = 4363dc+ 000236c1

    (x86 is little endian)

    (objdump output)

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    50/75

    Hook rb_newobj

    Overwrite the displacement so that all callsto rb_newobj actually call a different function

    instead.

    It may look like this:

    VALUE other_function(){

    VALUE new_obj = rb_newobj();

    /* set up tracking of new_obj */ return new_obj;}

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    51/75

    Doesnt work for all

    That trick only works for Ruby built with --disable-shared (no libruby.so)

    Ruby built with --enable-shared (withlibruby.so) doesnt work like that.

    Code in libruby.so calls rb_newobj via thePLT.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    52/75

    How the plt works

    0x7ffff7afd6e6

    .got.plt entryInitially, the .got.plt entry containsthe address of the instruction after

    the jmp.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    53/75

    How the plt works

    0x7ffff7afd6e6

    .got.plt entryAn ID is stored and the rtld is

    invoked.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    54/75

    How the plt works

    0x7ffff7b34ac0

    .got.plt entryrtld writes the address of

    rb_newobj to the .got.plt entry.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    55/75

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    56/75

    Hook the GOT

    Redirect execution by overwriting the .got.plt

    entry for rb_newobj with a handler functioninstead.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    57/75

    0xdeadbeef

    .got.plt entryVALUE other_function(){

    VALUE new_obj = rb_newobj(); /* set up tracking of new_obj */ return new_obj;

    }

    Hook the GOT

    NO, it isnt. other_function() lives in memprof.so, so itscalls to rb_newobj() use the .plt/.got.plt in memprof.so.

    As long as we leave memprof.so unmodified, well avoid aninfinite loop.

    WAIT... other_function() calls rb_newobj() isnt that an infinite loop?

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    58/75

    Hook add_freelist

    Were now tracking objects at the time ofcreation.

    In order to find leaks we need to track whenobjects get freed too.

    add_freelist is called in the VM when anobject is freed.

    Why not just overwrite call instructions orhook the GOT?

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    59/75

    Hook add_freelist

    Cant because add_freelist is inlined:staticinlinevoid

    add_freelist(p) RVALUE*p;

    {p->as.free.flags =0;

    p->as.free.next= freelist;freelist = p;

    }

    The compiler has the option of

    inserting the instructions of thisfunction directly into the callers.

    If this happens, you wont see any calls.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    60/75

    So... what now? Look carefully at the generated code:

    staticinlinevoidadd_freelist(p) RVALUE*p;

    {p->as.free.flags =0;

    p->as.free.next= freelist;

    freelist = p;}

    Notice that freelist gets updated.

    freelist has file level scope.

    hmmmm......

    A ( t id) id

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    61/75

    A (stupid) crazy idea

    freelist has file level scope, so it lives at somestatic address.

    add_freelist updates freelist, so...

    Why not search the binary for mov instructionsthat have freelist as the target!

    Overwrite that mov instruction with a call to

    our code!

    But... we have a problem.

    The system isnt ready for a call instruction.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    62/75

    Isnt ready? What? The 64bit ABI says that the stack must be

    aligned to a 16byte boundary after any/allarguments have been arranged.

    Since the overwrite is just some random mov,no way to guarantee that the stack is aligned. If we just plop in a call instruction, we wont

    be able to arrange for arguments to get put inthe right registers.

    Must save caller saved registers.

    So now what?

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    63/75

    jmp Can use a jmp instruction. call saves a return address

    jmp does not.

    Transfer execution to anassembly stub that sets thesystem up according to the ABI.

    then do the call to the Chandler dont forget to jmp back when

    handler is done!

    this instruction updates the freelist and comes from

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    64/75

    this instruction updates the freelist and comes fromadd_freelist:

    Cant overwrite it with a call instruction because thestate of the system is not ready for a function call.

    The jmp instruction and its offset are 5 bytes wide.Cant grow or shrink the binary, so insert 2 one byte

    NOPs.

    address of assembly stub

    this instruction updates the freelist and comes from

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    65/75

    this instruction updates the freelist and comes fromadd_freelist:

    Cant overwrite it with a call instruction because thestate of the system is not ready for a function call.

    The jmp instruction and its offset are 5 bytes wide.Cant grow or shrink the binary, so insert 2 one byte

    NOPs.

    must jump back here

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    66/75

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    67/75

    Sample Outputrequire'memprof'Memprof.startrequire"stringio"

    StringIO.newMemprof.stats

    108 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:__node__

    14 test2.rb:3:String

    2 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:Class

    1 test2.rb:4:StringIO

    1 test2.rb:4:String

    1 test2.rb:3:Array

    1 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:Enumerable

    require'memprof'Memprof.startMemprof.track(/tmp/file) {

    do_something}

    Or just track a block

    Or dump the entire heap as JSON

    require'memprof'Memprof.startdo_stuff

    Memprof.dump_all(/tmp/file)

    object count file, line number, class name

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    68/75

    Middleware Use memprof as middleware

    Get per-request object count information

    569 lib/ruby/1.8/yaml.rb:133:String528 gems/sequel-3.9.0/lib/sequel/model/base.rb:393:__node__522 gems/haml-2.2.20/lib/haml/precompiler.rb:545:String522 gems/haml-2.2.20/lib/haml/helpers.rb:135:String522 gems/haml-2.2.20/lib/haml/helpers.rb:135:ActiveSupport::SafeBuffer507 gems/haml-2.2.20/lib/haml/precompiler.rb:317:String488 gems/sequel-3.9.0/lib/sequel/adapters/mysql.rb:410:String445 lib/ruby/1.8/yaml.rb:133:YAML::Syck::Node432 gems/haml-2.2.20/lib/haml/precompiler.rb:566:String

    406 gems/sequel-3.9.0/lib/sequel/model/base.rb:392:__node__

    require'memprof/middleware'MyApp::Application.configuredoconfig.middleware.useMemprof::Middleware

    end

    rails 3, environment.rb:

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    69/75

    memprof.com

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    70/75

    memprof limitations

    only works on amd64 linux and snow leopard

    only works with MRI and REE 1.8

    only works on binaries that are NOT STRIPPED.

    OSX System Ruby is NOT supported (yet).

    support for EY rubies is forthcoming - you willhave to install -dbg packages, though.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    71/75

    More evil is brewing

    We have some crazy, scary, stupid ideas thatwe think youll love.

    Stay tuned to find out what they are.

    1.9 support is one of the ideas.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    72/75

    Use RVM.

    This would have been really hard to test onall the different Ruby binaries without RVM.

    Use it. Donate money. (Not my project).

    http://rvm.beginrescueend.com/

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    73/75

    Get memprof

    This talk was about the memprof Ruby gemwhich is free and provides text output.

    github.com/ice799/memprof

    gem install memprof

    #memprof on irc.freenode.net

    memprof.com is separate and visualizes theoutput from the memprof gem.

    memprof.com is in alpha.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    74/75

    Special Thanks

    Aman Gupta (@tmm1) - web ui, json output, andmuch more

    Jake Douglas (@jakedouglas) - mach-o layer, bugfixes,and more.

    Brian Lopez (@brianmario) - because hes cool.

    Brian Mitchell (@binary42) - for convincing me to dothis by telling me I wouldnt and was too scared.

  • 8/14/2019 Descent into Darkness: Understanding your system's binary interface is the only way out.

    75/75

    Questions ?

    @joedamato

    timetobleed.com

    github.com/ice799