#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
이번 코드는 이렇다. 포인터를 갓 뗀 초보인데 어려운 코드가 나왔다.
우선 모르는 것들을 정리해보자.
(signed) char | 1byte | -128 ~ 127 |
(signed) int | 4byte | -2,147,483,648 ~ 2,147,483,647 |
unsigned long (int) | 4byte | 0 ~ 4,294,967,295 |
const (자료형) (상수명) = (상수값) // const int MAX = 1000;
원래는 위와 같이 쓰지만 자료형 앞에 붙는 경우에는 상수만을 가리키는 포인터가 된다
const char* p : p는 상수만을 카리키는 포인터. p가 가리키는 위치는 변경할 수 있지만, 값은 변경할 수 없음. (값고정)
int* const p : 이때 p는 가리키는 위치는 변경할 수 있지만, 가리키는 값은 변경 불가능. (위치고정)
int* a; // int * a; // int *a;
처럼 *(asterisk) 의 위치는 상관없다고 한다. 그럼 (int*) p는?
p에 저장된 정수 값을 주소로 변환한 후 그 주소값을 리턴하는 것이다.
코드를 살펴보니 0x21DD09EC 에 맞게 인자를 입력해야되는것 같다.
하지만 20글자를 맞추지 않으면 안되는 것 같은데.... 문제는 check_password 함수의 범위가 다르다는 것.
char형으로 인자를 받아오지만 int로 저장하는 것 같다.
0x21DD09EC == 568,134,124 이다.
다음부터는 write-up을 참고했다. 너무 어렵다.
res를 0x21DD09EC로 맞춰놔야 하므로 X x 5 = 0x21DD09EC 가 나와야한다.
X = 0x06C5CEC8 이다.
하지만 실제로 5로 나눴을 때는 소수점으로 떨어진다. float 형이 아니라서 나머지는 버려지나 보다.
그렇다면 0x06C5CEC8 * 5 + 0x4 = 0x21DD09EC 라는 것이다.
0x06C5CEC8 + 0x06C5CEC8 + 0x06C5CEC8 + 0x06C5CEC8 + 0x06C5CEC8 + 0x00000004 = 0x21DD09EC 인데
실제 wirte-up은
0x06C5CEC8 + 0x06C5CEC8 + 0x06C5CEC8 + 0x06C5CEC8 + 0xC6C5CECC = 0x21DD09EC 가 답이라는 것이다.
Q. 굳이 더해줄거면 둘의 차이는 뭐지?
잘 모르겠다. 우선 해설대로 진행해보면
col `python -c 'print "\xC8\xCE\xC5\x06"*4+"\xcc\xce\xc5\x06"'`
를 입력하란다. Little Endian (리틀 엔디안) 때문에 반대로 입력한다고 한다.
리틀 엔디안이란 낮은 주소의 데이터를 낮은 바이트(LSB, Least Significant Bit)부터 저장하는 방식이다.
평소 우리가 숫자를 사용하는 선형 방식과는 반대로 거꾸로 읽어야 한다. 대부분의 인텔 CPU는 이 방식이라고 한다.
daddy! I just managed to create a hash collision :)
우선 flag는 추출할 수 있었다.
하지만 아까 의문처럼 전자인 4를 더하는 경우를 시도해보았다.
무슨 문제인지 모르겠다.
python -c print에 대한 내용도 찾아보려 했지만 잘 나오지 않았다.
정리
Q1. 입력 둘의 차이는 무엇인가?
long check_password 함수는 하나의 문자열을 받아 ip라는 int형 포인터를 생성한다.
이때 char(1byte)를 int(4byte)에 담기 위해서 4글자씩 짤라서 5번째까지 더하고 그값을 리턴한다
for 문의 반복횟수가 5번이기 때문에 6번을 더해주는 전자 방법은 안된다고 생각한다.
하지만 16번을 곱하는 번외 풀이는 어떻게 되는 걸까?
Q2. python -c print의 의미는?
-c (command) : 컴파일 없이 바로 실행시키는 옵션
`(backtick) : main함수의 인자를 받을 때 문자열로 받음. 따라서 ascii 코드로 값이 들어가기 때문에 실제 값 자체를 넣어주기 위해 사용. 백틱사이에 넣는 모든 것들은 메인 함수 전에 쉘에 의해 실행
Q3. 왜 collision 인가?
풀고 보니 큰 상관은 없는 내용이다. 원래는 MD5 Hash Collision이라고 한다.
'pwnable' 카테고리의 다른 글
[Toddler's Bottle] fd (0) | 2024.05.26 |
---|---|
[Toddler's Bottle] bof (0) | 2024.05.11 |
[Toddler's Bottle] flag (0) | 2024.05.11 |
[Toddler's Bottle] passcode (0) | 2024.05.11 |
[Toddler's Bottle] random (0) | 2024.05.11 |