wargame.kr의 14번째 문제인 php? c?다.
32비트 애플리케이션의 정수형 타입을 알고 있냐는 힌트가 주어진다. 32비트의 정수면 C언어에서는 -2^31 ~ 2^31-1까지가 아니었던가? 왠지 오버플로우를 통해 해결하는 문제일 것 같은데, 일단 Start 버튼을 눌러 문제를 확인해보자.
별다른 정보는 없고 두 수를 입력하여 try로 비교하는 것 같은데 일단 get source 버튼으로 문제를 확인해보자.
<?php
if (isset($_GET['view-source'])) {
show_source(__FILE__);
exit();
}
require("../lib.php"); // include for auth_code function.
if(isset($_POST['d1']) && isset($_POST['d2'])){
$input1=(int)$_POST['d1'];
$input2=(int)$_POST['d2'];
if(!is_file("/tmp/p7")){exec("gcc -o /tmp/p7 ./p7.c");}
$result=exec("/tmp/p7 ".$input1);
if($result!=1 && $result==$input2){echo auth_code("php? c?");}else{echo "try again!";}
}else{echo ":p";}
?>
간단한 코드로 POST로 받은 폼 데이터를 정수 자료형으로 변환하여 비교하는 것 같다. 그런데 첫번째 입력은 exec() 함수 호출을 통해 "/tmp/p7"에 위치한 파일의 매개변수로 사용되고 있다. 그리고 파일의 실행 결과를 두번째 입력과 비교하여 같은지 검사하고 있는데 이 p7 파일의 실행 결과를 우리가 어떻게 알 수 있을까?
일단 gcc로 컴파일되는 걸로 보아 p7 파일은 C언어 프로그램일 것이다. 컴파일 명령을 자세히 보면 컴파일된 실행파일은 /tmp 디렉토리에 저장되기 때문에 우리가 접근할 수 없지만 p7.c 소스 코드 자체는 ./ 디렉토리, 즉 현재 디렉토리에 저장되어 있기 때문에 접근해서 살펴볼 수 있다. 소스 코드를 열어보면 다음과 같은 것을 볼 수 있다.
#include <stdio.h>
#include <stdlib.h>
void nono();
int main(int argc,char **argv){
int i;
if(argc!=2){nono();}
i=atoi(argv[1]);
if(i<0){nono();}
i=i+5;
if(i>4){nono();}
if(i<5){printf("%d",i);}
return 0;
}
void nono(){
printf("%d",1);
exit(1);
}
간단히 보면 매개변수로 받은 정수값을 비교하여 1을 출력하거나 입력받은 정수값 자체를 출력하는데 1을 출력하면 php 구문에서 '$result != 1'에 실패하기 때문에 플래그를 출력하지 못한다. 그러므로 이 1을 출력하는 nono() 함수가 호출되지 않도록 해야 하는데 이를 위한 조건이 조금 이상하다.
-
입력받은 정수값(D1)이 0 이상인지 확인한다.
-
입력받은 정수값(D1)에 5를 더한다.
-
입력받은 정수값(D1+5)이 4 이하인지 확인한다.
-
입력받은 정수값(D1+5)이 5이하라면 이를 출력한다.
일반적으로 정수를 입력했을 때 0 이상인 정수에 5를 더하면 최소한 5가 된다. 그러므로 이 값이 4 이하인지 확인하는 조건에 걸리기 때문에 nono() 함수가 호출, 1이 반환된다. 음수를 입력했다면 0 이상인지 확인하는 조건에 걸리기 때문에 역시 nono() 함수가 호출된다. 그렇다면 이 조건들을 어떻게 우회할 수 있을까? 문제 힌트에서도 얘기했듯이 이는 int 자료형의 오버플로우를 활용하는 문제다.
#include <stdio.h>
int main()
{
int i=0;
for(i=0;i>=0;i=i+100000){
printf("%d\n", i);
}
printf("%d", i);
return 0;
}
오버플로우가 무엇인지 보이는 위의 코드를 보자. 이를 실행해보면 변수 i는 10만씩 증가밖에 하지 않기 때문에 i가 0 이상인 조건이 항상 해당되어 for 반복문이 무한히 동작할 것 같지만 어느순간 작동을 멈추고 프로그램이 종료된다. 그 순간이 바로 정수 자료형의 오버플로우가 발생하여 변수 i가 음수로 전환된 때다.
위의 실행 결과에서 볼 수 있듯이 100000씩 증가한 값은 32비트 int 자료형의 최댓값인 2^31-1, 즉 2147483647을 초과하여 -2147467296이란 음수값으로 변환되었다. 왜 이렇게 되는 것일까? 오버플로우의 원리에 대한 더 좋은 글은 차고 넘치니 간단하게 보이면 아래 그림과 같다.
컴퓨터에서는 음수를 2의 보수로 표현한다. 이는 비트로 나타낸 정수를 비트 반전(1은 0으로, 0은 1로)한 후 1을 더해서 음수로 표현하는 것인데 예를 들어 정수 7은 0111로 표현된다. 이를 비트 반전하면 1000이며 여기서 1을 더하면 1001인데 이를 컴퓨터에서 -7로 인식하는 것이다. 그렇기 때문에 위의 코드에서 변수 i를 증가시키며 int 자료형의 32비트를 11111....111로 모두 비트를 채워나가면 더이상 채울 곳이 없기 때문에 맨 위 비트인 부호 비트까지 침범하여 부호가 반전되는 것이다(int 자료형의 32비트에서 1비트는 0, 1로 음수/양수를 표현한다).
이렇게 양수가 음수로 바뀌는 특성을 활용하여 위의 p7 프로그램에 어떤 값을 전달해주면 될까? 일단 첫번째 조건인 0 이상인 값을 통과하기 위해 2147483643을 D1에 입력해준다. 이는 최댓값에서 4를 뺀 값인데 p7 프로그램에서 5를 더하면서 최댓값+1이 되기 때문에 오버플로우가 발생하여 이는 -2147483648이 된다. 이는 4 이하며 5이하이기 때문에 조건을 모두 통과하여 printf()로 출력되며 이 출력된 값은 php의 exec() 함수에서 인식하여 $result 변수에 저장된다. 이는 우리가 입력한 D2 변수와 비교되기 때문에 이 -2147483648을 D2에 입력해주면 된다.
입력할때는 HTML 태그 속성에서 최대 길이 제한을 없애야 이 긴 정수를 모두 입력할 수 있다.
실행 결과 어렵지 않게 플래그를 얻을 수 있었다.
이번 문제는 컴퓨터공학 1학년때부터 귀아프게 듣던 자료형의 최솟값, 최댓값의 오버플로우를 활용하는 문제였기 때문에 이게 되나? 싶어서 몇번 시도해봤더니 어렵지 않게 풀 수 있던 문제였다. 오버플로우를 방지하는 코드만 작성했지 이를 활용하는 측면은 처음이었기 때문에 좀 생소했는데 생각해보니 이런 시스템적인 측면을 활용하는게 포너블 문제들이 아닌가 싶다.
'챌린지 > Wargame.kr' 카테고리의 다른 글
bughela - web chatting (0) | 2021.01.04 |
---|---|
bughela - EASY_CrackMe (0) | 2020.12.31 |
bughela - img recovery (0) | 2020.12.28 |
bughela - type confusion (0) | 2020.12.27 |
bughela - tmitter (0) | 2020.12.26 |