티스토리 뷰
Pwnable에는 많은 기법이 있는데, 그중에서 자주 쓰이는 기법들 중 Int Overflow라는 것이 있다.
그대로 말하면 정수타입에서 일어나는 Overflow취약점이다.
가령 아래와 같은 코드가 있다. (shell-storm.org에서 받을 수 있음)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> /* Version "secured" of the challenge */ void rules_explanation(void) { printf("==========================================\n"); printf("+> The rules are very simple: <+\n"); printf("+> The one that take the last match win <+\n"); printf("==========================================\n"); } int check_decision(char decision) // no cheat :( { return (decision >= 1 && decision <= 3); } int human_turn(int *nb_matches) { unsigned int decision = 0; char buffer[10]; printf("How many matches do you want to take ? (1-3)\n"); fgets(buffer, sizeof(buffer), stdin); decision = strtoul(buffer, NULL, 10); // string to unsigned long if (!check_decision(decision)) { printf("Are you trying to cheat ??\n"); return 0; } *nb_matches -= decision; printf("You've taken %i match(es)\n", decision); return 1; } void computer_turn(int *nb_matches) { unsigned int decision = 0; if (*nb_matches == 4) { decision = 4; *nb_matches = 0; printf("HUMMM\n"); sleep(1); printf("HUMMMMMMMMMMM\n"); sleep(1); printf("Wait, I got this !\n"); sleep(1); } else if (*nb_matches % 4 == 0) { decision = 1; *nb_matches -= decision; } else { decision = (*nb_matches % 4); *nb_matches -= decision; } printf("The computer has taken %i match(es)\n", \ decision); } int main(void) { // We set the number of matches srand(time(NULL)); char pass[80]; FILE *fd; int nb_matches = 10 + rand() % 40; // randValue : 10 ~ 50 int turn = 0; // Game settings rules_explanation(); printf("There are %u match(es)\n", nb_matches); // Now we begin the game while (nb_matches > 0) { if (turn == 0) { if (human_turn(&nb_matches)) { turn = 1; } else { turn = 0; } } else { computer_turn(&nb_matches); turn = 0; } printf("there are %i match(es) remaining\n",\ nb_matches); } if (!turn) // turn must be TRUE { printf("You lose !\n"); } else // Game win! get flag. { printf("How did you do this ?\n"); printf("password is : \n"); fd = fopen(".password", "r"); fgets(pass, sizeof(pass), fd); printf("%s", pass); printf("You win !\n"); } return EXIT_SUCCESS; } |
이 게임은 우리가 흔히 아는 베스킨 라빈스 31게임과 매우 흡사하다.
차이점은 성냥이란 것과 성냥이 10~50중 랜덤하게 나온다는것,
그리고 우리가 정확하게 0개를 만들어야 이긴다는 점이다.
그런데 베스킨 라빈스 31게임은 남은 개수중 4로 나눠서 나머지 값을 계속해서 부르면
무조건 이기게 돼있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ~> ./fire_me ========================================== +> The rules are very simple: <+ +> The one that take the last match win <+ ========================================== There are 15 match(es) How many matches do you want to take ?(1-3) 3 You've taken 3 match(es) there are 12 match(es) remaining The computer has taken 1 match(es) there are 11 match(es) remaining How many matches do you want to take ?(1-3) 3 You've taken 3 match(es) there are 8 match(es) remaining The computer has taken 1 match(es) there are 7 match(es) remaining How many matches do you want to take ?(1-3) 3 You've taken 3 match(es) there are 4 match(es) remaining HUMMM HUMMMMMMMMMMM Wait, I got this ! The computer has taken 4 match(es) there are 0 match(es) remaining You lose ! | cs |
그런데 컴퓨터가 그걸 놔 둘리 없음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | if (*nb_matches == 4) { decision = 4; *nb_matches = 0; printf("HUMMM\n"); sleep(1); printf("HUMMMMMMMMMMM\n"); sleep(1); printf("Wait, I got this !\n"); sleep(1); } | cs |
computer_turn() 함수에 이런 구문이 있음.
즉 4가 되면 무조건 유저가 이겨버리니 컴퓨터가 4개를 모두
가져가 버림;
그런데 우리는 check_decision() 함수 때문에 1 ~ 3까지 밖에 선택을 못함.
여기서는 절대로 게임을 이길 수 없게 만들어 놓았음.
1 2 3 4 | int check_decision(char decision) // no cheat :( { return (decision >= 1 && decision <= 3); } | cs |
그러면 우리는 어떻게든 check_decision()함수를 Bypass해서 게임을
이겨야 함.
그런데 여기서 한가지 이상한 점을 발견할 수 있음.
1 | unsigned int decision; | cs |
원래 decision변수는 Unsigned Int형임. 즉 0 ~ 2^32까지 포함 할 수 있음.
(signed int형은 -2^31 ~ 2^31, 4byte, 32bit)
그런데 check_decision(char decision)함수를 보면 desicion변수를
char형으로 받아버림. char형은 0 ~ 256까지임 (1byte, 8bit)
코드에서는 fgets()함수로 buffer에 10개의 문자열을 받아서
strtoul(string to unsigned long)함수를 통해 정수형으로 바꿔줌
(마이너스 값은 입력 불가)
우리는 10e9 - 1 (9999999999) 만큼 값을 입력할 수 있음.
그렇다면 우리는 char형으로 인식했을 때 1~3이여야 하며
unsigned int형으로 인식했을 때 무조건 50보다 큰 값을 입력해야 함.
2진수로 바꿔보면 매우 쉽게 풀 수 있음.
0000 0000 0000 0000 0000 0001 0000 0001
4 3 2 1 (byte)
이 값을 10진수로 바꾸면 257임 char의 최대 값보다 1높은 값인데,
왜 이값을 char형으로 바꿧을 때 1로 인식하는지 알아야 함.
Char형은 1바이트까지 담아둘 수 있는 변수임. 즉 저기서 1바이트는
0000 0001
0000 0001 이며 0000 0001까지 밖에 인식을 못하기 때문에 char형으로
보았을 때 1로 인식해 버리는 것.
1은 check_decision() 함수 조건에 만족하는 값이기 때문에
Are You trying to cheat? 문자열이 뜨지 않을 것이고
우리는 게임에서 승리하여 Flag를 획득 할 수 있음.
1 2 3 4 5 6 7 8 9 10 11 12 | ========================================== +> The rules are very simple: <+ +> The one that take the last match win <+ ========================================== There are 38 match(es) How many matches do you want to take ?(1-3) 257 You've taken 257 match(es) there are -219 match(es) remaining How did you do this ? password is : Integer Overflow :) You win ! | cs |
1 | Integer Overflow :) | cs |
'Pwnable > Technique' 카테고리의 다른 글
The House of Force (kor) (0) | 2016.04.05 |
---|---|
SigReturn Oriented Programming - 32bit (0) | 2015.02.14 |
mprotect() 함수 이용하여 Exploit하기 (0) | 2015.01.16 |
Bypass NX-Bit And ASCII-Armor Python 익스플로잇 (3) | 2014.11.10 |
Bypass NX-Bit and ASCII-Armor (15) | 2014.10.26 |