Intro to Information Security 2 | Capture The Flag and Operating System Vulnerabilitiesο
1. Capture the Flagο
(1) Introο
In this project we are going to view some capture the flag (CTF) problems. The first question should be easy and you should add the ID number to e.py
script by vim.
(2) Buffer Overflow 1ο
In this case, we have a easy case of buffer overflow. The payload
is a input used for the buffer
and buffer
can be found in the file flag.c
. To exploit this vulnerability, we have to build a payload
with enough lenth to rewrite the data on the memory.
Itβs also good to know some gdb
commands for this case,
break <line>
orb <line>
: set the breakpoint to line number<line>
run
orr
: run the program to the next breakpointstep
ors
: go to the next line executed in the functionnext
orn
: go to the next line executed and skip the functioncontinue
orc
: continue running to the next breakpoint or to the endprint
orp
: print the value of a variable
In this case, we need to run the following command to trigger the GNU debugger,
$ python3 e.py dbg
(3) Buffer Overflow 2ο
Now, letβs come to the next challenge. In this case, we have a e.py
similar to the last one and we also have a flag.c
script. However, in the flag.c
script, we can find out that we need to bypass the current unsafe()
function and return to the can_you_reach_me()
function if we want to capture the flag. So the first task is to find the return address of the unsafe()
function.
First, we can make a long cyclic as the payload
and then use gdb
to find the space we want. In the gdb
page, we can press c
to continue to the SIGBUS
error where we can spot the Invalid address
. p64()
function can change that address back to the cyclic section so that we can then use cyclic_find
to get its position. To check if we got the return address correct, we can append p64( 0xdeadbeef )
to the end and use gdb
to check if we return to the address 0xdeadbeef
. If we now return to this address, we finished our first step.
Second, we have to find out the address we have to return. To view the address, we have to check the assembly code of the executable file flag
by,
$ objdump -D flag > flag.asm
And then we use vim
and /
to search for the function we need. Here we have to search for can_you_reach_me
. From the line with the bl
command, we can get the address we need to return. The returned address should be the hex before the :
sign. So finally, we can create a payload
with the cyclic offset plus the return address we need.
(4) Understanding Assemblyο
In this case, we can to figure out the right order for the assembly code to come up with the address for the flag. So again, we have to convert the executable flag
to its assembly by,
$ objdump -D flag > flag.asm
If we also look into the flag.c
script, we can figure out the callback of call_me
is stored in a variable called caller
. Then in the assembly code, we can search for this caller
and record its address.
Next, we have to figure out the right order of the assemblies. Here are some functions of the ARM assembly instructions,
MOVZ A, #N
: replace the current value in registerA
with instant number#N
. The bits not assigned will be 0s.MOVK A, #N, lsl M
: put the instant number#N
to registerA
and then logical shift left forM
bits. The other bits in the register remains the same.LDR A, [B]
: value in address found at registerB
is loaded to the registerA
STR A, [B]
: value in the registerA
is stored at the address found at registerB
STP A, B, [C]
: move the sp pointer to the address found at registerC
and then write the value ofA
andB
after thatret
: return the the address at registerx30
(5) Bad Random Caused By Leakο
Based on the flag.c
and also run the executable flag
, we can figure out in this case that the leaked part of the base address system
has 8 valid bytes and we have to figure out the rest 4 bytes.
Second, if we try out the executable flag
a few time, we can find out the the last 3 bytes of the base address never change. Then in this case, the only guess we have to make is the remaining 1 byte.
Then we have to edit the e.py
script and create a loop to guess the address for several times. When we make a correct guess, we are able to capture the flag. The following tricks are useful while editing the script.
p = process('./flag')
: create a process executing the command./flag
p.recvline()
: get one line from outputp.sendline(payload)
: send the payload to the processp.recvall()
: get all the output. It will hang up if we have to wait for an inputp.close()
: close the current process
(6) Get Stringsο
The next trick is that we can exploit the strings coded in the executable file. To do that, we can use strings
command in Linux to output all the readable strings in a file,
$ strings flag > flag_str
Then we can use vim
to check the flag_str
file and find out the answers. The assumption is that we commonly have related strings appeared together in the code.
(7) Client and Serverο
The solution for this problem is similar to the buffer overflow 2 one and itβs a good idea to operate a similar procedure. However, the difference is that we have a client sending messages to the server and the flag is stored on the server. In spite of this, we can also rewrite the return address on the client.
(8) XOR Trickο
In this problem, we have to read the script flag.c
first and then understand the logic of the for
loop. For the bitwise operations, hereβs a quick reference,
^
: XOR|
: OR&
: AND>>
: Shift right<<
: Shift left
After we figure out the meaning of the flag.c
script, we can easily build a payload that has a 0xefbeadde
value for XORbius
variable based on the following formula,
0 ^ a = a
Then we have to build a payload that gives us the result that we need to pass the if
check. In this case, we will have the following equation where both c1
and c2
are constants,
x ^ c1 = c2
In order to solve this equation, we will need the following property for XOR,
a ^ b ^ a = b
Therefore, we can solve the equation above by,
c1 ^ x ^ c1 = c1 ^ c2 = x
So,
x = c1 ^ c2
Here are also some tools that can help on solving this problem.
Also, because we are using python for payload, we can build characters by,
chr()
: decimal to char by ASCIIord()
: char to decimal by ASCII
(9) Buffer Overflow 3 (pointy_pointy_point)ο
This problem highly relies on Math and gdb
. This problem is very similar to the buffer overflow problems we have met. Here are some hints for this problem,
To bypass the
protector
, we have to do some calculationsIn order to pass the check, we have to put
0x0badf00d
somewhere and then make the pointer point at it.For the calculation of
*(&protector + some_other_value)
, we have to overwritesome_other_value
by buffer overflow and then make this address the address we want.To bypass the
characters_read
check, we can not use buffer overflow to rewrite this variable because it will redo the calculation. We can think about putting some Null charactersb'\x00'
in the buffer so that it will not detect the buffer overflow issue.Both x86 and ARM is little endian.
(10) Hunt Then Ropο
The final task is a combination of two tasks. The first one is to find the vulnerable file by verifying the sha256 checksum for each file. The second one is to find out how to crack the program.
For the first question, we can output the sha256 checksums of all the files in the current directory by the command line mentioned in this post. The output format can be different so I wrote a python script to check the difference, but there can be many other ways to deal with that issue. Make sure to ignore the files readme
, user.txt
, and checksum
, and the other file left should be the one with vulnerabilities.