├── browser_xss_filter └── README.md └── afl_internals └── afl_internals.md /browser_xss_filter/README.md: -------------------------------------------------------------------------------- 1 | # [Browser's XSS Filter Bypass Cheat Sheet](https://github.com/masatokinugawa/filterbypass/wiki/Browser's-XSS-Filter-Bypass-Cheat-Sheet) 2 | 3 | [브라우저 XSS 필터 우회의 모든 것](https://blog.rubiya.kr/index.php/2019/03/28/browsers-xss-filter-bypass-cheat-sheet/) 4 | -------------------------------------------------------------------------------- /afl_internals/afl_internals.md: -------------------------------------------------------------------------------- 1 | # [American Fuzzy Lop internals](http://shimasyaro.hatenablog.com/entry/2018/03/26/184415) 2 | 3 | ### American Fuzzy Lop 소스 코드 분석 4 | 5 | 최근 American Fuzzy Lop에 대한 여러가지 기술을 탑재하는 과정에서 소스 코드 분석을 했으므로, 그 결과를 블로그에 정리해서 올려보자는 생각으로 적어 보았습니다. 6 | 7 | 소스 코드의 분석을 하는 블로그 등은 딱히 본 적이 없기 때문에 (2018/03/19 시점) 쓸 가치가 있다고 생각하여 자기 만족으로 적어 보았습니다. 8 | 9 | 10 | 11 | ### American Fuzzy Lop란 12 | 13 | American Fuzzy Lop은 Google의 엔지니어인 Michal Zalewski의 fuzzing tool입니다. 14 | 15 | fuzzing은 간단하게 말해서 "자동으로 버그, 취약점을 찾자" 라는 개념입니다. 16 | 17 | 18 | 19 | 유명한 사례로서는 CGC(Cyber Grand Challenge) 컴퓨터 간 공방에서 fuzzing이 사용되기도 하며, 20 | 21 | 또 최근 사건으로는 2017년 보도된 Microsoft의 [Security Risk Detection](https://www.microsoft.com/en-us/security-risk-detection/) 툴은 [Neural fuzzing: applying DNN to software security testing](https://www.microsoft.com/en-us/security-risk-detection/) 에 작성되어 있듯이 fuzzing의 알고리즘에 Deep Neural Network를 적용하고 있습니다. 22 | 23 | 24 | 25 | 즉, "자동으로 버그나 취약점을 찾는 서비스를 시작했다." 라는 말입니다. 유명한 기업이 이런 일을 하고 있을 정도로 뜨거운 화제이므로 흥미가 있으면 꼭 이 글을 시작으로 fuzzing에 뛰어들면 좋겠습니다. 26 | 27 | 28 | 29 | ### American Fuzzy Lop의 알고리즘에 대해서 30 | 31 | American Fuzzy Lop의 알고리즘은 유전적 알고리즘입니다. 32 | 33 | 더 간단하게 말하면 "테스트 하는 실행 속도가 빠르기에 코드 커버리지가 (이하, 커버리지) 넓고 더 깊이까지 테스트 할 수 있는 것이 좋은 케이스" 라는 생각을 기반으로 구현되었습니다. 34 | 35 | 36 | 37 | American Fuzzy Lop로는 "어쨌든 빠르고 정확하며, 많은 불필요한 부분을 (불필요한 라이브러리로 CPU를 많이 사용하는 등) 제외한 심플한 소스 코드이다." 라는 컨셉을 하고 있습니다. 38 | 39 | 40 | 41 | ### American Fuzzy Lop의 변형에 대해서 42 | 43 | 변형 (Mutation) 이란 유저가 준비한 초기 값을 여러 가지 방식으로 변화하는 방법입니다. 44 | 45 | 방식은 크게 나눠 6개가 있습니다. 46 | 47 | * SIMPLE BITFLIP (xor) 48 | * ARITHMETIC INC/DEC (수의 증가/감소) 49 | * INTERESTING VALUES (고정치 삽입) 50 | * DICTIONARY STUFF (사전형 data 삽입) 51 | * RANDOM HAVOC (랜덤으로 준비된 방법 선택) 52 | * SPLICING (data splite) 53 | 54 | 이 글에서는 소스 코드를 (afl-fuzz.c의 fuzz_one 함수) 통해 6개의 방식을 자세하고 간단하게 설명하도록 하겠습니다. 55 | 56 | 아래 링크는 소스 코드에 메모를 적어둔 것이므로 필요하시면 이용하시기 바랍니다. (afl-2.25b 디렉토리에 있습니다.) 57 | 58 | [https://github.com/syarochan/public_study](https://github.com/syarochan/public_study) 59 | 60 | 61 | 62 | ### 변형에 들어가기 전 처리 (불필요한 data skip) 63 | 64 | 변형에 (Mutation) 들어가기 전 최소한의 fuzzing을 위하여 불필요한 data를 (queue) 삭제합니다. 65 | 66 | 삭제되는 data는 이하 3개입니다. 67 | 68 | * 에러를 찾기 위해 변형 처리를 기다리는 data가 (pending_favored) 있고, 그 data가 이미 fuzzing 된 data (already-fuzzed, was_fuzzed) 또는 에러를 찾고 있지 않는 data (non-favored, !favored) 일 경우에는 99%의 확률로 변형을 하지 않고 return 합니다. 69 | 70 | * 아래 3개의 조건이 갖춰졌을 때 아래 2가지 조건으로 분기합니다. 71 | 72 | 1. penging_favored가 없을 경우 fuzzing을 실행할 때 옵션이 dumb_mode가 아닐 것. (유저의 초기 값으로만 fuzzing을 하는 모드) 73 | 2. 현재의 data가 에러를 찾는 data가 (favored queue) 아닐 것. 74 | 3. 변형 처리를 기다리는 queue의 수가 (queue_paths) 10개 보다 적을 것. 75 | 76 | * 아래 2가지 조건이 충족되면 75%의 확률로 변형을 수행하지 않고 return 합니다. 77 | 1. queue_paths에 있는 queue를 한 바퀴 돌고나서 더해지는 수가 (queue cycle) 1보다 위 일 것. 78 | 2. 이미 fuzzing 되고 있는 queue 일 것. 79 | * 그 이외의 조건이라면 95% 확률로 변형을 수행하지 않고 return 합니다. 80 | 81 | 아래는 설명한 내용에 대한 소스 코드입니다. 82 | 83 | ```c 84 | #ifdef IGNORE_FINDS 85 | 86 | /* In IGNORE_FINDS mode, skip any entries that weren't in the 87 | initial data set. */ 88 | 89 | if (queue_cur->depth > 1) return 1; 90 | 91 | #else 92 | 93 | if (pending_favored) { 94 | 95 | /* If we have any favored, non-fuzzed new arrivals in the queue, 96 | possibly skip to them at the expense of already-fuzzed or non-favored 97 | cases. */ 98 | // already-fuzzed와 non-favored는 99%의 확률로 skip 됩니다. 99 | if ((queue_cur->was_fuzzed || !queue_cur->favored) && 100 | UR(100) < SKIP_TO_NEW_PROB) return 1; 101 | 102 | } else if (!dumb_mode && !queue_cur->favored && queued_paths > 10) {// pending_favored가 없을 경우 이 조건을 비교합니다. 103 | 104 | /* Otherwise, still possibly skip non-favored cases, albeit less often. 105 | The odds of skipping stuff are higher for already-fuzzed inputs and 106 | lower for never-fuzzed entries. */ 107 | 108 | if (queue_cycle > 1 && !queue_cur->was_fuzzed) {//lower for never-fuzzed entries. 109 | 110 | if (UR(100) < SKIP_NFAV_NEW_PROB) return 1;// 75%의 확률로 return 111 | 112 | } else {//higher for already-fuzzed 113 | 114 | if (UR(100) < SKIP_NFAV_OLD_PROB) return 1;// 95%의 확률로 return 115 | 116 | } 117 | 118 | } 119 | 120 | #endif /* ^IGNORE_FINDS */ 121 | ``` 122 | 123 | 124 | 125 | ### 변형에 들어가기 전 처리 (CALIBRATION을 실패한 data가 있을 때) 126 | 127 | * CALIBRATION이란 실제로 data를 (queue) 사용하여 실행 파일을 실행하고 해당 queue의 커버리지, 실행 속도, 어떤 에러가 발생하는지 등을 기록하는 함수입니다. 128 | * 아래는 calibrate_case 함수의 설명입니다. 129 | * fuzz_one 함수에 들어가기 전 단계에서 calibration 함수는 실행 되어 있으며, 실행 실패 flag는 (cal_failed) calibration 함수가 실행된 직후 설정됩니다. 130 | * 디폴트의 상태이면 (dumb_mode가 아닌 상태) init_forkserver를 사용하여 자식 프로세스를 생성합니다. 131 | * write_to_testcase로 .cur_input 파일에 data 내용을 작성합니다. 132 | * 작성한 후, run_target 함수를 통해 자식 프로세스에서 execv로 실행 파일을 실행하고, 실행 결과를 부모 프로세스에 반환합니다. 133 | * stage는 모두 8번 (fast calibration의 flag가 설정 되어 있지 않을 때) 실행됩니다. 즉, run_target도 8번 실행됩니다. 134 | * run_target이 끝날 때마다 커버리지를 (trace_bits) 사용하여 hash를 생성합니다. 135 | * hash는 최초 run_target을 실행할 때의 hash를 현재 queue에 저장한 후, 최초 커버리지를 first_trace에 넣어 나중의 stage와 비교합니다. 136 | * 두번째 이후 생성된 hash가 처음 hash와 다를 경우, 새로운 input의 편성으로 (new tuple) 새로운 커버리지를 찾았기에 전체의 커버리지를 (virgin_bits) 갱신합니다. 137 | * first trace와 trace bits를 비교해나가며 일치하지 않은 부분이 있다면 해당 부분에 (var_bytes) flag를 설정합니다. 138 | * update_bitmap_score 함수에서 현재 queue가 현 시점에서 가장 뛰어난 queue와 (top_rated) 비교해서 실행 시간과 data의 길이를 곱한 수보다 작으면 top_rated와 교체합니다. 139 | * 만약 top_rated가 없을 경우, top_rated에 현재의 queue를 넣습니다. 140 | * queue에 변화한 부분이 있었다는 (var_bytes) 표시로 flag를 설정합니다. 141 | 142 | 아래에 변형에 들어가기 전 처리 (CALIBRATION을 실패한 data가 있을 때) 의 소스 코드를 기재했습니다. calibration 함수는 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 봐주시기 바랍니다. 143 | 144 | ```c 145 | /******************************************* 146 | * CALIBRATION (only if failed earlier on) * 147 | *******************************************/ 148 | 149 | if (queue_cur->cal_failed) { 150 | 151 | u8 res = FAULT_TMOUT; 152 | 153 | if (queue_cur->cal_failed < CAL_CHANCES) {// 3보다 작을 경우만 calibrate_case 함수를 실행 154 | 155 | res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0); 156 | 157 | if (res == FAULT_ERROR) 158 | FATAL("Unable to execute target application"); 159 | 160 | } 161 | 162 | if (stop_soon || res != crash_mode) { 163 | cur_skipped_paths++;// 현재의 queue를 skip했으니 skip한 수를 늘린다. 164 | goto abandon_entry; 165 | } 166 | 167 | } 168 | ``` 169 | 170 | 171 | 172 | ### 변형에 들어가기 전 처리 (data의 최소한까지 trimming) 173 | 174 | * trim에서는 data 동작에 영향을 주지 않는 최소한까지 data의 trimming을 수행합니다. trim_case 함수가 해당 기능을 수행합니다. 아래는 trim_case 함수의 설명입니다. 175 | 176 | * trim_case 함수에서 data의 길이를 16으로 나눈 후 계속 2씩 나눠, 최종적으로는 1024으로 나눈 곳까지 갑니다. 177 | 178 | 이 때, run_target 함수를 실행하여 hash가 변경되는지 비교하여 변경되었다면 calibration 함수와 마찬가지로 update_bitmap_score를 갱신합니다만, 나누어 떨어질 때까지 루프를 빠지지 않으므로 현재의 trace_bits를 clean_trace에 저장합니다. 179 | 180 | * 나누어 떨어질 때까지 나누기를 계속하므로 필연적으로 "커버리지 (trace_bits)에 변화가 있던 최소한의 길이" 까지 나누어집니다. 181 | 182 | 아래는 trimming의 소스 코드를 기재했습니다. trim_case 함수는 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 봐주시기 바랍니다. 183 | 184 | ```c 185 | /************ 186 | * TRIMMING * 187 | ************/ 188 | 189 | if (!dumb_mode && !queue_cur->trim_done) { 190 | 191 | u8 res = trim_case(argv, queue_cur, in_buf);// queue를 trim하여 실행합니다. 192 | 193 | if (res == FAULT_ERROR) 194 | FATAL("Unable to execute target application"); 195 | 196 | if (stop_soon) { 197 | cur_skipped_paths++;// 포기한 수를 늘립니다. 198 | goto abandon_entry; 199 | } 200 | 201 | /* Don't retry trimming, even if it failed. */ 202 | // 실패해도 trim_done flag를 설정합니다. 203 | queue_cur->trim_done = 1; 204 | 205 | if (len != queue_cur->len) len = queue_cur->len;// trimming하여 queue의 길이가 다르다면 변경합니다. 206 | 207 | } 208 | 209 | memcpy(out_buf, in_buf, len);// trim 되고 있다면 data의 경신를 경신합니다. (trim 되고 있지 않다면 값은 변하지 않습니다.) 210 | ``` 211 | 212 | 213 | 214 | ### 변형에 들어가기 전 처리 (data의 점수화) 215 | 216 | * performance score는 queue의 점수화를 수행하는 부분입니다. calculate_score 함수에서 점수를 매깁니다. 217 | 218 | 아래는 calculate_score 함수의 설명입니다. 219 | 220 | * score가 좋아지려면 4개의 조건이 있습니다. 221 | 1. 평균 실행 시간보다 적으면 적을수록 좋은 score가 됩니다. 222 | 2. 커버리지가 넓으면 넓을수록 좋은 score가 됩니다. 223 | 3. queue cycle의 횟수가 높으면 높을수록 좋은 score가 됩니다. 이것은 queue가 많이 실행될수록 다양한 변형을 한 tuple에서 에러를 찾기 쉽기 때문입니다. 224 | 4. queue의 조건 깊이가 깊을수록 좋은 score가 됩니다. 225 | 226 | * 아래 3개의 조건 중 하나에 해당하면 skip 합니다. 227 | 1. calculate_score가 끝나고 변형을 skip하는 (정확히는 RANDOM HAVOC 까지 goto) 조건으로 -d option가 있을 것. 228 | 229 | 2. 이미 fuzzing 되어 있는 것. (was_fuzzed) 230 | 231 | 3. 과거에 fuzzing 했던 (resume) favored path로 queue가 (pass_det) 남아있는 것. 232 | 233 | (American Fuzzy Lop은 output 디렉토리에 과거 중단한 data를 (queue) 기록하기에, 이것을 사용하여 다시 도중부터 실행하는 기능을 가지고 있습니다. 이 data를 resume queue 라고 부릅니다. 20분 이상 실행한 흔적이 있다면 fuzzing을 처음부터 실행할 때 초기화 단계에서 경고문을 출력하고 실행이 중단됩니다.) 234 | 235 | 236 | * skip 하지 않았을 경우, 앞으로 모든 변형을 실행하며 flag를 (doing_det) 설정합니다. 237 | 238 | 아래는 설명한 내용에 대한 소스 코드입니다. calculate_score 함수는 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 봐주시기 바랍니다. 239 | 240 | ```c 241 | /********************* 242 | * PERFORMANCE SCORE * 243 | *********************/ 244 | 245 | orig_perf = perf_score = calculate_score(queue_cur);// queue의 score를 매깁니다. 246 | 247 | /* Skip right away if -d is given, if we have done deterministic fuzzing on 248 | this entry ourselves (was_fuzzed), or if it has gone through deterministic 249 | testing in earlier, resumed runs (passed_det). */ 250 | 251 | if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det) 252 | goto havoc_stage; 253 | 254 | /* Skip deterministic fuzzing if exec path checksum puts this out of scope 255 | for this master instance. */ 256 | 257 | if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1) 258 | goto havoc_stage; 259 | 260 | doing_det = 1;// deterministic fuzzing flag를 설정합니다. 261 | ``` 262 | 263 | 264 | 265 | ### SIMPLE BITFLIP (xor) 266 | 267 | * SIMPLE BITFLIP에서는 bit 단위 xor, byte 단위 xor 의 2개의 방법으로 queue를 변형시킵니다. 268 | 269 | * 먼저 bit 단위 xor의 설명입니다. 270 | 271 | * bit 단위 xor에서는 3단계로 나누어 queue를 변형합니다. 272 | 273 | * 첫번째 단계에서는 1 byte를 하나의 부분에 대하여 0x80 만큼 stage 수에 더하여 left bit shift하여 xor 합니다. 274 | 275 | * 두번째 단계에서는 1 byte를 두개의 부분에 대하여 0x80 만큼 stage 수에 더하여 left bit shift 하여 xor 합니다. 276 | 277 | * 세번째 단계에서는 1 byte를 네개의 부분에 대하여 0x80 만큼 stage 수에 더하여 left bit shift 하여 xor 합니다. 278 | 279 | 기본적인 처리는 3개 모두 같기때문에 첫 단계의 Single walking bit만을 설명하도록 하겠습니다. 280 | 281 | * stage 마다 common_fuzz_stuff 함수가 실행되고 run_target 함수가 실행됩니다. run_target의 반환 값으로 돌아온 실행 결과를 (fault) save_if_interesting 함수를 사용하여 배분합니다. 아래는 save_if_interesting 함수의 설명입니다. 282 | 283 | * save_if_interesting 함수를 실행하고 -C option의 (crash_mode) 경우 바로 해당 옵션 처리에 진입합니다. 284 | 285 | 이번에는 디폴트 처리에 대해서 설명하도록 하겠습니다. 286 | 287 | * switch문 FAULT_TMOUT, FAULT_CRASH에서 fault의 내용이 배분됩니다. 288 | * FAULT_TMOUT은 실행 파일이 time out 에러를 발생하고 종료한 상태입니다. 289 | * time out을 발생하고 종료한 전체의 수를 (total_tmouts) 증가시킵니다. 290 | * simplify_trace 함수를 사용하여 trace_bits의 not hit, hit의 (각각 0x01, 0x80 으로 정의되어 있습니다.) 값을 정의합니다. 291 | * has_new_bits를 사용하여 trace_bits와 virgin_tmout을 (time out error용의 전체 커버리지) 비교하여 발견한 적 없는 time out이 (hit count의 변경과 new tuple) 없으면 return 합니다. 292 | * unique time out의 수를 증가합니다. (unique_tmouts) 293 | * 만약 유저가 설정한 time out이 작을 경우 한 번 더 hang_tmout으로 (1000ms) run_target 함수를 실행합니다. 294 | * 다시 실행한 결과가 FAULT_CRASH 이라면 FAULT_CRASH의 처리로 분기합니다. FAULT_TMOUT 이라면 아무것도 하지 않고 return 합니다. 295 | * FAULT_CRASH은 실행 파일이 SEGV 에러를 발생시키고 종료한 상태입니다. 296 | 297 | 처리는 FAULT_TMOUT와 다르지 않기에 생략합니다. 298 | 299 | * SIMPLE BITFLIP에서 stage가 8의 배수 - 1 (8byte 단위) 일 때, hash를 생성하여 각각의 단계에 자동 사전형을 (a_extras) 생성합니다. 300 | * 현재의 stage가 가장 마지막이고, cksum과 prev_cksum이 (이전의 체크섬) 일치 할 경우에는 다음 처리에 들어갑니다. 301 | * out_buf의 가장 마지막 문자를 a_collect에 넣습니다. 302 | * a_len의 (a_collect의 길이) 길이가 3 이상 32 이하 일 경우, maybe_add_auto 함수를 실행하여 자동사전형을 (a_extras) 생성합니다. 303 | * cksum과 prev_cksum이 (이전의 체크섬) 일치하지 않을 때 304 | * a_len의 (a_collect의 길이) 길이가 3 이상 32 이하 일 경우, maybe_add_auto 함수를 실행하여 자동사전형을 (a_extras) 생성합니다. 305 | * prev_cksum을 cksum으로 갱신합니다. 306 | * 현재 queue의 cksum과 생성한 cksum을 비교하여 일치하지 않을 경우 307 | * a_len의 (a_collect의 길이) 길이가 32 보다 작을 경우, out_buf의 가장 마지막 문자를 a_collect에 넣습니다. a_len을 더합니다. 308 | 309 | * 루프 처리를 빠져 나오면 Single walking bit에서 발견한 조건 분기 (queued_paths), 실행 에러를 (unique_crashes) 더합니다. 310 | 311 | 아래는 설명한 내용에 대한 소스 코드입니다. save_if_interesting 함수, simplify_trace 함수, common_fuzz_stuff 함수, maybe_add_auto 함수, 2단계 처리, 3단계 처리는 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 봐주시기 바랍니다. 312 | 313 | ```c 314 | /********************************************* 315 | * SIMPLE BITFLIP (+dictionary construction) * 316 | *********************************************/ 317 | 318 | #define FLIP_BIT(_ar, _b) do { \ 319 | u8* _arf = (u8*)(_ar); \ 320 | u32 _bf = (_b); \ 321 | _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \ 322 | } while (0) 323 | 324 | /* Single walking bit. */ 325 | 326 | stage_short = "flip1"; 327 | stage_max = len << 3;// len * 2^3 값을 (bit 단위) stage_max로 합니다. 328 | stage_name = "bitflip 1/1"; 329 | 330 | stage_val_type = STAGE_VAL_NONE; 331 | 332 | orig_hit_cnt = queued_paths + unique_crashes; 333 | 334 | prev_cksum = queue_cur->exec_cksum; 335 | 336 | for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { 337 | 338 | stage_cur_byte = stage_cur >> 3; 339 | 340 | FLIP_BIT(out_buf, stage_cur); 341 | 342 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;// 실행하여 time out과 skip flag가 있으면 goto 합니다. 343 | 344 | FLIP_BIT(out_buf, stage_cur); 345 | . 346 | . 347 | . 348 | 349 | if (!dumb_mode && (stage_cur & 7) == 7) {// stage_cur이 (8의 배수 - 1)일 경우 350 | 351 | u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); 352 | 353 | if (stage_cur == stage_max - 1 && cksum == prev_cksum) { 354 | 355 | /* If at end of file and we are still collecting a string, grab the 356 | final character and force output. */ 357 | 358 | if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; 359 | a_len++; 360 | 361 | if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) 362 | maybe_add_auto(a_collect, a_len);// out_buf의 1 byte를 추가. 마지막 stage 이기에 a_len의 초기화는 하지 않습니다. 363 | 364 | } else if (cksum != prev_cksum) {// 이전과 체크섬이 다를 경우 365 | 366 | /* Otherwise, if the checksum has changed, see if we have something 367 | worthwhile queued up, and collect that if the answer is yes. */ 368 | 369 | if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) 370 | maybe_add_auto(a_collect, a_len); 371 | 372 | a_len = 0;// add했기에 a_len를 초기화 합니다. 373 | prev_cksum = cksum;// 체크섬의 갱신 374 | 375 | } 376 | 377 | /* Continue collecting string, but only if the bit flip actually made 378 | any difference - we don't want no-op tokens. */ 379 | 380 | if (cksum != queue_cur->exec_cksum) { 381 | 382 | if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; 383 | a_len++; 384 | 385 | } 386 | 387 | } 388 | 389 | } 390 | 391 | new_hit_cnt = queued_paths + unique_crashes; 392 | 393 | stage_finds[STAGE_FLIP1] += new_hit_cnt - orig_hit_cnt;// stage_flip1에서 발견한 path와 crashes의 수를 더합니다. 394 | stage_cycles[STAGE_FLIP1] += stage_max;// stage를 실행한 횟수를 더합니다. 395 | ``` 396 | 397 | 398 | 399 | 다음은 byte 단위 xor의 설명입니다. 400 | 401 | * byte 단위 xor에서는 1 byte 단위, 2 byte 단위, 4 byte 단위 총 3개로 queue를 변형합니다. 402 | 403 | 이번에는 1 byte 단위만 설명하도록 하겠습니다. 404 | * 루프에 진입하면 바로 out_buf의 1 byte를 0xff로 xor 합니다. 405 | * common_fuzz_stuff 함수로 run_target 함수를 통하여 실행 파일을 실행합니다. 406 | * stage마다 변형을 추가한 곳을 관리하는 곳에 (eff_map) 해당 부분이 저장되어 있지 않을 경우 407 | * data의 길이가 128 byte 이상 일 경우 hash을 생성합니다. (단, dumb_mode가 아닐 경우) 408 | * 그 이외에는 queue에 저장되어 있는 체크섬을 반전한 값을 cksum에 넣습니다. 409 | * cksum과 queue에 저장되어있는 체크섬이 다를 경우 eff_map에 저장하고, eff_map에 저장되어 있는 수를 (eff_cnt) 증가시킵니다. 410 | * xor 한 out_buf의 1 byte를 원래대로 되돌립니다. 411 | * 루프를 빠져 나오면 바로 eff_map에 저장된 수를 체크하여 수가 90% 이상 일 경우, len을 8 byte 단위로 한 상태에서 eff_map의 처음부터 다시 저장합니다. 412 | * 8 byte 마다 저장한 수를 (blocks_eff_select) 더합니다. 413 | * 전체의 8 byte마다 저장한 수를 (blocks_eff_total) 더합니다. 414 | 415 | 1 byte 단위 xor의 소스코드입니다. 나머지 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 봐주시기 바랍니다. 416 | 417 | ```c 418 | /* Walking byte. */ 419 | 420 | stage_name = "bitflip 8/8"; 421 | stage_short = "flip8"; 422 | stage_max = len; 423 | 424 | orig_hit_cnt = new_hit_cnt; 425 | 426 | for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { 427 | 428 | stage_cur_byte = stage_cur; 429 | 430 | out_buf[stage_cur] ^= 0xFF;// 1 byte를 0xff로 xor 합니다. 431 | 432 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 433 | 434 | /* We also use this stage to pull off a simple trick: we identify 435 | bytes that seem to have no effect on the current execution path 436 | even when fully flipped - and we skip them during more expensive 437 | deterministic stages, such as arithmetics or known ints. */ 438 | 439 | if (!eff_map[EFF_APOS(stage_cur)]) {// 현재의 stage가 eff_map에 없을 경우 440 | 441 | u32 cksum; 442 | 443 | /* If in dumb mode or if the file is very short, just flag everything 444 | without wasting time on checksums. */ 445 | 446 | if (!dumb_mode && len >= EFF_MIN_LEN) 447 | cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);// 현재 queue의 길이가 128 byte 이상 일 경우 448 | else 449 | cksum = ~queue_cur->exec_cksum;// 128 byte 이상이 아닐 경우 체크섬을 반전한 값을 생성합니다. 450 | 451 | if (cksum != queue_cur->exec_cksum) {// 128 byte 이상에서 생성한 체크섬, 반전한 queue의 체크섬이 queue의 체크섬과 다를 경우, eff_map에 현재 stage의 flag를 설정합니다. 452 | eff_map[EFF_APOS(stage_cur)] = 1; 453 | eff_cnt++; 454 | } 455 | 456 | } 457 | 458 | out_buf[stage_cur] ^= 0xFF;// 원래대로 되돌립니다. 459 | 460 | } 461 | 462 | /* If the effector map is more than EFF_MAX_PERC dense, just flag the 463 | whole thing as worth fuzzing, since we wouldn't be saving much time 464 | anyway. */ 465 | // 밀집한 수가 90%보다 높으면 (저장된 수가 90%보다 높으면) 466 | if (eff_cnt != EFF_ALEN(len) && 467 | eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) {//len의 8 byte 단위 개수와 len AND 0x03의 값을 (len이 1 byte부터 7 byte 용) 더한 값을 eff_cnt와 비교하고, 468 | 469 | memset(eff_map, 1, EFF_ALEN(len));//8 byte 단위에 alignment 한 값을 eff_map 처음부터 채웁니다. 470 | 471 | blocks_eff_select += EFF_ALEN(len);// 채운 수만큼 더합니다. 472 | 473 | } else { 474 | 475 | blocks_eff_select += eff_cnt;// 그 이외라면 현재의 eff_cnt를 더합니다. 476 | 477 | } 478 | 479 | blocks_eff_total += EFF_ALEN(len); 480 | 481 | new_hit_cnt = queued_paths + unique_crashes; 482 | 483 | stage_finds[STAGE_FLIP8] += new_hit_cnt - orig_hit_cnt; 484 | stage_cycles[STAGE_FLIP8] += stage_max; 485 | ``` 486 | 487 | 488 | 489 | ### ARITHMETIC INC/DEC (수의 증가/감소) 490 | 491 | 이 변형에서는 1부터 35까지의 수를 차례로 증가・감소합니다. 492 | 493 | * 증가・감소는 8 bit, 16 bit, 32 bit 총 3개의 방법으로 이뤄집니다. 494 | 495 | * eff_map에 저장되어 있지 않은 곳은 이 변형을 수행하지 않습니다. 496 | 497 | * 이전의 변형에 있던 bitflip이 되지 않았던 것에 대하여 (could_be_bitflip 함수를 사용하여 판단합니다.) 이 변형을 수행합니다. 498 | 499 | bitflip이 되지 않았던 것에 대해서 변형을 수행하는 이유는 이전의 bitflip 방식과 같은 data를 실행하지 않기 위해서입니다. 500 | 501 | * 16 bit, 32 bit는 little endian과 big endian을 고려한 변형으로 되어 있습니다. 502 | 503 | 이번에는 16 bit에서의 설명을 하겠습니다. 504 | 505 | * data의 길이가 2 byte 미만이라면 ARITHMETIC INC/DEC 변형을 skip합니다. 506 | 507 | * stage_max는 (루프를 실행하는 최대 횟수) 증가・감소의 little endian용과 증가・감소의 big endian용 총 4개를 최대 횟수로 설정합니다. 508 | 509 | * out_buf를 2 byte씩 orig에 넣어 eff_map에 연속으로 저장되고 있는지 확인하고 저장되고 있지 않으면 skip합니다. 510 | 511 | * eff_map에 저장되어 있을 경우, ARITHMETIC까지를 (1 ~ 35) 차례로 little endian과 big endian의 덧・뺄셈을 수행합니다. 512 | 513 | * 가장 처음의 little endian 514 | 515 | * 증가할 때 overflow를 일으키지 않는지 체크한 후, could_be_bitflip 함수를 사용하여 bitflip이 되지 않은는지를 확인합니다. 이 조건을 충족하면 common_fuzz_stuff 함수를 실행합니다. 516 | * 감소할 때 underflow를 일으키지 않는지 체크한 후, could_be_bitflip 함수를 사용하여 bitflip이 되지 않았는지를 확인합니다. 이 조건을 충족하면 common_fuzz_stuff 함수를 실행합니다. 517 | 518 | * out_buf를 원래대로 돌려놓습니다. 519 | 520 | * 다음은 big endian입니다. 521 | 522 | * 처리는 little endian과 다르지 않습니다. 523 | * 다른 점은 SWAP함수를 사용하여 big endian으로 바꾸는 것입니다. 524 | 525 | 아래는 16 bit의 변형입니다. 그 이외의 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 봐주시기 바랍니다. 526 | 527 | ```c 528 | if (len < 2) goto skip_arith; 529 | 530 | stage_name = "arith 16/8"; 531 | stage_short = "arith16"; 532 | stage_cur = 0; 533 | stage_max = 4 * (len - 1) * ARITH_MAX;// 4는 INC/DEC의 little endian과 Big endian용 534 | 535 | orig_hit_cnt = new_hit_cnt; 536 | 537 | for (i = 0; i < len - 1; i++) { 538 | 539 | u16 orig = *(u16*)(out_buf + i); 540 | 541 | /* Let's consult the effector map... */ 542 | 543 | if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { 544 | stage_max -= 4 * ARITH_MAX; 545 | continue; 546 | } 547 | 548 | stage_cur_byte = i; 549 | 550 | for (j = 1; j <= ARITH_MAX; j++) { 551 | // SWAP16은 little endian의 하위 1 byte 상위 1 byte를 교체합니다. (big endian용) 552 | u16 r1 = orig ^ (orig + j), 553 | r2 = orig ^ (orig - j), 554 | r3 = orig ^ SWAP16(SWAP16(orig) + j), 555 | r4 = orig ^ SWAP16(SWAP16(orig) - j); 556 | 557 | /* Try little endian addition and subtraction first. Do it only 558 | if the operation would affect more than one byte (hence the 559 | & 0xff overflow checks) and if it couldn't be a product of 560 | a bitflip. */ 561 | 562 | stage_val_type = STAGE_VAL_LE; 563 | 564 | if ((orig & 0xff) + j > 0xff && !could_be_bitflip(r1)) {// orig를 (2 byte) 0xff와 (하위 1 byte가 little endian이므로 상위가 됩니다.) 비교하여 overflow 하지 않는지 체크합니다. 565 | 566 | stage_cur_val = j; 567 | *(u16*)(out_buf + i) = orig + j; 568 | 569 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 570 | stage_cur++; 571 | 572 | } else stage_max--; 573 | 574 | if ((orig & 0xff) < j && !could_be_bitflip(r2)) {// underflow를 체크합니다. 하위 1 byte가 j보다 작으면 아래를 실행합니다. 575 | 576 | stage_cur_val = -j; 577 | *(u16*)(out_buf + i) = orig - j; 578 | 579 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 580 | stage_cur++; 581 | 582 | } else stage_max--; 583 | 584 | /* Big endian comes next. Same deal. */ 585 | 586 | stage_val_type = STAGE_VAL_BE;// 다음은 big endian으로 생각합니다. 587 | 588 | 589 | if ((orig >> 8) + j > 0xff && !could_be_bitflip(r3)) { 590 | 591 | stage_cur_val = j; 592 | *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) + j); 593 | 594 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 595 | stage_cur++; 596 | 597 | } else stage_max--; 598 | 599 | if ((orig >> 8) < j && !could_be_bitflip(r4)) { 600 | 601 | stage_cur_val = -j; 602 | *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) - j); 603 | 604 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 605 | stage_cur++; 606 | 607 | } else stage_max--; 608 | 609 | *(u16*)(out_buf + i) = orig; 610 | 611 | } 612 | 613 | } 614 | 615 | new_hit_cnt = queued_paths + unique_crashes; 616 | 617 | stage_finds[STAGE_ARITH16] += new_hit_cnt - orig_hit_cnt; 618 | stage_cycles[STAGE_ARITH16] += stage_max; 619 | ``` 620 | 621 | 622 | 623 | ### INTERESTING VALUES (고정치 삽입) 624 | 625 | INTERESTING VALUES는 고정치를 삽입하는 방법으로 8 bit, 16 bit, 32 bit의 각각 크기로 overflow, underflow, off-by-one을 비교할 때 실수할 것 같은 값을 사용하여 fuzzing을 수행합니다. 626 | 627 | * eff_map에 저장되어 있지 않은 곳은 이 변형을 수행하지 않습니다. 628 | * 이전 변형인 bitflip, arithmetic, interesting 이 (자신의 처리보다 작은 크기의 변형, 자기자신) 되지 않았던 것에 대하여 (could_be_bitflip 함수, could_be_arith 함수, could_be_interest 함수를 사용하여 판단합니다.) 이 변형을 수행합니다. 629 | 630 | 되지 않았던 것에 대하여 수행하는 이유는 이전 변형과 같이 data를 실행하지 않기 위해서 입니다. (무의미함을 줄입니다.) 631 | * 16 bit, 32 bit는 little endian과 big endian을 고려한 변형으로 되어있습니다. 632 | 633 | 이번에는 16 bit를 설명하겠습니다. 그리고 little endian과 big endian의 처리는 SWAP 함수의 사용 유무 차이이기에 little endian 부분만 설명하도록 하겠습니다. 634 | 635 | * data의 길이가 2 byte 미만인 상태라면 변형 INTERESTING VALUES를 skip합니다. 636 | 637 | * stage_max는 (루프를 수행하는 최대 횟수) little endian용과 big endian용 2개를 최대 횟수로 설정합니다. 638 | 639 | * out_buf를 2 byte씩 orig에 넣어 eff_map에 연속으로 저장되고 있는지 확인하고 저장되고 있지 않으면 skip합니다. 640 | * eff_map에 저장되지 않았을 경우, 이전 변형의 bitflip, arithmetic, interesting과 같은지를 확인한 후 같으면 skip합니다. 641 | * 같지 않으면 common_fuzz_stuff 함수를 실행합니다. 642 | 643 | * out_buf를 원래대로 되돌립니다. 644 | 645 | 아래는 16 bit 변형입니다. 그 이외 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 확인해주시기 바랍니다. 646 | 647 | ```c 648 | /* Setting 16-bit integers, both endians. */ 649 | 650 | if (no_arith || len < 2) goto skip_interest; 651 | 652 | stage_name = "interest 16/8"; 653 | stage_short = "int16"; 654 | stage_cur = 0; 655 | stage_max = 2 * (len - 1) * (sizeof(interesting_16) >> 1);// little endian and big endian용 656 | 657 | orig_hit_cnt = new_hit_cnt; 658 | 659 | for (i = 0; i < len - 1; i++) { 660 | 661 | u16 orig = *(u16*)(out_buf + i); 662 | 663 | /* Let's consult the effector map... */ 664 | 665 | if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { 666 | stage_max -= sizeof(interesting_16); 667 | continue; 668 | } 669 | 670 | stage_cur_byte = i; 671 | 672 | for (j = 0; j < sizeof(interesting_16) / 2; j++) { 673 | 674 | stage_cur_val = interesting_16[j]; 675 | 676 | /* Skip if this could be a product of a bitflip, arithmetics, 677 | or single-byte interesting value insertion. */ 678 | 679 | if (!could_be_bitflip(orig ^ (u16)interesting_16[j]) && 680 | !could_be_arith(orig, (u16)interesting_16[j], 2) && 681 | !could_be_interest(orig, (u16)interesting_16[j], 2, 0)) {// orig이 3개가 동시에 맞지 않는 것만 682 | 683 | stage_val_type = STAGE_VAL_LE;// 현재 stage를 little endian으로 설정합니다. 684 | 685 | *(u16*)(out_buf + i) = interesting_16[j]; 686 | 687 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 688 | stage_cur++; 689 | 690 | } else stage_max--; 691 | // big endian용 692 | if ((u16)interesting_16[j] != SWAP16(interesting_16[j]) && 693 | !could_be_bitflip(orig ^ SWAP16(interesting_16[j])) && 694 | !could_be_arith(orig, SWAP16(interesting_16[j]), 2) && 695 | !could_be_interest(orig, SWAP16(interesting_16[j]), 2, 1)) { 696 | 697 | stage_val_type = STAGE_VAL_BE; 698 | 699 | *(u16*)(out_buf + i) = SWAP16(interesting_16[j]); 700 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 701 | stage_cur++; 702 | 703 | } else stage_max--; 704 | 705 | } 706 | 707 | *(u16*)(out_buf + i) = orig;// 원래대로 되돌립니다. 708 | 709 | } 710 | 711 | new_hit_cnt = queued_paths + unique_crashes; 712 | 713 | stage_finds[STAGE_INTEREST16] += new_hit_cnt - orig_hit_cnt; 714 | stage_cycles[STAGE_INTEREST16] += stage_max; 715 | ``` 716 | 717 | 718 | 719 | ### DICTIONARY STUFF (사전형 data 삽입) 720 | 721 | DICTIONARY STUFF는 3개의 방식으로 이뤄집니다. 722 | 723 | * 유저측에서 준비한 사전형 data를 사용하여 out_buf을 overwrite 724 | * 유저측에서 준비한 사전형 data를 사용하여 out_buf을 연결 및 삽입 725 | * 자동으로 생성한 사전형 data를 사용하여 out_buf를 overwirte 726 | 727 | 단, 유저측에서 준비한 사전형의 수와 (extras_cnt) 자동으로 생성한 사전형의 수가 (a_extras_cnt) 0 이라면 이 변형들은 각각 skip 됩니다. (기본적으로 유저측에서 사전형의 초기치를 준비하지 않았을 경우 이 변형은 수행되지 않습니다.) 728 | 729 | 우선 처음으로 유저측에서 준비한 사전형 data를 사용하여 out_buf를 overwrite 방식의 설명을 하겠습니다. 730 | 731 | * 루프 처리는 out_buf의 len 크기가 최대 횟수로 설정됩니다. 즉, out_buf의 처음 부분부터 유저측에서 준비한 사전형 data를 사용하여 out_buf를 overwrite 합니다. 732 | 733 | * 다음 루프 처리는 유저측에서 준비한 사전형의 수를 최대 횟수로 설정합니다. 사전형은 미리 size 값에 정렬되어 있습니다. (초기화 단계의 load_extras 함수 부분에서 정렬됩니다.) 734 | * 아래 4개의 조건 중 하나에 해당되면 루프를 continue 합니다. 735 | 1. extras_cnt가 200을 넘어도, extras_cnt를 사용해 랜덤화하여 (UR과 함께 정의되어 있습니다.) 200보다 작은 경우 736 | 2. 사전형의 len이 out_buf len보다 클 때 737 | 3. out_buf와 같은 값 738 | 4. eff_map에 extras의 길이 만큼 끝부분에서 찾았을 때 falg가 하나도 설정되지 않았을 때 739 | * 4가지 조건에 해당되지 않을 경우, out_buf를 유저측에서 준비한 사전형 data를 사용하여 덮어쓰기 하며, common_fuzz_stuff 함수를 실행합니다. 성공하면 stage_cur을 증가합니다. 740 | 741 | * out_buf에 in_buf를 사용하여 덮어 쓰기한 부분을 원래대로 돌려놓고, 다음 루프를 실행합니다. 742 | 743 | 아래는 설명한 내용에 대한 소스 코드입니다. 그 이외의 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 확인해주시기 바랍니다. 744 | 745 | ```c 746 | /******************** 747 | * DICTIONARY STUFF * 748 | ********************/ 749 | 750 | if (!extras_cnt) goto skip_user_extras; 751 | 752 | /* Overwrite with user-supplied extras. */ 753 | 754 | stage_name = "user extras (over)"; 755 | stage_short = "ext_UO"; 756 | stage_cur = 0; 757 | stage_max = extras_cnt * len; 758 | 759 | stage_val_type = STAGE_VAL_NONE; 760 | 761 | orig_hit_cnt = new_hit_cnt; 762 | 763 | for (i = 0; i < len; i++) { 764 | 765 | u32 last_len = 0; 766 | 767 | stage_cur_byte = i; 768 | 769 | /* Extras are sorted by size, from smallest to largest. This means 770 | that we don't have to worry about restoring the buffer in 771 | between writes at a particular offset determined by the outer 772 | loop. */ 773 | 774 | for (j = 0; j < extras_cnt; j++) { 775 | 776 | /* Skip extras probabilistically if extras_cnt > MAX_DET_EXTRAS. Also 777 | skip them if there's no room to insert the payload, if the token 778 | is redundant, or if its entire span has no bytes set in the effector 779 | map. */ 780 | // extras_cnt가 200을 넘어도, extras_cnt를 사용하여 랜덤화를 합니다. 200 보다 낮을 경우 다음의 조건으로 넘어갑니다. 781 | // 사전형의 len이 out_buf의 len보다 클 때, out_buf와 같은 값일 때, eff_map에 extras의 길이만큼 끝 부분에서 찾았을 때, 이 3개 중 하나를 만족했을 때, skip 합니다. 782 | if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) || 783 | extras[j].len > len - i || 784 | !memcmp(extras[j].data, out_buf + i, extras[j].len) || 785 | !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len))) { 786 | 787 | stage_max--; 788 | continue; 789 | 790 | } 791 | 792 | last_len = extras[j].len; 793 | memcpy(out_buf + i, extras[j].data, last_len); 794 | 795 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 796 | 797 | stage_cur++; 798 | 799 | } 800 | 801 | /* Restore all the clobbered memory. */ 802 | memcpy(out_buf + i, in_buf + i, last_len);// 원래대로 되돌립니다. 803 | 804 | } 805 | 806 | new_hit_cnt = queued_paths + unique_crashes; 807 | 808 | stage_finds[STAGE_EXTRAS_UO] += new_hit_cnt - orig_hit_cnt; 809 | stage_cycles[STAGE_EXTRAS_UO] += stage_max; 810 | ``` 811 | 812 | 813 | 814 | 다음으로 유저측에서 준비한 data를 사용하여 out_buf를 연결 및 삽입 방식을 설명하겠습니다. 815 | 816 | * len + 128 byte 크기 만큼 allocate 합니다. (ex_tmp) 817 | * 루프 처리는 out_buf의 len 크기가 최대 횟수로 설정됩니다. 즉, out_buf의 처음부터 유저측에서 준비한 사전형 data를 사용해 out_buf에 삽입합니다. 818 | * 다음 루프 처리는 유저측에서 준비한 사전형의 수를 최대 횟수로 설정합니다. 사전형은 미리 size 값을 정렬합니다. (초기화 단계의 load_extras 함수의 부분에서 정렬합니다.) 819 | * len + extras의 길이가 1 KB 보다 클 경우, skip합니다. (작을 경우는 아래로 진행합니다.) 820 | * ex_tmp에 extras를 복사합니다. 821 | * extras를 삽입된 곳 뒤에 out_buf를 삽입합니다. (루프가 진행되면서 삽입하는 내용이 out_buf의 처음부터 하나씩 밀려들어갑니다.) 822 | * common_fuzz_stuff 함수를 실행합니다. 823 | * stage_cur을 더합니다 824 | * ex_tmp에 out_buf의 address를 대입합니다. (루프가 진행되면서 ex_tmp의 처음부터 out_buf로 바뀌어갑니다.) 825 | 826 | ```c 827 | /* Insertion of user-supplied extras. */ 828 | // 유저가 준비한 file의 data + out_buf를 직접 전부 넣는 것 829 | stage_name = "user extras (insert)"; 830 | stage_short = "ext_UI"; 831 | stage_cur = 0; 832 | stage_max = extras_cnt * len; 833 | 834 | orig_hit_cnt = new_hit_cnt; 835 | 836 | ex_tmp = ck_alloc(len + MAX_DICT_FILE); 837 | 838 | for (i = 0; i <= len; i++) { 839 | 840 | stage_cur_byte = i; 841 | 842 | for (j = 0; j < extras_cnt; j++) { 843 | 844 | if (len + extras[j].len > MAX_FILE) {// 1 KB을 넘을 때 845 | stage_max--; 846 | continue; 847 | } 848 | 849 | /* Insert token */ 850 | memcpy(ex_tmp + i, extras[j].data, extras[j].len);// 먼저 extras를 복사 851 | 852 | /* Copy tail */ 853 | memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i);// out_buf을 뒤에 붙입니다. 854 | 855 | if (common_fuzz_stuff(argv, ex_tmp, len + extras[j].len)) { 856 | ck_free(ex_tmp); 857 | goto abandon_entry; 858 | } 859 | 860 | stage_cur++; 861 | 862 | } 863 | 864 | /* Copy head */ 865 | ex_tmp[i] = out_buf[i];// out_buf을 ex_tmp의 앞에 붙입니다. (루프가 진행되면서 ex_tmp의 앞에부터 out_buf으로 바뀌어갑니다.) 866 | 867 | } 868 | 869 | ck_free(ex_tmp); 870 | 871 | new_hit_cnt = queued_paths + unique_crashes; 872 | 873 | stage_finds[STAGE_EXTRAS_UI] += new_hit_cnt - orig_hit_cnt; 874 | stage_cycles[STAGE_EXTRAS_UI] += stage_max; 875 | ``` 876 | 877 | 878 | 879 | 자동으로 생성한 사전형 data를 사용해 out_buf를 overwrite 하는 변형에 대한 설명은 처음 설명한 유저측에서 준비한 사전형 data를 사용해 out_buf를 overwrite 하는 변형과 같기에 생략합니다. 880 | 881 | 아래는 설명한 내용에 대한 소스 코드입니다. 그 이외의 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 확인해주시기 바랍니다. 882 | 883 | ```c 884 | // 자동으로 생성한 것으로 out_buf의 overwrite를 수행합니다. 885 | stage_name = "auto extras (over)"; 886 | stage_short = "ext_AO"; 887 | stage_cur = 0; 888 | stage_max = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len; 889 | 890 | stage_val_type = STAGE_VAL_NONE; 891 | 892 | orig_hit_cnt = new_hit_cnt; 893 | 894 | for (i = 0; i < len; i++) { 895 | 896 | u32 last_len = 0; 897 | 898 | stage_cur_byte = i; 899 | 900 | for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) { 901 | 902 | /* See the comment in the earlier code; extras are sorted by size. */ 903 | 904 | if (a_extras[j].len > len - i || 905 | !memcmp(a_extras[j].data, out_buf + i, a_extras[j].len) || 906 | !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, a_extras[j].len))) { 907 | 908 | stage_max--; 909 | continue; 910 | 911 | } 912 | 913 | last_len = a_extras[j].len; 914 | memcpy(out_buf + i, a_extras[j].data, last_len); 915 | 916 | if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; 917 | 918 | stage_cur++; 919 | 920 | } 921 | 922 | /* Restore all the clobbered memory. */ 923 | memcpy(out_buf + i, in_buf + i, last_len); 924 | 925 | } 926 | 927 | new_hit_cnt = queued_paths + unique_crashes; 928 | 929 | stage_finds[STAGE_EXTRAS_AO] += new_hit_cnt - orig_hit_cnt; 930 | stage_cycles[STAGE_EXTRAS_AO] += stage_max; 931 | ``` 932 | 933 | 934 | 935 | ### RANDOM HAVOC (랜덤으로 준비된 방법 선택) 936 | 937 | RANDOM HAVOC 변형에서는 최대 16 패턴의 변형을 랜덤하게 선택하여 out_buf를 변형합니다. 938 | 939 | RANDOM HAVOC 변형 전에 현재 queue에 (queue_cur) RANDOM HAVOC 변형까지의 변형을 끝낸 flag를 (passed_det) 설정합니다. 940 | 941 | 이 flag로 인해 다음부터 RANDOM HAVOC 변형만으로 시작하게 됩니다. (자세히는 변형에 들어가기 전 처리 (data의 점수화)를 참조해주시기 바랍니다.) 942 | 943 | RANDOM HAVOC 변형 설명을 하겠습니다. 944 | 945 | * splice_cycle이 (SPLICING 변형 부분에서 flag가 설정됨) 0 일 경우 다음 처리를 합니다. 946 | 947 | * doing_det이 (PERFORMANCE SCORE를 수행한 flag) 설정된 경우 1024, 그 이외의 경우에는 256이 설정됩니다. 948 | 949 | 이 값들에 perf_score를 (자세히는 변형에 들어가기 전 처리 (data의 점수화)를 참조해주시기 바랍니다.) 100과 havoc_div으로 (평균 실행 속도가 느린 것에 대하여 큰 값이 붙습니다.) 나눕니다. 그 결과를 stage_max 로 합니다. 950 | 951 | * splice_cycle이 (SPLICING 변형 부분에서 flag가 설정됨) 0 이외의 일 경우 다음 처리를 합니다. 952 | 953 | * stage_max는 32 * perf_score / havoc_div / 100 이 됩니다. 954 | 955 | * stage_max가 16보다 작을 경우 16이 됩니다. 956 | 957 | * stage 루프 처리에 들어갑니다. 958 | 959 | * use_stacking은 16 패턴이 있는 변형을 몇 번 반복하는지 루프 처리 최대 값입니다. 이 값은 2 ~ 128 중 랜덤하게 선택됩니다. 960 | 961 | * use_stacking을 최대 값으로한 루프 처리에 들어갑니다. 962 | 963 | * switch문에서 사전형 data가 있으면 true로 2를 더해 0 ~ 16 범위에서 랜덤하게 선택되며, false 이면 0에서 15를 더하여 0 ~ 14 범위에서 랜덤하게 선택됩니다. 964 | * 아래 패턴은 out_buf가 삽입되는 곳, out_buf가 바뀌어 쓰여지는 곳, out_buf에 삽입되는 값, 선택되는 endian 등 모두 랜덤입니다. 965 | 1. byte 단위로 bit 반전처리를 합니다. 966 | 2. 랜덤하게 선택된 1 byte의 interesting value를 삽입 967 | 3. 랜덤하게 선택된 2 byte의 interesting value를 랜덤하게 선택된 endian으로 삽입 968 | 4. 랜덤하게 선택된 4 byte의 interesting value를 랜덤하게 선택된 endian으로 삽입 969 | 5. 랜덤하게 선택된 ARITH_MAX를 1 byte의 out_buf에 감소를 수행 970 | 6. 랜덤하게 선택된 ARITH_MAX를 1 byte의 out_buf에 증가를 수행 971 | 7. 랜덤하게 선택된 ARITH_MAX를 2 byte의 out_buf에 랜덤하게 선택된 endian으로 감소를 수행 972 | 8. 랜덤하게 선택된 ARITH_MAX를 2 byte의 out_buf에 랜덤하게 선택된 endian으로 증가를 수행 973 | 9. 랜덤하게 선택된 ARITH_MAX를 4 byte의 out_buf에 랜덤하게 선택된 endian으로 감소를 수행 974 | 10. 랜덤하게 선택된 ARITH_MAX를 4 byte의 out_buf에 랜덤하게 선택된 endian으로 증가를 수행 975 | 11. 랜덤으로 1 - 255의 값을 사용하여 xor 976 | 12. out_buf의 데이터를 delete 977 | 13. 12 (or l) 와 동일 978 | 14. out_buf에 out_buf 자신을 사용해 복사 또는 insert 979 | 15. out_buf에 out_buf 자신을 사용해 재작성 980 | 16. extras를 사용해 overwrite 981 | 17. extras를 사용해 out_buf에 추가 982 | 983 | * use_stacking 루프를 빠져나오면 common_fuzz_stuff 함수를 실행합니다. 984 | 985 | * out_buf에 in_buf를 사용해 원래의 값으로 되돌립니다. 986 | 987 | * havoc_queued의 (초기값은 queued_paths와 동일합니다.) 값과 일치하지 않을 때 다음의 처리를 수행합니다. 988 | 989 | * perf_score가 havoc의 최대 스코어인 1600 이상이면 stage_max와 perf_score를 2배로 합니다. 990 | 991 | * havoc_queued에 queued_paths를 대입하여 값을 갱신합니다. 992 | 993 | 아래는 설명한 내용에 대한 소스 코드를 기재했습니다. 그 이외 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 확인해주시기 바랍니다. 994 | 995 | ```c 996 | /**************** 997 | * RANDOM HAVOC * 998 | ****************/ 999 | 1000 | havoc_stage: 1001 | 1002 | stage_cur_byte = -1; 1003 | 1004 | /* The havoc stage mutation code is also invoked when splicing files; if the 1005 | splice_cycle variable is set, generate different descriptions and such. */ 1006 | 1007 | if (!splice_cycle) {//retry_splice (havoc의 다음) 때에 flag가 설정됩니다. 1008 | 1009 | stage_name = "havoc"; 1010 | stage_short = "havoc"; 1011 | stage_max = (doing_det ? HAVOC_CYCLES_INIT : HAVOC_CYCLES) * 1012 | perf_score / havoc_div / 100;// doin_det은 performance score를 매겨 그 후의 조건을 통과하면 매겨집니다. (기본 처음 fuzzing 된 것) 1013 | // havoc_div는 평균 실행 속도가 늦으면 늦을수록 값이 큽니다. 1014 | 1015 | } else { 1016 | 1017 | static u8 tmp[32]; 1018 | 1019 | perf_score = orig_perf; 1020 | 1021 | sprintf(tmp, "splice %u", splice_cycle); 1022 | stage_name = tmp; 1023 | stage_short = "splice"; 1024 | stage_max = SPLICE_HAVOC * perf_score / havoc_div / 100; 1025 | 1026 | } 1027 | 1028 | if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN;// 16보다 작을 때는 가장 작은 16의 사이즈를 조정합니다. 1029 | 1030 | temp_len = len; 1031 | 1032 | orig_hit_cnt = queued_paths + unique_crashes; 1033 | 1034 | havoc_queued = queued_paths; 1035 | 1036 | /* We essentially just do several thousand runs (depending on perf_score) 1037 | where we take the input file and make random stacked tweaks. */ 1038 | 1039 | for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { 1040 | 1041 | u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2));// 2부터 2^7(128)까지 랜덤값 1042 | 1043 | stage_cur_val = use_stacking; 1044 | 1045 | for (i = 0; i < use_stacking; i++) { 1046 | // extras_cnt + a_extras_cnt가 있다면 true는 2를 더하여 0부터 16의 범위에서 랜덤화, false 라면 0을 선택 후 15를 더하여 0부터 14의 범위에서 랜덤화를 합니다. 1047 | switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) { 1048 | // byte 단위로 bit 반전처리를 수행합니다. 1049 | case 0: 1050 | 1051 | /* Flip a single bit somewhere. Spooky! */ 1052 | 1053 | FLIP_BIT(out_buf, UR(temp_len << 3));// 1 byte 단위로 bit 반전 1054 | break; 1055 | // 랜덤하게 선택된 1 byte의 interesting value를 삽입 1056 | case 1: 1057 | 1058 | /* Set byte to interesting value. */ 1059 | 1060 | out_buf[UR(temp_len)] = interesting_8[UR(sizeof(interesting_8))];// 1 byte 단위의 interesting value를 랜덤하게 넣습니다. 1061 | break; 1062 | // 랜덤하게 선택된 2 byte의 interesting value를 랜덤하게 선택된 endian으로 삽입 1063 | case 2: 1064 | 1065 | /* Set word to interesting value, randomly choosing endian. */ 1066 | 1067 | if (temp_len < 2) break;// len이 2 byte보다 작을 경우는 종료합니다. 1068 | 1069 | if (UR(2)) {// 1 일 경우 little endian 1070 | 1071 | *(u16*)(out_buf + UR(temp_len - 1)) = 1072 | interesting_16[UR(sizeof(interesting_16) >> 1)];// 2 byte의 interesting value를 랜덤하게 넣습니다. 1073 | 1074 | } else {// 0 일 경우 big endian 1075 | 1076 | *(u16*)(out_buf + UR(temp_len - 1)) = SWAP16( 1077 | interesting_16[UR(sizeof(interesting_16) >> 1)]);// 2 byte의 interesting value를 교체하여 값을 랜덤하게 넣습니다. 1078 | 1079 | } 1080 | 1081 | break; 1082 | // 랜덤하게 선택된 4 byte의 interesting value를 랜덤하게 선택된 endian으로 삽입 1083 | case 3: 1084 | 1085 | /* Set dword to interesting value, randomly choosing endian. */ 1086 | 1087 | if (temp_len < 4) break; 1088 | 1089 | if (UR(2)) { 1090 | 1091 | *(u32*)(out_buf + UR(temp_len - 3)) = 1092 | interesting_32[UR(sizeof(interesting_32) >> 2)]; 1093 | 1094 | } else { 1095 | 1096 | *(u32*)(out_buf + UR(temp_len - 3)) = SWAP32( 1097 | interesting_32[UR(sizeof(interesting_32) >> 2)]); 1098 | 1099 | } 1100 | 1101 | break; 1102 | // 랜덤하게 선택된 ARITH_MAX를 1 byte의 out_buf에 감소를 수행 1103 | case 4: 1104 | 1105 | /* Randomly subtract from byte. */ 1106 | 1107 | out_buf[UR(temp_len)] -= 1 + UR(ARITH_MAX); 1108 | break; 1109 | // 랜덤하게 선택된 ARITH_MAX를 1 byte의 out_buf에 증가를 수행 1110 | case 5: 1111 | 1112 | /* Randomly add to byte. */ 1113 | 1114 | out_buf[UR(temp_len)] += 1 + UR(ARITH_MAX); 1115 | break; 1116 | // 랜덤하게 선택된 ARITH_MAX를 2 byte의 out_buf에 랜덤하게 선택된 endian으로 감소를 수행 1117 | case 6: 1118 | 1119 | /* Randomly subtract from word, random endian. */ 1120 | 1121 | if (temp_len < 2) break; 1122 | 1123 | if (UR(2)) { 1124 | 1125 | u32 pos = UR(temp_len - 1); 1126 | 1127 | *(u16*)(out_buf + pos) -= 1 + UR(ARITH_MAX); 1128 | 1129 | } else { 1130 | 1131 | u32 pos = UR(temp_len - 1); 1132 | u16 num = 1 + UR(ARITH_MAX); 1133 | 1134 | *(u16*)(out_buf + pos) = 1135 | SWAP16(SWAP16(*(u16*)(out_buf + pos)) - num); 1136 | 1137 | } 1138 | 1139 | break; 1140 | // 랜덤하게 선택된 ARITH_MAX를 2 byte의 out_buf에 랜덤하게 선택된 endian으로 증가를 수행 1141 | case 7: 1142 | 1143 | /* Randomly add to word, random endian. */ 1144 | 1145 | if (temp_len < 2) break; 1146 | 1147 | if (UR(2)) { 1148 | 1149 | u32 pos = UR(temp_len - 1); 1150 | 1151 | *(u16*)(out_buf + pos) += 1 + UR(ARITH_MAX); 1152 | 1153 | } else { 1154 | 1155 | u32 pos = UR(temp_len - 1); 1156 | u16 num = 1 + UR(ARITH_MAX); 1157 | 1158 | *(u16*)(out_buf + pos) = 1159 | SWAP16(SWAP16(*(u16*)(out_buf + pos)) + num); 1160 | 1161 | } 1162 | 1163 | break; 1164 | // 랜덤하게 선택된 ARITH_MAX를 4 byte의 out_buf에 랜덤하게 선택된 endian으로 감소를 수행 1165 | case 8: 1166 | 1167 | /* Randomly subtract from dword, random endian. */ 1168 | 1169 | if (temp_len < 4) break; 1170 | 1171 | if (UR(2)) { 1172 | 1173 | u32 pos = UR(temp_len - 3); 1174 | 1175 | *(u32*)(out_buf + pos) -= 1 + UR(ARITH_MAX); 1176 | 1177 | } else { 1178 | 1179 | u32 pos = UR(temp_len - 3); 1180 | u32 num = 1 + UR(ARITH_MAX); 1181 | 1182 | *(u32*)(out_buf + pos) = 1183 | SWAP32(SWAP32(*(u32*)(out_buf + pos)) - num); 1184 | 1185 | } 1186 | 1187 | break; 1188 | // 랜덤하게 선택된 ARITH_MAX를 4 byte의 out_buf에 랜덤하게 선택된 endian으로 증가를 수행 1189 | case 9: 1190 | 1191 | /* Randomly add to dword, random endian. */ 1192 | 1193 | if (temp_len < 4) break; 1194 | 1195 | if (UR(2)) { 1196 | 1197 | u32 pos = UR(temp_len - 3); 1198 | 1199 | *(u32*)(out_buf + pos) += 1 + UR(ARITH_MAX); 1200 | 1201 | } else { 1202 | 1203 | u32 pos = UR(temp_len - 3); 1204 | u32 num = 1 + UR(ARITH_MAX); 1205 | 1206 | *(u32*)(out_buf + pos) = 1207 | SWAP32(SWAP32(*(u32*)(out_buf + pos)) + num); 1208 | 1209 | } 1210 | 1211 | break; 1212 | // 랜덤으로 1 ~ 255 의 값을 사용하여 xor 1213 | case 10: 1214 | 1215 | /* Just set a random byte to a random value. Because, 1216 | why not. We use XOR with 1-255 to eliminate the 1217 | possibility of a no-op. */ 1218 | 1219 | out_buf[UR(temp_len)] ^= 1 + UR(255); 1220 | break; 1221 | // out_buf의 데이터를 delete 1222 | case 11 ... 12: { 1223 | 1224 | /* Delete bytes. We're making this a bit more likely 1225 | than insertion (the next option) in hopes of keeping 1226 | files reasonably small. */ 1227 | 1228 | u32 del_from, del_len; 1229 | 1230 | if (temp_len < 2) break; 1231 | 1232 | /* Don't delete too much. */ 1233 | 1234 | del_len = choose_block_len(temp_len - 1);// delete하는 길이를 지정, delete하는 길이는 최소 사이즈로 정합니다. 1235 | 1236 | del_from = UR(temp_len - del_len + 1);// delete 하는 처음을 랜덤으로 정합니다. 1237 | 1238 | memmove(out_buf + del_from, out_buf + del_from + del_len, 1239 | temp_len - del_from - del_len);// del_from부터 del_len를 더한 곳으로(delete되는 가장 끝부분의 다음) del_from으로 지정합니다. 1240 | 1241 | temp_len -= del_len;// delete한 길이를 뺍니다. 1242 | 1243 | break; 1244 | 1245 | } 1246 | 1247 | // out_buf에 out_buf 자신을 사용해 복사 또는 insert 1248 | case 13: 1249 | 1250 | if (temp_len + HAVOC_BLK_XL < MAX_FILE) {// 1 KB를 넘지 않았을 때 1251 | 1252 | /* Clone bytes (75%) or insert a block of constant bytes (25%). */ 1253 | 1254 | u8 actually_clone = UR(4); 1255 | u32 clone_from, clone_to, clone_len; 1256 | u8* new_buf; 1257 | 1258 | if (actually_clone) {// 1,2,3 일 때 1259 | 1260 | clone_len = choose_block_len(temp_len);// clone할 길이를 선정합니다. 1261 | clone_from = UR(temp_len - clone_len + 1);// clone할 처음을 선정합니다. 1262 | 1263 | } else {// 0 일 때는 강제로 block 단위의 insert가 됩니다. 1264 | 1265 | clone_len = choose_block_len(HAVOC_BLK_XL); 1266 | clone_from = 0; 1267 | 1268 | } 1269 | 1270 | clone_to = UR(temp_len); 1271 | 1272 | new_buf = ck_alloc_nozero(temp_len + clone_len); 1273 | 1274 | /* Head */ 1275 | 1276 | memcpy(new_buf, out_buf, clone_to); 1277 | 1278 | /* Inserted part */ 1279 | 1280 | if (actually_clone) 1281 | memcpy(new_buf + clone_to, out_buf + clone_from, clone_len);// 복사 1282 | else 1283 | memset(new_buf + clone_to, 1284 | UR(2) ? UR(256) : out_buf[UR(temp_len)], clone_len);// insert 1285 | 1286 | /* Tail */ 1287 | memcpy(new_buf + clone_to + clone_len, out_buf + clone_to, 1288 | temp_len - clone_to);// 복사 또는 insert한 다음 부분에 out_buf의 clone 다음의 남은 값을 넣습니다. 1289 | 1290 | ck_free(out_buf); 1291 | out_buf = new_buf;// out_buf의 버튼을 new_buf로 설정합니다. 1292 | temp_len += clone_len;// clone한 길이만큼 더합니다. 1293 | 1294 | } 1295 | 1296 | break; 1297 | // out_buf에 out_buf 자신을 사용해 재작성 1298 | case 14: { 1299 | 1300 | /* Overwrite bytes with a randomly selected chunk (75%) or fixed 1301 | bytes (25%). */ 1302 | 1303 | u32 copy_from, copy_to, copy_len; 1304 | 1305 | if (temp_len < 2) break; 1306 | 1307 | copy_len = choose_block_len(temp_len - 1); 1308 | 1309 | copy_from = UR(temp_len - copy_len + 1); 1310 | copy_to = UR(temp_len - copy_len + 1); 1311 | 1312 | if (UR(4)) {// 75%의 확률로 out_buf 의 값을 사용하여 재작성됩니다. 1313 | 1314 | if (copy_from != copy_to) 1315 | memmove(out_buf + copy_to, out_buf + copy_from, copy_len); 1316 | 1317 | } else memset(out_buf + copy_to, 1318 | UR(2) ? UR(256) : out_buf[UR(temp_len)], copy_len);// 25%의 확률로 이쪽으로 되어있으며 50%의 확률로 0xff로 바뀌어 재작성됩니다. 1319 | 1320 | break; 1321 | 1322 | } 1323 | 1324 | /* Values 15 and 16 can be selected only if there are any extras 1325 | present in the dictionaries. */ 1326 | // extras를 사용해 overwrite 1327 | case 15: { 1328 | 1329 | /* Overwrite bytes with an extra. */ 1330 | 1331 | if (!extras_cnt || (a_extras_cnt && UR(2))) {// extras가 없을 때 또는 자동 extras가 1과 AND 일 때 1332 | 1333 | /* No user-specified extras or odds in our favor. Let's use an 1334 | auto-detected one. */ 1335 | 1336 | u32 use_extra = UR(a_extras_cnt); 1337 | u32 extra_len = a_extras[use_extra].len; 1338 | u32 insert_at; 1339 | 1340 | if (extra_len > temp_len) break; 1341 | 1342 | insert_at = UR(temp_len - extra_len + 1); 1343 | memcpy(out_buf + insert_at, a_extras[use_extra].data, extra_len); 1344 | 1345 | } else {// extras가 있을 때 또는 자동 extras가 0과 AND 일 때 1346 | 1347 | /* No auto extras or odds in our favor. Use the dictionary. */ 1348 | 1349 | u32 use_extra = UR(extras_cnt); 1350 | u32 extra_len = extras[use_extra].len; 1351 | u32 insert_at; 1352 | 1353 | if (extra_len > temp_len) break; 1354 | 1355 | insert_at = UR(temp_len - extra_len + 1); 1356 | memcpy(out_buf + insert_at, extras[use_extra].data, extra_len);// insert_at의 부분부터 바꿔 작성합니다. 1357 | 1358 | } 1359 | 1360 | break; 1361 | 1362 | } 1363 | // extras를 사용해 out_buf에 추가 1364 | case 16: { 1365 | 1366 | u32 use_extra, extra_len, insert_at = UR(temp_len + 1);// out_uf의 랜덤한 길이 1367 | u8* new_buf; 1368 | 1369 | /* Insert an extra. Do the same dice-rolling stuff as for the 1370 | previous case. */ 1371 | 1372 | if (!extras_cnt || (a_extras_cnt && UR(2))) {// extras가 없을 때 또는 자동 extras가 1과 AND 일 때 1373 | 1374 | use_extra = UR(a_extras_cnt); 1375 | extra_len = a_extras[use_extra].len; 1376 | 1377 | if (temp_len + extra_len >= MAX_FILE) break; 1378 | 1379 | new_buf = ck_alloc_nozero(temp_len + extra_len); 1380 | 1381 | /* Head */ 1382 | memcpy(new_buf, out_buf, insert_at);// out_buf의 처음부터 insert_at까지의 길이를 new_buf에 복사합니다. 1383 | 1384 | /* Inserted part */ 1385 | memcpy(new_buf + insert_at, a_extras[use_extra].data, extra_len);// 뒤에 extras를 삽입합니다. 1386 | 1387 | } else {// extras가 있을 때 또는 자동 extras가 0과 AND 일 때 1388 | 1389 | use_extra = UR(extras_cnt); 1390 | extra_len = extras[use_extra].len; 1391 | 1392 | if (temp_len + extra_len >= MAX_FILE) break; 1393 | 1394 | new_buf = ck_alloc_nozero(temp_len + extra_len); 1395 | 1396 | /* Head */ 1397 | memcpy(new_buf, out_buf, insert_at); 1398 | 1399 | /* Inserted part */ 1400 | memcpy(new_buf + insert_at, extras[use_extra].data, extra_len);// 뒤에 extras를 삽입합니다. 1401 | 1402 | } 1403 | 1404 | /* Tail */ 1405 | memcpy(new_buf + insert_at + extra_len, out_buf + insert_at, 1406 | temp_len - insert_at);// out_buf의 나머지를 extras의 뒤에 붙입니다. 1407 | 1408 | ck_free(out_buf); 1409 | out_buf = new_buf; 1410 | temp_len += extra_len; 1411 | 1412 | break; 1413 | 1414 | } 1415 | 1416 | } 1417 | 1418 | } 1419 | 1420 | if (common_fuzz_stuff(argv, out_buf, temp_len)) 1421 | goto abandon_entry; 1422 | 1423 | /* out_buf might have been mangled a bit, so let's restore it to its 1424 | original size and shape. */ 1425 | 1426 | if (temp_len < len) out_buf = ck_realloc(out_buf, len); 1427 | temp_len = len; 1428 | memcpy(out_buf, in_buf, len);// out_buf를 원래대로 되돌립니다. 1429 | 1430 | /* If we're finding new stuff, let's run for a bit longer, limits 1431 | permitting. */ 1432 | 1433 | if (queued_paths != havoc_queued) {// havoc_queued의 (초기값은 queued_paths와 동일) 값과 일치하지 않을 때 1434 | 1435 | if (perf_score <= HAVOC_MAX_MULT * 100) {// HAVOC의 최대 스코어보다 perf_score가 작을 경우 1436 | stage_max *= 2;// stage를 2배로 길게 합니다. 1437 | perf_score *= 2;// score를 2배로 길게 합니다. 1438 | } 1439 | 1440 | havoc_queued = queued_paths;// queued_paths가 증가하므로 수를 경신합니다. 1441 | 1442 | } 1443 | 1444 | } 1445 | 1446 | new_hit_cnt = queued_paths + unique_crashes; 1447 | 1448 | if (!splice_cycle) { 1449 | stage_finds[STAGE_HAVOC] += new_hit_cnt - orig_hit_cnt; 1450 | stage_cycles[STAGE_HAVOC] += stage_max; 1451 | } else { 1452 | stage_finds[STAGE_SPLICE] += new_hit_cnt - orig_hit_cnt; 1453 | stage_cycles[STAGE_SPLICE] += stage_max; 1454 | } 1455 | ``` 1456 | 1457 | 1458 | 1459 | ### SPLICING (data splite) 1460 | 1461 | * 아래 4개의 조건에 해당되어야 SPLICING 변형에 진입할 수 있습니다. 1462 | 1. use_spliceing은 -M option (force_deterministic flag가 설정되지 않았을 때) 이외에는 flag가 설정되어 있을 것. 1463 | 2. splice_cycle과 비교하여 spliceing하는 최대횟수인 14회보다 적을 것. (이 때는 splice_cycle이 증가합니다.) 1464 | 3. queue와 현재 queue의 길이가 2 byte 이상이어야 할 것. 1465 | 4. queue의 수가 (queue_paths) 1보다 많을 것. 1466 | 1467 | 1468 | * in_buf와 orig_in의 address가 일치하지 않을 경우 현재의 in_buf를 해제하여 orig_in과 같은 address로 합니다. 1469 | * 적당히 queue된 test case를 불러옵니다. 이 때, 현재의 test case와 일치하면 같은 처리를 한 번 더 수행합니다. 1470 | * splicing_with에 적당히 불러온 queue를 (tid) 넣습니다. 또, target에 처음의 queue를 넣습니다. 1471 | * tid가 100을 넘기고 있다면 현재 queue부터 시작하여 next_100으로 100개씩 증가합니다. (이 때 tid에서 100이 감소됩니다.) 다음으로 tid가 0가 될 때까지 next로 하나씩 증가합니다. 최종 queue에 다다르기까지 증가합니다. 1472 | * target의 길이가 2 byte보다 아래, 또는 현재의 queue인 경우에는 다음의 target으로 하여 splicing_with를 더합니다. 1473 | * target이 없을 경우에는 처음부터 SPLICING 변형을 다시 수행합니다. 1474 | * target의 data를 new_buf에 복사합니다. 1475 | * locate_diffs 함수를 사용하여 in_buf와 new_buf를 처음부터 비교하여 가장 처음으로 다른 byte와 (f_diff) 가장 마지막으로 다른 byte를 (l_diff) 얻어옵니다. 1476 | * f_diff와 l_diff가 split이 가능하지 않은 길이라면 처음부터 SPLICING 변형을 다시 수행합니다. 1477 | * splite_at에 f_diff + l_diff와 f_diff의 등급을 두어 랜덤하게 선택한 후 그 값을 대입합니다. 1478 | * new_buf의 처음부터 in_buf의 내용을 splite_at의 길이만큼 복사합니다. 1479 | * in_buf에 new_buf의 address를 대입합니다. 1480 | * out_buf의 메모리를 해제한 후 다시 target의 길이만큼 allocate하고, out_buf에 in_buf의 (실질적인 new_buf의 내용) 내용을 복사합니다. 1481 | * RANDOM HAVOC 변형을 수행합니다. 1482 | 1483 | 아래는 설명한 내용에 대한 소스 코드를 기재했습니다. 그 이외 부분은 [GitHub](https://github.com/syarochan/public_study)에 공개한 소스 코드를 확인해주시기 바랍니다. 1484 | ```c 1485 | #ifndef IGNORE_FINDS 1486 | 1487 | /************ 1488 | * SPLICING * 1489 | ************/ 1490 | 1491 | /* This is a last-resort strategy triggered by a full round with no findings. 1492 | It takes the current input file, randomly selects another input, and 1493 | splices them together at some offset, then relies on the havoc 1494 | code to mutate that blob. */ 1495 | 1496 | retry_splicing: 1497 | // use_spliceing은 -M option(force_deterministic flag가 설정되어 있지 않을 때)이외일 때 사용됩니다. 1498 | // spliceing하는 최대횟수는 14회 1499 | // queue와 현재 queue의 길이가 2 byte 이상이어야 합니다. 1500 | if (use_splicing && splice_cycle++ < SPLICE_CYCLES && 1501 | queued_paths > 1 && queue_cur->len > 1) { 1502 | 1503 | struct queue_entry* target; 1504 | u32 tid, split_at; 1505 | u8* new_buf; 1506 | s32 f_diff, l_diff; 1507 | 1508 | /* First of all, if we've modified in_buf for havoc, let's clean that 1509 | up... */ 1510 | 1511 | if (in_buf != orig_in) {// address가 일치하지 않을 경우 현재의 in_buf를 해제하여 원래대로 되돌립니다. 1512 | ck_free(in_buf); 1513 | in_buf = orig_in; 1514 | len = queue_cur->len; 1515 | } 1516 | 1517 | /* Pick a random queue entry and seek to it. Don't splice with yourself. */ 1518 | // 적당히 queue된 test case를 불러옵니다. 이 때, 현재의 test case와 일치하면 한 번 더 불러옵니다. 1519 | do { tid = UR(queued_paths); } while (tid == current_entry); 1520 | 1521 | splicing_with = tid; 1522 | target = queue; 1523 | // tid가 100을 넘는다면 현재 queue부터 next_100을 따라갑니다. 다음으로 tid가 0 될 때까지 next를 따라갑니다. 그리고 최종 tid의 queue에 도착합니다. 1524 | while (tid >= 100) { target = target->next_100; tid -= 100; } 1525 | while (tid--) target = target->next; 1526 | 1527 | /* Make sure that the target has a reasonable length. */ 1528 | // target의 길이가 2 byte보다 아래 또는, 현재 queue인 경우 다음의 target으로 옮깁니다. 1529 | while (target && (target->len < 2 || target == queue_cur)) { 1530 | target = target->next; 1531 | splicing_with++; 1532 | } 1533 | 1534 | if (!target) goto retry_splicing;// target가 없을 경우 한 번 더 처음부터 시작합니다. 1535 | 1536 | /* Read the testcase into a new buffer. */ 1537 | 1538 | fd = open(target->fname, O_RDONLY); 1539 | 1540 | if (fd < 0) PFATAL("Unable to open '%s'", target->fname); 1541 | 1542 | new_buf = ck_alloc_nozero(target->len); 1543 | 1544 | ck_read(fd, new_buf, target->len, target->fname); 1545 | 1546 | close(fd); 1547 | 1548 | /* Find a suitable splicing location, somewhere between the first and 1549 | the last differing byte. Bail out if the difference is just a single 1550 | byte or so. */ 1551 | // in_buf와 new_buf의 가장 처음에 다른 byte와 가장 마지막에 다른 byte를 가져옵니다. 1552 | locate_diffs(in_buf, new_buf, MIN(len, target->len), &f_diff, &l_diff); 1553 | 1554 | if (f_diff < 0 || l_diff < 2 || f_diff == l_diff) {// split이 되지 않는 길이라면 1555 | ck_free(new_buf); 1556 | goto retry_splicing; 1557 | } 1558 | 1559 | /* Split somewhere between the first and last differing byte. */ 1560 | 1561 | split_at = f_diff + UR(l_diff - f_diff);// split의 길이를 결정합니다. 1562 | 1563 | /* Do the thing. */ 1564 | 1565 | len = target->len; 1566 | memcpy(new_buf, in_buf, split_at);// new_buf에 split한 길이만큼 복사합니다. 1567 | in_buf = new_buf; 1568 | 1569 | ck_free(out_buf); 1570 | out_buf = ck_alloc_nozero(len); 1571 | memcpy(out_buf, in_buf, len);// out_buf에 target queue의 길이만큼 out_buf에 복사합니다. 1572 | 1573 | goto havoc_stage; 1574 | 1575 | } 1576 | 1577 | #endif /* !IGNORE_FINDS */ 1578 | ``` 1579 | 1580 | 1581 | 1582 | 지금까지 fuzz_one 함수의 설명이었습니다. 1583 | 1584 | 여유가 생긴다면 undocument 부분도 작성하고 싶지만 피곤하기에 이번 글은 이 정도에서 글을 줄이겠습니다. 1585 | 1586 | 다음은 (만약 있다면) 메모리 관련, undocument 부분, 병행 처리 관련에 대하여 작성할 생각입니다. 1587 | --------------------------------------------------------------------------------