Write up (Wargame)/Pwnable

[pwnable.xyz] GrownUp 풀이

그믐​ 2022. 11. 1. 17:59
반응형

분석


checksec 결과

 

18세 이상인지 물어보고 아니면 종료시킨다,

 

IDA에선 이렇게 되어있고

이번엔 win 함수가 없다. 흑..

 

https://tribal1012.tistory.com/39

 

LOBYTE, BYTE1, HIBYTE 매크로

LOBYTE : 가장 하위 1바이트를 말함, 리틀엔디안으로 들어간 메모리 값 중 가장 오른쪽 값? BYTE1 : 리틀엔디안으로 들어간 메모리 값 중 오른쪽 2번째 값? BYTE2 : 리틀엔디안으로 들어간 메모리 값

tribal1012.tistory.com

 

Y인 경우에 이름을 입력받는데 동적할당한다.

해당 주소에 입력하고

usr에 strcpy를 한다(overflow가능?)

그리곤 welcome 이후에 printf한다.

 

printf에서 포맷스트링이 bss에 있다는 점이 이상하다. (gdb로 봐야할듯)

 

\

usr 뒤에 포맷스트링이 존재한다? 아니면 fsb이고 뭐..

 

근데 usr 크기가 128, 0x80으로 보이는데 read하는 바이트 수도 0x80이다. 이건 함 봐야겠다.

 

보니까 이미 %s가 있다. 뭔가 있더라도 bss에 포맷스트링이 있다는 점부터 이상하다.

 

그리고 bss를 살펴보니 flag가 있다.

 

여기에 적힐거라고 하는데.

어떻게 그런거징 암튼 여기 flag 주소만 읽으면 되는건가.

그러면 usr에 flag 주소를 넣으면 되겠네 .....?

 

라고 생각했으나 그러고보니 %s면 주소는 이미 usr 주소로 정해져 있고, 그러면 usr에 있는 값을 읽는 것(flag 주소를 읽음)밖에 안된다.

 

 

살펴보면 usr+128부분이 0x601168로 포맷스트링을 가리킨다.

0x601168에는 "%s\n"이 들어있다.

 

처음에는 이제 생각나는 방법이 fsb라서 0x80이 usr+128이후도 덮을줄 알고 포맷스트링을 flag도 돌리려고 0x80만큼의 사이즈를 넣었었다.

 

사실 'A'*0x78 + '%p %p' 이렇게만 했어서 그대로 출력되었는데 %p 부분에서 0x80을 못 맞춰서 그런가 싶어 

 

'A'*0x78 + '%p %p %p' 이렇게 해서 8바이트를 맞추주었는데 이건 또 포맷스트링 공격이 된다.

 

 

뭔가 이상해서 메모리를 봤는데, 

 

usr + 128부분이 0x601168이 아니라 0x601100으로 되어있었다.

이렇게 되면 문제가 생길 부분은 하나다. strcpy.

 

exploit


보통 strcpy를 사용하면 오버플로우때문에 문제가 되지만, 여기서는 어떻게 보면 overflow이나, null을 삽입한게 문제가 되었다.

 

src는 malloc을 해서 힙 주소에 0x0으로 가득 채워진 0x84? 만큼의 주소를 가진다.

 

strcpy는 null 부분까지 복사한다.

usr의 사이즈는 딱 0x80이다.

0x80만큼을 read 하게 되면 0x80만큼 src에 들어간다.

strcpy를 하게되면 알잘딱으로 0x80만큼 넣어주는게 아니라 null을 만날 때 까지, 그러니까 null도 카피하므로 0x81만큼을 usr에 넣게 된다.

 

그러면 usr의 범위인 0x80을 벗어나서 뒤에 있던 포맷스트링의 주소를 건들게 된다.

 

이제 0x80만큼 대입하면 포맷스트링 공격이 먹힌다는 것을 알았다.

 

그럼 이걸로 뭘 할 수 있을까.

그걸 알기 위해서 포맷스트링 공격 시에 출력되는 상황을 보자.

 

포맷스트링 시에 상황이다. 지금은 익스하는 코드가 들어간 뒤의 상황을 보고 있지만,

 

$1 부터 순서대로

rsi, rdx, rcx, r8, r9, rsp, rsp+8, rsp+16 ... 

이런 순서대로 나오게 된다는건 포맷스트링 공격을 공부했다면 알 것이다.

 

그럼 여기서 우리가 출력해야할 건 flag인데, 중요한건 레지스터에 주소가 있는 것과 스택에 있는 것은 다르다는 것이다.

 

레지스터에 주소가 있는걸 %p하면 해당 레지스터의 값을 출력한다.

rsp에 주소가 있으면 rsp(스택) 주소가 아니라, rsp에 들어있는 주소를 출력한다.

 

어떻게 보면 같은데.. 뭐..

 

rsp+16부터 우리가 처음에 인증하고자 대입했던 y명령어가 들어간다. 익스 전에는 그냥 y만 있지만 이걸 알아채야했다.

y/N 입력에서는 첫 바이트가 y이기만 하면 되고, 총 0x10 바이트를 입력받는다.

그러면 스택 사이즈에 맞춰서 첫 바이트를 y에 맞추고 flag 주소를 넣고

 

그러면 rsp+24에 있는 주소를 읽어야 하므로 %$9s를 하면 되겠다.

 

 

from pwn import *

#p = process('./GrownUpRedist')
p = remote('svc.pwnable.xyz', 30004)

flag ='y'*0x8 + p64(0x601080)

p.sendafter(': ', flag)

payload = 'A'*0x78 + '%9$sAAAA'
p.sendafter(': ', payload)

p.interactive()

 

 

반응형