Hello, this is Dr. Miller, and this
is Episode thirteen point two of
Assembly.
Today, we're going to talk about the
reverse engineering process and then
give a simple example,
the reverse engineering process.
So this dissembler is a program
that translates machine language
into assembly language.
So we take the raw binary and
we create assembly out of that.
And then it's a process of taking
a binary program and generating
a view of a program that could be
executed.
And there are a lot of different
caveats to that on people who
try and mess up this assemblers and
make things that
don't disassemble well.
So the disassembly process, so
what it does is it's going to read
in the binary data and this
really depends on the format that
the data is stored in based on
the OS and then determine
the layout and references.
So once you determine which
architecture it's for, you can
determine what the entry point is or
where it's going to start executing
dynamic libraries.
So what libraries are going to be
loaded into this binary
that it's allowed to call and then
system calls how the binary will
interact with the system
if it's not running as root,
and then it'll start disassembling
instructions and it will start at
the entry point.
So step one, reading binary data.
So this is Sembler is going to use
magic numbers in order to guess at a
format. So oftentimes
this is just for convenience so
that when you double click or
open a binary that
this is assembler grabs the right
one. So that way
it's it's easier and you don't have
to select that as an option.
And then that is going to be based
on the operating system and
the method of loading the binary.
So it will load in the data.
It will make sure that
it reads it into the correct
structures. So that way it can
then start analyzing that.
And when it analyzes, it will
determine determine offsets.
It will look at the architecture
and the operating systems.
And so then we'll understand
what memory locations we need to
start looking at.
So as it's determining the layout
and the references, it figures out
what the entry point is, and so this
is going to depend on our different
formats.
So for Linux, Mocho
for OS 10 and P
for Windows.
And then dynamic libraries
are specified in that binary
format and loaded by the operating
system as a binary is loaded
and so the dissembler will go ahead
and understand what those are.
So that way it can
read in the data and understand
what functions we have and what
functions or can be called.
And then system calls are going to
depend on which OS.
And so I'm enwrapped
on Linux is going to be different
than an interrupt on Mac OS
and system calls are going to be
done in a different way based on
each OS and the parameters might
vary depending on
which OS it's going to be loaded on.
So it'll go ahead and then and start
disassembling instructions, so
the typical way that it works is
it'll start at the entry point, look
up the instruction length.
So if you're going to be in thumb
or arm or if you're going to be
in x 86, where
the instructions can have varying
length, you got to figure out how
long each instruction is.
You then disassemble that
instruction and then if there is a
branch that occurs, so if you see a
jump or a branch, then
you basically have to say, I can go
to the next instruction and I might
end up coming back depending on if
the branch is taken or it's not
taken.
And so the disassembles will add
these in order into a queue of
instructions that need to be
disassembled and then continue
on until all the instructions
in the queue are processed.
And we do have two different types
of dissemblers, so linear
sleep, linear sweep does
not start at the first.
It does start at the first
instruction and
it just basically goes to the next
instruction and doesn't look at the
branching and just goes
from the start until it gets to the
end.
And so it might disassemble
instructions that are not actually
instructions.
So then we typically like to see
something that is what we call
recursive descent.
So that's going to again start at
the first instruction and then it's
going to follow the different
possible paths that we have.
So if we have a jump or a branch or
call a return, those are going to
basically add two things that we two
different places that we could go
and then some functions may not
return. Right. So those will get
not shown or they'll stop
there. And then some offsets
might have some code that's going to
be hidden. And you have to
understand the instruction and
the architecture in order to find
those. So typically, a recursive
descent is a much better one.
A linear sleep is kind of a
lazy way of doing it, this sort of
simple way.
So here's a simple example,
so we have here a C program,
and all it does is the Put's is
going to print off to the console
Hello World, and then it's all
done in return zero.
So it's written in a high level
language that
programmers use to write
write low level code.
So somewhat low level, not not a
super high level one.
And so if we have that code
and we compile it and then
we go ahead and disassemble it so we
can use object dump in order to
disassemble that code, we can
see that it adds a lot of
information here.
So.
We can see here these are sort of
the headers for the
function here, so it's instructions
that are added by the compiler that
if we're compiling it by hand, we
may not add or may not need to add.
And then we can see here we have our
our pushy BP movie, PSP,
and then it saves X,
it pushes an offset on here for the
call to Put's.
And you can see that the dissembler
knows that puts is in the plot,
which is part of Linux, and
that when it calls that function
it's going to print something.
And so it's going to call the Put's
function and then we end up cleaning
up the stack and then doing our
return.
So if we take the same example, we
can see it in a sort of modern Ed.
So this is an Ghidorah and
we can see that it sort of took
the offset here that we saw
in binary in the previous one.
And it actually shows us, oh, by the
way, this is a
string, that's hello world.
And then we can see it says directly
call the puts and we can see
the function signature that is
associated with that.
And also it's going to do additional
highlighting, showing that when
it does this leha, this is parameter
one and then we see we have a
local variable that it
has determined for that.
And so all of this is going to
to be done automatically by
that type of disassembly.
And once again, here's another
view. So this is going to be in
binary ninja.
And again, we can see that
they all have different ways of sort
of showing the exact same
information. So here again,
we can see the string inside
of there and it's going to
use arge see.
So it's saying that it's saving Arc
C and then we
have a local variable and then we're
saving AVP and again,
cleaning up the stack and exiting.
So today, we talked about the
reverse engineering process and then
gave a simple example and then
viewed it in a couple of different
disassembles.
So looking forward, we'll
have an example to show you how to
see different programing constructs
and build a reverse engineering lab
so that we can see those different
constructs.
And then we'll also talk about sea
constructs.
If you have questions, you can email
me millirems, you, Ankita Aidoo,
and you can find me on Twitter at
House 30.