├── 2023 └── zer0con │ ├── README.md │ ├── images │ ├── logos │ │ ├── 0x3c3e.png │ │ └── zerocon.png │ ├── memes │ │ └── bloodborne.jpg │ └── messages │ │ └── twitter.png │ └── slides.pdf └── README.md /2023/zer0con/README.md: -------------------------------------------------------------------------------- 1 | # `CodeQL + DTrace = 💧🐞 in XNU` 2 | ### How to find multiple memory disclosures in XNU using CodeQL 3 | 4 | --- 5 | # whoami 6 | ## Arsenii Kostromin 7 | * Security researcher 8 | * Focus on macOS security: userland and kernel 9 | * Twitter [@0x3C3E](https://twitter.com/0x3C3E) 10 | 11 | --- 12 | # Agenda 13 | Kernel Memory Disclosure, my 🥇 and 🥈 bugs in XNU 14 | 15 | --- 16 | # Motivation 17 | Apple interviewer asked me several times why I don't look for bugs in the kernel 18 | * `Is it hard for you?` 19 | * Before `December 2022`, I haven't looked into the `XNU` source code 20 | 21 | --- 22 | ![bg](images/memes/bloodborne.jpg) 23 | 24 | --- 25 | # Kernel Memory Disclosure 🥇 26 | 27 | --- 28 | # My approach 29 | * Search online and [tag](https://raindrop.io/quodro/mac-os-31318426) writeups 30 | * Prepare a debugging environment 31 | * Use CodeQL to search for some patterns 32 | 33 | --- 34 | # Some `easy` bugs in XNU 35 | * [A tale of a simple Apple kernel bug](https://pwning.systems/posts/easy-apple-kernel-bug/) 36 | * [Weggli](https://github.com/weggli-rs/weggli) was used to find a specific pattern 37 | * [Finding a memory exposure vulnerability with CodeQL](https://securitylab.github.com/research/apple-xnu-dtrace-CVE-2017-13782/) 38 | * [CodeQL](https://codeql.github.com/) was used, the author found a bug in the DTrace module of XNU 39 | --- 40 | # How to debug kernel on a single M1 laptop? 41 | * [QEMU](https://github.com/utmapp/UTM) emulates Intel-based macOS 42 | * [DTrace](https://en.wikipedia.org/wiki/DTrace), dynamic tracing framework in XNU 43 | 44 | --- 45 | # DTrace 46 | * Released in `2005` by Oracle 47 | * Apple merged it into XNU in `2007` 48 | * Was it thoroughly audited? 49 | * It's complex and has its emulator in `the kernel` 50 | 51 |
52 | 53 | ```c 54 | #define DIF_OP_OR 1 /* or r1, r2, rd */ 55 | #define DIF_OP_XOR 2 /* xor r1, r2, rd */ 56 | ... 57 | #define DIF_OP_STRIP 80 /* strip r1, key, rd */ 58 | ``` 59 | 60 | 61 | --- 62 | # CodeQL 63 | * Framework for doing static analysis 64 | * Models code as data → database 65 | * Write logic-based SQL-like queries to find patterns 66 | 67 | --- 68 | # Building a CodeQL database 69 | * Have to compile the program we want to query 70 | * By default, some files were [missing](https://github.com/github/codeql-cli-binaries/issues/145) 71 | * A great [script](https://github.com/pwn0rz/xnu-build) to build a CodeQL database for XNU by pwn0rz 72 | 73 | --- 74 | # Code pattern 75 | I decided to look for OOB issues. For that, I wrote a query to find such code, which meets the conditions below: 76 | * `a >= b`, where `a` is signed, and `b` is not 77 | * No `a <= 0` and `a < 0` checks 78 | * `a` is an array index 79 | 80 | --- 81 | # `a >= b`, where `a` is signed, and `b` is not 82 | ```sql 83 | from Variable arg 84 | where exists( 85 | GEExpr ge | ge.getLeftOperand() = arg.getAnAccess() 86 | and ge.getLeftOperand(). 87 | getExplicitlyConverted(). 88 | getUnderlyingType().(IntegralType).isSigned() 89 | and ge.getRightOperand(). 90 | getExplicitlyConverted(). 91 | getUnderlyingType().(IntegralType).isUnsigned() 92 | ) 93 | select arg 94 | ``` 95 | 96 | --- 97 | # No `a < 0` and `a <= 0` checks 98 | ```sql 99 | from Variable arg 100 | where not exists( 101 | LTExpr le | le.getLeftOperand() = arg.getAnAccess() 102 | and le.getRightOperand().getValue() = "0" 103 | ) 104 | and not exists( 105 | LEExpr le | le.getLeftOperand() = arg.getAnAccess() 106 | and le.getRightOperand().getValue() = "0" 107 | ) 108 | select arg 109 | ``` 110 | --- 111 | ## `a` is an array index 112 | ```sql 113 | from Variable arg, ArrayExpr ae 114 | where ae.getArrayOffset() = arg.getAnAccess() 115 | select ae.getArrayOffset(), 116 | ae.getEnclosingFunction() 117 | ``` 118 | 119 | --- 120 | # Combined 121 | ```sql 122 | from Variable arg, ArrayExpr ae 123 | where exists( 124 | GEExpr ge | ge.getLeftOperand() = arg.getAnAccess() 125 | and ge.getLeftOperand(). 126 | getExplicitlyConverted(). 127 | getUnderlyingType().(IntegralType).isSigned() 128 | and ge.getRightOperand(). 129 | getExplicitlyConverted(). 130 | getUnderlyingType().(IntegralType).isUnsigned() 131 | ) 132 | and not exists( 133 | LTExpr le | le.getLeftOperand() = arg.getAnAccess() 134 | and le.getRightOperand().getValue() = "0" 135 | ) 136 | and not exists( 137 | LEExpr le | le.getLeftOperand() = arg.getAnAccess() 138 | and le.getRightOperand().getValue() = "0" 139 | ) 140 | and ae.getArrayOffset() = arg.getAnAccess() 141 | select ae.getArrayOffset(), 142 | ae.getEnclosingFunction() 143 | ``` 144 | --- 145 | # The query produces 146 | * `20` results 147 | * Only `6` different functions 148 | 149 | --- 150 | ### `fasttrap_pid_getargdesc` [^1] 151 | ```c 152 | // args: (void *arg, dtrace_id_t id, void *parg, dtrace_argdesc_t *desc) 153 | if (probe->ftp_prov->ftp_retired != 0 || 154 | desc->dtargd_ndx >= probe->ftp_nargs) { 155 | desc->dtargd_ndx = DTRACE_ARGNONE; 156 | return; 157 | } 158 | 159 | ndx = (probe->ftp_argmap != NULL) ? 160 | probe->ftp_argmap[desc->dtargd_ndx] : desc->dtargd_ndx; 161 | ``` 162 | Docs: get the argument description for args[X] 163 | 164 | [^1]: [bsd/dev/dtrace/fasttrap.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/fasttrap.c#L1367-L1374) 165 | --- 166 | 167 | ### `dtargd_ndx` is `int` [^2] 168 | ```c {3} 169 | typedef struct dtrace_argdesc { 170 | ... 171 | int dtargd_ndx; /* arg number (-1 iff none) */ 172 | ... 173 | } dtrace_argdesc_t; 174 | ``` 175 | [^2]: [bsd/sys/dtrace.h](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/sys/dtrace.h#L1331-L1337) 176 | 177 | ### `ftp_nargs` is `unsigned char` [^3] 178 | ```c 179 | struct fasttrap_probe { 180 | ... 181 | uint8_t ftp_nargs; /* translated argument count */ 182 | ... 183 | }; 184 | ``` 185 | 186 | [^3]: [bsd/sys/fasttrap_impl.h](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/sys/fasttrap_impl.h#L119-L134) 187 | 188 | --- 189 | # Both sides are converted to `int` 190 | As `desc->dtargd_ndx` is `int` and `probe->ftp_nargs` is `unsigned char` 191 | ```c {2} 192 | if (probe->ftp_prov->ftp_retired != 0 || 193 | desc->dtargd_ndx >= probe->ftp_nargs) { 194 | desc->dtargd_ndx = DTRACE_ARGNONE; 195 | return; 196 | } 197 | ``` 198 | If `desc->dtargd_ndx < 0`, then `desc->dtargd_ndx >= probe->ftp_nargs` is always `false` 199 | 200 | --- 201 | ## ⚔️ OOB Read, `desc->dtargd_ndx` is an index 202 | ```c {2} 203 | ndx = (probe->ftp_argmap != NULL) ? 204 | probe->ftp_argmap[desc->dtargd_ndx] : desc->dtargd_ndx; 205 | ``` 206 | If `probe->ftp_argmap` isn't `null`, it's possible to reach the first expression and use 207 | `desc->dtargd_ndx` with values less than `0` 208 | 209 | --- 210 | # No direct calls to the function 211 | It's called as a C-style `virtual function` 212 | 213 | --- 214 | ### `dtrace_pops` [^4] 215 | ```c {3-4} 216 | typedef struct dtrace_pops { 217 | ... 218 | void (*dtps_getargdesc)(void *arg, dtrace_id_t id, void *parg, 219 | dtrace_argdesc_t *desc); 220 | ... 221 | } dtrace_pops_t; 222 | ``` 223 | [^4]: [bsd/sys/dtrace.h](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/sys/dtrace.h#L2316-L2317) 224 | 225 | ### `dtrace_pops_t` [^5] 226 | ```c {3} 227 | static dtrace_pops_t pid_pops = { 228 | ... 229 | .dtps_getargdesc = fasttrap_pid_getargdesc, 230 | ... 231 | }; 232 | ``` 233 | [^5]: [bsd/dev/dtrace/fasttrap.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/fasttrap.c#L1440) 234 | 235 | 236 | --- 237 | ### `dtps_getargdesc` might be a pointer to `fasttrap_pid_getargdesc` [^6] 238 | ```c {1,6} 239 | prov->dtpv_pops.dtps_getargdesc( 240 | prov->dtpv_arg, 241 | probe->dtpr_id, 242 | probe->dtpr_arg, 243 | &desc 244 | ); 245 | ``` 246 | [^6]: [bsd/dev/dtrace/dtrace.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/dtrace.c#L18251-L18252) 247 | 248 | --- 249 | ### Upper bound check in `fasttrap_pid_getargdesc` [^7] 250 | ```c {2} 251 | if (probe->ftp_prov->ftp_retired != 0 || 252 | desc->dtargd_ndx >= probe->ftp_nargs) { 253 | desc->dtargd_ndx = DTRACE_ARGNONE; 254 | return; 255 | } 256 | ``` 257 | 258 | ### Comparing to `-1` in `dtrace_ioctl` [^8] 259 | ```c {1} 260 | if (desc.dtargd_ndx == DTRACE_ARGNONE) 261 | return (EINVAL); 262 | ``` 263 | [^7]: [bsd/dev/dtrace/fasttrap.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/fasttrap.c#L1367-L1371) 264 | [^8]: [bsd/dev/dtrace/dtrace.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/dtrace.c#L18214-L18215) 265 | 266 | --- 267 | # How to leak out-of-bounds values? 268 | ```c {2,4,9} 269 | ndx = (probe->ftp_argmap != NULL) ? 270 | probe->ftp_argmap[desc->dtargd_ndx] : desc->dtargd_ndx; 271 | 272 | str = probe->ftp_ntypes; 273 | for (i = 0; i < ndx; i++) { 274 | str += strlen(str) + 1; 275 | } 276 | 277 | (void) strlcpy(desc->dtargd_native, str, sizeof(desc->dtargd_native)); 278 | ``` 279 | * We control integer index `desc->dtargd_ndx` and array of `null` delimited strings `probe->ftp_ntypes` (array of chars) 280 | * We have to leak `probe->ftp_argmap[desc->dtargd_ndx]` (`ndx` is integer) value into `desc->dtargd_native` 281 | 282 | --- 283 | # The idea 284 | ```c 285 | str = probe->ftp_ntypes; // { 1, 1, 0, 1, 0, 2, 0, 3, 0, ...} 286 | for (i = 0; i < ndx; i++) { // ndx is a value to leak 287 | str += strlen(str) + 1; 288 | } 289 | (void) strlcpy(desc->dtargd_native, str, sizeof(desc->dtargd_native)); 290 | ``` 291 | * We could populate `probe->ftp_ntypes` with an array of null delimited strings 292 | * `[1, 1, 0, 1, 0, 2, 0, 3, 0, ..., 255]` from 0 to 255 (showed as bytes) 293 | * Encode `0` for example as `[1, 1, 0]`, so it's copied to the userland 294 | * Then `ndx` equals to value in `str` 295 | * Special case — `0` is `"\x01\x01\x00"` 296 | 297 | --- 298 | # `ndx = 0` 299 | ```c {2} 300 | str = probe->ftp_ntypes; // { 1, 1, 0, 1, 0, 2, 0, 3, 0, ...} 301 | for (i = 0; i < ndx; i++) { // ^ 302 | str += strlen(str) + 1; 303 | } 304 | // str points to "\x01\x01\x00" 305 | (void) strlcpy(desc->dtargd_native, str, sizeof(desc->dtargd_native)); 306 | ``` 307 | 308 | # `ndx = 1` 309 | ```c {2} 310 | str = probe->ftp_ntypes; // { 1, 1, 0, 1, 0, 2, 0, 3, 0, ...} 311 | for (i = 0; i < ndx; i++) { // ^ 312 | str += strlen(str) + 1; 313 | } 314 | // str points to "\x01\x00" 315 | (void) strlcpy(desc->dtargd_native, str, sizeof(desc->dtargd_native)); 316 | ``` 317 | 318 | --- 319 | # How to reach? 320 | `_dtrace_ioctl` → `DTRACEIOC_PROBEARG` switch case → `fasttrap_pid_getargdesc` 321 | 322 | --- 323 | # [CVE-2023-27941](https://support.apple.com/en-us/HT213670) 324 | 325 | #### Kernel 326 | `Available for: macOS Ventura` 327 | 328 | `Impact: An app may be able to disclose kernel memory` 329 | 330 | `Description: An out-of-bounds read issue existed that led to the disclosure of kernel memory. This was addressed with improved input validation.` 331 | 332 | #### Details 333 | * The bug allows reading data byte by byte in a range of 2GB 334 | * Requires root access 335 | 336 | --- 337 | # Patch 338 | Reversed `fasttrap_pid_getargdesc` changes 339 | ```c {2} 340 | if (probe->ftp_prov->ftp_retired != 0 || 341 | desc->dtargd_ndx < 0 || // added 342 | desc->dtargd_ndx >= probe->ftp_nargs) { 343 | desc->dtargd_ndx = DTRACE_ARGNONE; 344 | return; 345 | } 346 | ``` 347 | * Apple hasn't released the new `XNU` source code 348 | * upd: [xnu-8792.81.2 and 8796.101.5 diff](https://github.com/apple-oss-distributions/xnu/compare/xnu-8792.81.2...xnu-8796.101.5?diff=unified#diff-60c1704f05c8cd2332cecee78d2f218865cdec7bc930d2401443d0b5a7913e02L1367-L1370) 349 | 350 | --- 351 | # Kernel Memory Disclosure 🥈 352 | 353 | --- 354 | # Code pattern 355 | * `a < b`, where `a` is signed 356 | * The comparison above happens in `IfStmt` 357 | * No `a <= 0` and `a < 0` checks 358 | * `a` is an array index 359 | 360 | --- 361 | # `a < b`, where `a` is signed, happens in `IfStmt` 362 | ```sql 363 | from Variable arg 364 | where exists( 365 | LTExpr le | 366 | le.getLeftOperand() = arg.getAnAccess() 367 | and le.getParent() instanceof IfStmt 368 | and le.getLeftOperand(). 369 | getExplicitlyConverted(). 370 | getUnderlyingType().(IntegralType).isSigned() 371 | ) 372 | select arg 373 | ``` 374 | `IfStmt` is `if (a < b) {}`, but not `a < b` in `for (a = 0; a < b; a++)` 375 | 376 | --- 377 | # No `a < 0` and `a <= 0` checks 378 | ```sql 379 | from Variable arg 380 | where not exists( 381 | LTExpr le | le.getLeftOperand() = arg.getAnAccess() 382 | and le.getRightOperand().getValue() = "0" 383 | ) 384 | and not exists( 385 | LEExpr le | le.getLeftOperand() = arg.getAnAccess() 386 | and le.getRightOperand().getValue() = "0" 387 | ) 388 | select arg 389 | ``` 390 | 391 | --- 392 | # `a` is an array index 393 | ```sql 394 | from Variable arg, ArrayExpr ae 395 | where ae.getArrayOffset() = arg.getAnAccess() 396 | select ae.getArrayOffset(), 397 | ae.getEnclosingFunction() 398 | ``` 399 | 400 | --- 401 | # Filter results by a file path 402 | ```sql 403 | from ArrayExpr ae 404 | where ae.getFile().getAbsolutePath(). 405 | matches("%/xnu-build/xnu/%") 406 | and not ae.getFile().getAbsolutePath(). 407 | matches("%/xnu-build/xnu/SETUP/%") 408 | select ae.getArrayOffset(), 409 | ae.getEnclosingFunction() 410 | ``` 411 | 412 | --- 413 | # Combined 414 | ```sql 415 | from Variable arg, ArrayExpr ae 416 | where exists( 417 | LTExpr le | 418 | le.getLeftOperand() = arg.getAnAccess() 419 | and le.getParent() instanceof IfStmt 420 | and le.getLeftOperand(). 421 | getExplicitlyConverted(). 422 | getUnderlyingType().(IntegralType).isSigned() 423 | ) 424 | and not exists( 425 | LTExpr le | le.getLeftOperand() = arg.getAnAccess() 426 | and le.getRightOperand().getValue() = "0" 427 | ) 428 | and not exists( 429 | LEExpr le | le.getLeftOperand() = arg.getAnAccess() 430 | and le.getRightOperand().getValue() = "0" 431 | ) 432 | and ae.getArrayOffset() = arg.getAnAccess() 433 | and ae.getFile().getAbsolutePath().matches("%/xnu-build/xnu/%") 434 | and not ae.getFile().getAbsolutePath().matches("%/xnu-build/xnu/SETUP/%") 435 | select ae.getArrayOffset(), 436 | ae.getEnclosingFunction() 437 | ``` 438 | --- 439 | # The query produces 440 | * `169` results 441 | * Only `45` different functions 442 | 443 | --- 444 | ### ⚔️ OOB Read, `argno` is an index on `arm64` [^9] 445 | ```c 446 | uint64_t 447 | fasttrap_pid_getarg(void *arg, dtrace_id_t id, void *parg, int argno, 448 | int aframes) 449 | { 450 | arm_saved_state_t* regs = find_user_regs(current_thread()); 451 | 452 | /* First eight arguments are in registers */ 453 | if (argno < 8) { 454 | return saved_state64(regs)->x[argno]; 455 | } 456 | ``` 457 | Docs: get the value for an argX or args[X] variable 458 | [^9]: [bsd/dev/arm64/fasttrap_isa.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/arm64/fasttrap_isa.c#L1156-L1176) 459 | 460 | --- 461 | ### ⚔️ OOB Read, `argno` is an index on `x86_64` [^10] 462 | ```c 463 | uint64_t 464 | fasttrap_pid_getarg(void* arg, dtrace_id_t id, void* parg, int argno, 465 | int aframes) 466 | { 467 | pal_register_cache_state(current_thread(), VALID); 468 | return (fasttrap_anarg( 469 | (x86_saved_state_t*)find_user_regs(current_thread()), 470 | 1, 471 | argno)); 472 | } 473 | ``` 474 | ### `fasttrap_anarg` [^11] 475 | ```c 476 | // args: (x86_saved_state_t *regs, int function_entry, int argno) 477 | if (argno < 6) 478 | return ((®s64->rdi)[argno]); 479 | ``` 480 | 481 | [^10]: [bsd/dev/i386/fasttrap_isa.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/i386/fasttrap_isa.c#L2207-L2214) 482 | [^11]: [bsd/dev/i386/fasttrap_isa.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/i386/fasttrap_isa.c#L206-L248) 483 | 484 | --- 485 | ### `dtrace_pops` [^12] 486 | ```c 487 | typedef struct dtrace_pops { 488 | ... 489 | uint64_t (*dtps_getargval)(void *arg, dtrace_id_t id, void *parg, 490 | int argno, int aframes); 491 | ... 492 | } dtrace_pops_t; 493 | ``` 494 | [^12]: [bsd/sys/dtrace.h](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/sys/dtrace.h#L2316-L2317) 495 | 496 | 497 | ### `dtrace_pops_t` [^13] 498 | ```c 499 | static dtrace_pops_t pid_pops = { 500 | ... 501 | .dtps_getargval = fasttrap_pid_getarg, 502 | ... 503 | }; 504 | ``` 505 | [^13]: [bsd/dev/dtrace/fasttrap.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/fasttrap.c#L1433-L1457) 506 | 507 | --- 508 | ### `dtps_getargval` might be a pointer to `fasttrap_pid_getarg` [^14] 509 | ```c 510 | // func: dtrace_dif_variable 511 | // args: (dtrace_mstate_t *mstate, dtrace_state_t *state, uint64_t v, 512 | // uint64_t ndx) 513 | val = pv->dtpv_pops.dtps_getargval(pv->dtpv_arg, 514 | mstate->dtms_probe->dtpr_id, 515 | mstate->dtms_probe->dtpr_arg, ndx, aframes); 516 | ``` 517 | [^14]: [bsd/dev/dtrace/dtrace.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/dtrace.c#L3308-L3310) 518 | 519 | --- 520 | # Bounds check? 521 | ```c {4} 522 | // func: dtrace_dif_variable 523 | // args: (dtrace_mstate_t *mstate, dtrace_state_t *state, uint64_t v, 524 | // uint64_t ndx) 525 | if (ndx >= sizeof (mstate->dtms_arg) / sizeof (mstate->dtms_arg[0])) { 526 | ... 527 | dtrace_provider_t *pv; 528 | uint64_t val; 529 | 530 | pv = mstate->dtms_probe->dtpr_provider; 531 | if (pv->dtpv_pops.dtps_getargval != NULL) 532 | val = pv->dtpv_pops.dtps_getargval(pv->dtpv_arg, 533 | mstate->dtms_probe->dtpr_id, 534 | mstate->dtms_probe->dtpr_arg, ndx, aframes); 535 | ``` 536 | `ndx` is an `unsigned long long`, later it's converted into an `int` in `fasttrap_pid_getarg`, `argno` argument 537 | 538 | --- 539 | # How to reach? 540 | `dtrace_dif_emulate` → `DIF_OP_LDGA` opcode → `dtrace_dif_variable` → `fasttrap_pid_getarg` 541 | 542 | --- 543 | # An old PoC helped to trigger the vulnerable function 544 | Almost the same code flow as in [CVE-2017-13782](https://securitylab.github.com/research/apple-xnu-dtrace-CVE-2017-13782/) by Kevin Backhouse 545 | * But you have to use a `fasttrap` provider, which allows tracing userland functions 546 | * It's possible to define a function `void foo() {}` 547 | * Trace it using DTrace: `pid$target::foo:entry { ... }` 548 | 549 | --- 550 | # Code flow difference [^15] 551 | ```c {3-5,8} 552 | pv = mstate->dtms_probe->dtpr_provider; 553 | if (pv->dtpv_pops.dtps_getargval != NULL) 554 | val = pv->dtpv_pops.dtps_getargval(pv->dtpv_arg, 555 | mstate->dtms_probe->dtpr_id, 556 | mstate->dtms_probe->dtpr_arg, ndx, aframes); // CVE-2023-28200 557 | ... 558 | else 559 | val = dtrace_getarg(ndx, aframes, mstate, vstate); // CVE-2017-13782 560 | ``` 561 | * `9` lines difference 562 | [^15]: [bsd/dev/dtrace/dtrace.c](https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/dev/dtrace/dtrace.c#L3306-L3317) 563 | 564 | --- 565 | # [CVE-2023-28200](https://support.apple.com/en-us/HT213670) 566 | 567 | #### Kernel 568 | `Available for: macOS Ventura` 569 | 570 | `Impact: An app may be able to disclose kernel memory` 571 | 572 | `Description: A validation issue was addressed with improved input sanitization.` 573 | #### Details 574 | * The bug allows reading data in a range of 16GB 575 | * Requires root access 576 | 577 | --- 578 | # Patch 579 | Reversed `dtrace_dif_variable` changes 580 | ```c {2} 581 | if (ndx >= sizeof (mstate->dtms_arg) / sizeof (mstate->dtms_arg[0])) { 582 | if ((ndx & 0x80000000) != 0) return 0; // added 583 | ... 584 | dtrace_provider_t *pv; 585 | uint64_t val; 586 | 587 | pv = mstate->dtms_probe->dtpr_provider; 588 | if (pv->dtpv_pops.dtps_getargval != NULL) 589 | val = pv->dtpv_pops.dtps_getargval(pv->dtpv_arg, 590 | mstate->dtms_probe->dtpr_id, 591 | mstate->dtms_probe->dtpr_arg, ndx, aframes); 592 | ``` 593 | * Additional check added in caller function 594 | * Callee functions are unfixed for some reason 595 | * Apple hasn't released the new XNU source code 596 | * upd: [8792.81.2 and 8796.101.5 diff](https://github.com/apple-oss-distributions/xnu/compare/xnu-8792.81.2...xnu-8796.101.5?diff=unified#diff-8689fbf120ca6acdbe298fa3b4949bd4fee3531e9ba671460d74569b6a9dda21R3307-R3310) 597 | 598 | 599 | --- 600 | # 😿😿😿 601 | ![bg fit](images/messages/twitter.png) [^16] 602 | [^16]: [@jaakerblom](https://twitter.com/jaakerblom/status/1565049707759828992) 603 | 604 | --- 605 | # Why? 606 | * `root` access != `kernel` access on macOS 607 | * `SIP` puts the whole system into a sandbox 608 | * even `root` can't load untrusted kernel extensions 609 | * \+ I had `App Sandbox Escape` → `user to root` LPE chain 610 | 611 | --- 612 | # PoCs 613 | * [CVE-2023-27941](https://github.com/0x3c3e/pocs/tree/main/CVE-2023-27941) matches kernel addresses from leaked data 614 | * [CVE-2023-28200](https://github.com/0x3c3e/pocs/tree/main/CVE-2023-28200) only panics the kernel 615 | 616 | --- 617 | # Conclusion 618 | * Apple has to maintain two architectures: `x86_64` and `arm64` 619 | * C-like `virtual functions` make `static` analysis harder 620 | 621 | --- 622 | # Resources 623 | * [Real hackers don't leave DTrace](https://www.synacktiv.com/sites/default/files/2022-05/real_hackers_don-t_leave_dtrace.pdf) 624 | * [Finding a memory exposure vulnerability with CodeQL](https://securitylab.github.com/research/apple-xnu-dtrace-CVE-2017-13782/) 625 | * [There is no S in macOS SIP](https://s.itho.me/ccms_slides/2022/9/29/367d1246-198f-40c5-95bf-2916bcb33f14.pdf) 626 | 627 | --- 628 | # Thank you 629 | -------------------------------------------------------------------------------- /2023/zer0con/images/logos/0x3c3e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x3c3e/slides/f116c87a30306b2ceedea8adc125551f628d8a70/2023/zer0con/images/logos/0x3c3e.png -------------------------------------------------------------------------------- /2023/zer0con/images/logos/zerocon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x3c3e/slides/f116c87a30306b2ceedea8adc125551f628d8a70/2023/zer0con/images/logos/zerocon.png -------------------------------------------------------------------------------- /2023/zer0con/images/memes/bloodborne.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x3c3e/slides/f116c87a30306b2ceedea8adc125551f628d8a70/2023/zer0con/images/memes/bloodborne.jpg -------------------------------------------------------------------------------- /2023/zer0con/images/messages/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x3c3e/slides/f116c87a30306b2ceedea8adc125551f628d8a70/2023/zer0con/images/messages/twitter.png -------------------------------------------------------------------------------- /2023/zer0con/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x3c3e/slides/f116c87a30306b2ceedea8adc125551f628d8a70/2023/zer0con/slides.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slides 2 | 3 | | Conference | Year | Name | Description | 4 | |------------ |------ |----------------------------- |------------------------------------------------------------- | 5 | | [Zer0Con](https://zer0con.org/archive/2023.html) | 2023 | [CodeQL + DTrace = 💧🐞 in XNU](2023/zer0con) | How to find multiple memory disclosures in XNU using CodeQL | 6 | --------------------------------------------------------------------------------