tmxklab
[Pwnable.xyz] TLSv00 본문
1. 문제
nc svc.pwnable.xyz 30006
1) 문제 확인
- menu 1 : 키를 재생성하는 메뉴
- menu 2 : 플래그를 로드
- menu 3 : 플래그 값을 출력(하지만 안됨)
2) 함수 확인
- 특이하게 real_print_flag()와 print_flag()가 존재
2-1) 메인 함수
- line 8 : generate_key함수 실행
- line 18 ~ 20 : 2를 입력한 경우 load_flag함수 실행
- line 24 ~ 28 : 1을 입력한 경우 generate_key함수 실행
- line 30 ~ 32 : 3을 입력한 경우 print flag함수 실행
2-2) generate_key() - menu(1)
- line 11 : s변수를 0x48만큼 0으로 메모리에 세팅
- line 12 : /dev/unrandom파일을 열어 fd에 파일디스크립터 값 저장
- line 13 ~ 16 : 열지 못한 경우 종료
- line 18 : read함수를 통해 s변수에 fd가 가리키는 파일의 내용을 64bytes만큼 저장
- line 19 ~ 23 : read함수를 통해 한 바이트씩 fd가 가리키는 파일의 내용을 s[i]에 저장
- line 24 ~ 25 : strcpy를 통해 s값을 key변수에 복사
2-3) load_flag() - menu(2)
- line 6 : /flag파일을 열어서 파일디스크립터 값을 fd에 저장
- line 7 ~ 9 : 열지 못한 경우 종료
- line 12 : /flag파일의 내용을 flag변수(bss영역)에 64bytes만큼 저장
- line 13 ~ 14 : flag[i] = flag[i] ^ key[i] (xor연산)을 64번반복(0x3F == 63)
2-4) print_flag() - menu(3)
- line 6 : do_comment(bss영역 변수)의 값을 result에 저장
- line 7 ~ 12 : do_comment값이 존재하지 않으면 getchar()를 통해 y를 입력하여 f_do_comment함수의 반환 값을 do_comment에 저장
2-5) f_do_comment()
- line 8 : buf에 33bytes까지 표준 입력을 받음
2-6) real_print_flag()
- flag값(bss영역)을 출력
3) 메모리 보호기법 및 파일 정보 확인
2. 접근 방법
1) 로직
1-1) generate_key() - menu(1)
- key값의 사이즈를 사용자의 입력으로 받는다. → generate_key(input)
- s변수를 72bytes만큼 0으로 세팅한다. → memset(s, 0, 0x48)
- /dev/urandom파일을 열어 난수 값을 s변수에 저장한다.(크기는 input값 만큼) → read(fd, s, input)
- key변수에 s변수의 내용을 저장한다.
- 결국 key변수에 input크기만큼 난수 값을 저장
1-2) load_flag() - menu(2)
- /flag파일을 연다. → fd = open("/flag", 0 );
- /flag파일의 내용을 flag변수에 64bytes만큼 저장한다. → read(fd, flag, 0x40);
- flag배열과 key배열간에 1byte씩 xor연산하여 flag배열에 1byte씩 저장한다. flag[i] ^= key[i]; (이때, i는 0 ~ 63까지 반복)
1-3) print_flag() - menu(3)
- result변수에 do_comment의 값을 저장한다. → result = do_comment;
- do_comment값이 존재하지 않는 경우
(1) y를 입력하게 되면 f_do_comment()의 주소 값을 result에 저장한다.
(2) y를 입력하지 않으면 result에 do_comment값이 저장된다.
- 반환 값으로 result를 준다. → return result;
위 1~3까지는 사용자가 사용할 수 있는 함수들이다.
하지만, main함수에서 real_print_flag함수를 호출하는 부분은 존재하지 않는다.
의문점이 드는 부분이 print_flag함수에서 result값을 반환 값으로 주는 것이며, y를 누른 경우 result에 굳이 f_do_comment함수의 주소 값을 넣는 것이다.
또한, real_print_flag함수에서 flag값을 출력해주는데 flag주소는 bss영역에 존재하며 load_flag함수에서 사용하는 flag변수 값과 동일하다!!
결국에 어떻게 real_print_flag함수를 실행시켜도 flag변수에 flag값이 들어있지 않으면 출력되지 않는 것이다.
그러면 먼저 real_print_flag함수를 어떻게 호출할 수 있을지 생각해보자
아까 전에 print_flag()에서 f_do_comment값을 result에 넣고 반환 값으로 넣는다고 했다.
굳이 f_do_comment주소 값을 넣는다 길레 주소 값을 확인해본 결과..
- real_print_flag함수와 마지막 한 바이트만 차이가 난다.
그럼 return에 real_print_flag함수의 주소 값을 넣고 싶을 것이다.
그러기 위해서 0xB1F에서 1F대신에 00으로 채워 넣어야 한다.
00으로 채워 넣기 위한 로직이 어디있을까 찾아보던 중
main함수의 처음 generate_key()를 호출하는 부분에서 의문이 들었다.
- main() line 8 : 파라미터로 63을 받음
generate_key함수에서는 key의 size값으로 64까지 받을 수 있는데 굳이 63을 받는 것이다.
- generate_key() line 9 : 1~64까지 입력으로 받을 수 있음(a1은 파라미터)
그 이후에 read()와 strcpy()를 사용하여 난수 값을 key변수에 저장한다.
- generate_key() line 18 ~ 24 : 난수 값을 s변수에 a1만큼 read한 후 다시 key변수에 s변수의 내용을 복사
그 전에 read()와 strcpy()에서 문자열을 다룰 때 차이가 난다고 배웠다.
read(fd, s, a1) : fd파일의 내용을 s변수에 a1까지 저장한다. 이 때, 문자열 마지막에 널 값을(0x0)을 넣지 않는다.
strcpy(key, s) : key변수에 s변수의 내용을 복사하여 저장한다. 이 때, 문자열의 마지막은 널 값이므로 널 값이 나올 때까지 읽어서 널 값까지 포함하여 저장한다.
결론적으로 만약, size값을 63으로 넣는 경우 s변수에는 63bytes의 데이터가 저장되지만 key변수에는 1byte(0x0)를 추가한 64bytes가 저장하게 된다.
이제 디버깅을 통해 확인해보자
2) 디버깅
2-1) generate_key(63)
- $rbp-0x50 : s변수, 63바이트까지 값이 존재하며 s[64]에는 널 값임
- 0x8202040 : key변수, s변수 모두 다 받음 근데 마지막에 널 값까지 포함됨
2-2) generate_key(64)
- $rbp-0x50 : s변수, 64바이트까지 값이 존재하며 s[65]에는 널 값임
- 0x8202040 : key변수, 64bytes를 파라미터로 넣엇으면 do_comment부분에 널 값 한 바이트가 들어가야되는데 원래 do_commnet가 0x0으로 세팅되어 있어서 값이 변하는지 확인할 수 없음
- 그래서 확인할 수 있는 방법이 do_comment부분에 값을 채워넣고 다시 64bytes를 넣는 방법임(do_commnet변수를 다루는 곳은 print_flag함수 부분)
2-3) print_flag() - do_comment
- print_flag() line 11 : d_comment에 f_do_comment()의 주소 값을 넣는 부분
확인)
- do_comment변수에 f_do_comment의 주소 값이 들어가 있다.
이제, 다시 generate_key()에 64를 넣어 진행해보자
원래는 0x800b1f값(f_do_comment주소 값)이 들어 있어야하는데 read함수와 strcpy함수를 거치면서 널 바이트(1byte)가 더 추가되면서 0x800b00으로 변경되었다.
그리고 0x800b00은...
즉, do_comment에 real_print_flag함수의 주소 값이 저장되며 결국에 print_flag함수의 ret에는 real_print_flag함수의 주소 값이 들어가게 된다.
2-4) real_print_flag() - flag
print_flag()의 ret의 값을 변경하였으니 이제 real_print_flag()를 실행해보자
주의할 점)
- generate_key()에 64를 넣어 do_comment가 real_print_flag()의 주소 값으로 변하였으므로 print_flag()에서 y를 누르면 다시 f_do_commnet()의 주소 값으로 덮어지므로 y를 누르면 안된다.
printf_flag()에서 y를 누르지 않고 ret까지 오면 call rdx를 통해 real_print_flag()를 실행한다.
하지만,
맞다 flag값을 구해야되는구나ㅋㅋㅋ
2-5) load_flag() - xor
flag값은 key값과 1byte씩 xor연산을 통해 결정되는 것을 확인하였다.
< load_flag() line 13 ~ 14 >
< flag^=key(xor연산) >
flag | key | flag(xor연산 결과) |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
위 표에서 왼쪽 flag(원본 flag)와 오른쪽 flag(변환된 flag)가 변하지 않는 것을 볼 수 있다.
즉, key값이 0이면 flag값은 변하지 않으므로 원본 flag값이 나오게 된다.
이제, 해야할 일은 key변수에 어떻게 64bytes만큼 0으로 세팅할 것인가?이다.
사용자가 입력으로 받을 수 있는 것은 key의 size값이므로 size값을 변경하면서 확인해보자
size값 1을 넣은 경우)
size값 2를 넣은 경우)
size값 3을 넣은경우)
rpb-0x50 : s[72]
0x8202040 : key변수
- key변수에 s변수의 값이 저장되면서 1byte(0x00) 더 추가되어 들어가는 것을 확인할 수 있다.
- 결론으로 key변수에 64bytes만큼 0으로 채워넣을 수는 없다.
- 하지만, key배열의 1 ~ 63인덱스에 부분적으로 0x0으로 채워넣을 수 있으며 1byte씩 64번 반복하여 출력하다 보면 flag값을 얻을 수 있을 것이다!
결론)
- generate_key()에 64를 파라미터로 주어 print_flag의 ret를 real_print_flag()의 주소 값으로 바꾼다.
- generate_key(), load_flag(), print_flag()를 64번 호출(인덱스 값은 1~63까지)
3. 풀이
익스 코드를 통해 64번 반복하여 호출하도록 실행해보자
공격 코드)
from pwn import *
#context.log_level="debug"
r = remote("svc.pwnable.xyz", 30006)
flag = ""
# 1. print_flag() : do_comment = f_do_comment()
r.sendafter('> ', '3\n')
r.sendafter('instead? ', 'y\n')
# 2. generate_key() : Input 64bytes
r.sendafter('> ', '1\n')
r.sendafter('len: ', '64\n')
# 3. Input(1 ~ 63)
for i in range(1, 63):
# 3.1 generate_key() : input 1 ~ 63
r.sendafter('> ', '1\n')
r.sendafter('len: ', str(i)+'\n')
# 3.2 load_flag() : key[i] = 0x00(flag[i] ^= key[i])
r.sendafter('> ', '2\n')
# 3.3 print_flag() -> real_print_flag() : print flag[i]
r.sendafter('> ', '3\n')
r.sendafter('instead? ', 'A\n')
data = r.recv(64)
#print(data[i])
flag += data[i]
print(flag)
r.interactive()
공격 실행)
앞에 1byte는 flag[0]은 못구하니깐 F라고 유추해볼 수 있는데 저거 바로 대입하면(중괄호 닫는 부분까지)안됨 thdnk가 아니라 think임
1byte씩 깨지는 부분이 있는데 왜그러는지는 모르겟음
계속 하거나 아니면 문자열 추측해서 답 제출하면 성공함
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] I33t-ness (0) | 2020.04.09 |
---|---|
[Pwnable.xyz] Jmp_table (0) | 2020.04.09 |
[Pwnable.xyz] two target (0) | 2020.04.09 |
[Pwnable.xyz] xor (0) | 2020.04.09 |
[Pwnable.xyz] note (0) | 2020.04.09 |