tmxklab
[Pwnable.xyz] I33t-ness 본문
1. 문제
nc svc.pwnable.xyz 30008
1) 문제 확인
2) 함수 확인
2-1) 메인 함수
round_1,2,3()를 실행하여 반환 값이 모두 참인 경우 win함수 실행
+) round_1,2,3()의 로직은 다음 접근 방법에서 분석해보도록 하자
3) 메모리 보호기법 및 파일 정보 확인
2. 접근방법
1) round_1()
- '-'문자가 없는 v1과 v2를 입력으로 받는다.(음수를 사용할 수 없음)
- 다음 두 개의 조건 문이 참이면 통과한다.
1. v1 <= 1336 && v2 <= 1336
2. v1 - v2 == 1337
- 이 때, v2는 v4의 값을 int형으로 변환하며 v4변수의 데이터 타입은 int64, v2변수의 데이터 타입은 int
2) round_2()
- v1과 v2를 입력으로 받는다.
- 다음 조건 문이 참이면 통과한다.
1. v1 > 1 && v2 > 1337 && v1 * v2 == 1337
- 이 때, v1과 v2의 데이터 타입은 int형이다.
3) round_3()
- v2, v2+4, v3, v3+4, v4를 입력으로 받는다.
- 다음 2개의 조건문이 참이면 통과한다.
1. (for문) : v2 < (v2+4) < v3 < (v3+4) < v4
2. (return) : v2 + (v2+4) + v3 + (v3+4) + v4 == v2 * (v2+4) * v3 * (v3+4) * v4
3. 풀이
1) round_1() 풀이
우선 두 개의 변수가 '-'문자가 들어가면 안된다. 하지만, 저 문제를 풀기 위해 v2가 음수가 되어야 한다.(왜냐하면 1, 2번째의 조건 때문에)
위에서 v2는 v4를 통해 int형으로 변환하고 v4의 데이터 타입은 int64이며 v2의 데이터 타입은 int라고 하였다.
그리고 atoi함수를 통해 v4의 값을 int형으로 변환하여 v2에 저장한다.
각 데이터 타입에는 표현할 수 있는 범위가 있으며 표현할 수 없는 양수의 범위를 넘어서면 음수로 변한다.
다음 링크를 참조하자
int형의 범위 : -2,147,483,648 ~ 2,147,483,647
__int64형의 범위 : -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
따라서, 2,147,483,648 + 2,147,483,647를 더한 값을 v4에 입력으로 받으면 음수로 표현이 될 것이다.
그리고 v4는 __int64이므로 위의 두 개의 값을 더한 값이 범위를 초과하지 않으므로 값은 변하지 않을 것이다.
1-1) 4,294,967,295(=2,147,483,648 + 2,147,483,647)입력
(참고로 v1 - v2 == 1337이 되기 위해 v1의 값은 1336으로 입력)
v4[rbp-0x20]에는 값이 잘 들어가 있다.
v2 = atoi(v4)실행한 직후의 모습이다.
v2[rbp-0x34]에 0xffffffff값으로 변하였다.(int형은 4bytes이므로)
물론 if v2 ≤ 1336도 통과됨
이제 v1 - v2의 값을 eax에 넣고 0x539(1337)과 비교한다.
현재 rax는 v1[rbp-0x38]의 입력으로 0x538(1336)이 들어가 있다.
sub한 결과 rax는 0x539(1337)이 들어가게 되며
참이되어 round_1이 통과된다.
2) round_2() 풀이
이번에도 v1과 v2 입력을 두개 받는다.
그리고 위 조건으로 v1과 v2가 모두 양수여야 하며 단순한 방법으로는 절대 v1*v2가 1337이 될 수 없다.
하지만, v1과 v2의 데이터 타입이 모두 int형이며 round_1을 통해 int형의 범위를 알 수 있었다.
int형의 범위 : -2,147,483,648 ~ 2,147,483,647
위 조건을 통해 모두 양수여야 하므로 음수가 들어가면 안된다.
여기서 생각해 볼 수 있는 것이 v1과 v2를 곱한다는 것이다.
아무리 v1과 v2가 범위를 넘지 않아도 곱하게 되면 범위를 넘어설 것이다.
그리고 v1 * v2의 결과가 1337이 되려면 간단하게 1 * 1337을 떠오를 것이다.
그러면 곱해서 양수의 범위인 2,147,483,647을 넘고 음수의 범위인 -2,147,483,648을 넘어서 다시 1로 돌아오는 방법을 생각해낼 수 있다.
v1 * v2 = 2,147,483,648(음수의 범위) + 2,147,483,647(양수의 범위) + 1337 + 1(0도 포함됨) = 4,294,968,633
2-1) v1, v2입력(v1 = 3, v2 = 1,431,656,211 / v1 * v2 = 4,294,968,633)
3) round_3() 풀이
미리 디버깅하고 정리였으므로 어떻게 for문과 return에서의 조건이 위와 같이 나오는지 확인해보자
(IDA) round_3()
- 파악해야 할 것들
1. scanf의 파라미터
2. for문의 조건문
3. return에서 조건문
3-1) scanf의 파라미터 - 디버깅
scanf함수를 실행하기전에 파라미터 값들의(5개) 주소 값을 레지스터에 저장한다. 그리고 함수들의 파라미터는 뒤에서부터 가져온다.
그러면 v4 → v3+4 → v3 → v2+4 → v2 순으로 가져옴
- rdi <- v4[rbp-0x10]
- rsi <- (char)v3+4[rbp-0x14]
- rcx <- v3[rbp-0x18]
- rdx <- (char)v2+4[rbp-0x1c]
- rax <- v2[rbp-0x20]
십진수 | 32 | 28 | 24 | 20 | 16 |
변수 | v2 | v2+4 | v3 | v3+4 | v4 |
위와 같은 구조로 되어 있음(4bytes씩 끊어서 사용함)
이제, 1000, 2000, ..., 5000을 입력하고 확인해보자
v2[rbp-0x20] : 0x3e8(=1,000)
v2+4[rbp-0x1c] : 0x7d0(=2,000)
v3[rbp-0x18] : 0xbb8(=3,000)
v3+4[rbp-0x14] : 0xfa0(=4,000)
v4[rbp-0x10] : 0x1388(=5,000)
3-2) for문의 조건문 - 디버깅
다음은 for문에서 작동하는 부분이다.
round_3+120 : scanf실행 후 바로 다음에 실행되며 eax에 scanf의 리턴 값이 담겨져 있음(인자 5개 받았으므로 eax에 5가 들어있음) 그거를 다시 rbp-0x2c에 옮김
<for문>
- round_3+123 ~ 130 : 제어 변수인 i[rbp-0x30]에 1을 저장하고 점프
- round_3+164 ~ 172 : 제어 변수의 값을 증가하면서 0x4와 비교한 뒤 i와 0x4가 같거나 작으면 다시 round_3+132로 이동
- i는 총 1,2,3,4까지 커지므로 for문은 4번 반복
<if문>
데이터 타입은 DWORD이므로 4bytes
- round_3+153 ~ 155 : eax와 edx를 비교하여 edx가 eax보다 크거나 같으면 다시 for문 반복
- round_3+157 ~ 162 : edx가 eax보다 작으면 참이므로 main함수 종료
이제, eax와 edx에 어떠한 값이 담겨져있는지 확인해보자
<if문 - edx>
- round_3+132 ~ 137 : i의 값을 eax로 옮긴 후 edx에 [rbp+rax*4-0x20]의 값을 edx로 옮긴다.(즉, rbp+i*4-0x20이므로 rbp-0x20+i*4)
- i가 1~4까지 증가하면서 (rbp-0x1c, 0x18, 0x14, 0x10)
- edx는 if문의 이 부분에 해당
<if문 - eax>
- round_3+134 ~ 149 : i의 값을 eax로 옮긴 뒤 0x1을 빼주고 eax에 [rbp-rax*4-0x20]의 값을 옮긴다.(즉, rbp+(i-1)*4-0x20이므로 rbp-0x20+(i-1)*4
- i가 1~4까지 증가하면서 (rbp-0x20, 0x1c, 0x18, 0x14)
- eax는 if문의 이 부분에 해당
즉, if문을 통과하기 위해서 (edx < eax) 최종적으로는 다음과 같이 되어 있어야 한다.
[rbp-0x10] ≥ [rbp-0x14] ≥ [rbp-0x18] ≥ [rbp-0x1c] ≥ [rbp-0x20]
즉, v4 ≥ v3+4 ≥ v3 ≥ v2+4 ≥ v2 이렇게 되어야 한다.
확인(입력 값 - 1000, 2000, 3000, 4000, 5000)
3-3) return에서 조건문 - 디버깅
근데 HIDWORD라는 것은 처음 접해본다.
찾아보면 다음과 같다.
8bytes만큼 저장된 데이터에서 높은 주소의 4bytes만 가져오는 것같다.
따라서, _dword와 비교했을때 dword는 낮은 주소의 4bytes를 가져오고 HIDWORD는 높은 주소의 4bytes를 가져온다.
ex)
0xAAAAAAAABBBBBBBB
DWORD → 0xBBBBBBBB
HIDWORD → 0xAAAAAAAA
빨간색 부분 : [rbp-0x28]에 HIDWORD(v3) + (DWORD)v3 + HIDWORD(v2) + (DWORD)v2 + v4의 결과 값을 저장
노란색 부분 : [rbp-0x24]에 HIDWORD(v3) + (DWORD)v3 + HIDWORD(v2) + (DWORD)v2 + v4
주황색 부분 : if문으로 [rbp-0x28]과 [rbp-0x24]를 비교하여 같으면 참이되어 round_3가 종료된다.
[rbp-0x28] = [rbp-0x20] + [rbp-0x1c] + [rbp-0x18] + [rbp-0x14] + [rbp-0x10](v2 + (v2+4) + v3 + (v3+4) + v4)
[rbp-0x24] = [rbp-0x20] * [rbp-0x1c] * [rbp-0x18] * [rbp-0x14] * [rbp-0x10](v2 * (v2+4) * v3 * (v3+4) * v4)
즉 HIDWORD가 쓰이면 기존 변수 x의 x+4가 되어진다.
이제 위에 저 두 값이 참이 되어야한다.
디버깅을 통해 확인해보자(입력 값은 그대로 1000, 2000, 3000, 4000, 5000)
1000 + 2000 + 3000 + 4000 + 5000 = 15000이므로 0x3a98이 [rbp-0x28]에 저장됨
결국 round3가 참이되려면 다음 두 개의 조건이 참이 되어야 한다.
1. v2 ≤ (v2+4) ≤ v3 ≤ (v3+4) ≤ v4
2. v2 + (v2+4) + v3 + (v3+4) + v4 == v2 * (v2+4) * v3 * (v3+4) * v4
참이 되기 위해서 입력 값으로 "-2, -1, 0, 1, 2"를 입력하였다.
통과되어 win함수를 호출한다
++다른 방법으로 모두 0으로 세팅해도 됨
4) 공격 실행
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] fspoo (0) | 2020.04.23 |
---|---|
[Pwnable.xyz] Game (0) | 2020.04.13 |
[Pwnable.xyz] Jmp_table (0) | 2020.04.09 |
[Pwnable.xyz] TLSv00 (0) | 2020.04.09 |
[Pwnable.xyz] two target (0) | 2020.04.09 |