#include "api.h" #include "graphics/icon_file.h" #include "nt/providers.h" #include "nt/nls.h" #include #include #include #include #include #include static void check(bool value,const char *message) { if (!value) throw std::runtime_error(message); } static std::vector read(const char *path) { std::ifstream file(path,std::ios::binary|std::ios::ate); check(bool(file) && file.tellg()>0,"cannot read extraction fixture"); std::vector bytes(size_t(file.tellg()));file.seekg(0); check(bool(file.read(reinterpret_cast(bytes.data()),std::streamsize(bytes.size()))),"short extraction fixture");return bytes; } int main(int argc,char **argv) { try { check(argc==5 || argc==6,"bootstrap, client, PE icons and ICO required"); const auto client=read(argv[2]),pe=read(argv[3]),ico=read(argv[4]); const auto source=[&](std::string_view name)->std::optional> { if (name=="icons.dll") return pe; if (name=="icons.ico" || name=="caf\xc3\xa9.ico") return ico; if (name=="empty.bin") return std::vector{}; if (name=="not-icon.txt") return std::vector{'t','e','x','t'}; return std::nullopt; }; for (const auto bytes:{std::vector{0,0,2,0},std::vector{'B','M'},std::vector{'R','I','F','F'}}) { bool rejected=false;try { raven::IconFile unsupported(bytes); } catch(uint32_t error) { rejected=error==120; } check(rejected,"unsupported icon format reported no icons"); } for (const auto bytes:{std::vector{0,0,1,0},std::vector{0,0,1,0,1,0},std::vector{'M','Z'}}) { bool rejected=false;try { raven::IconFile malformed(bytes); } catch(const std::runtime_error &) { rejected=true; } check(rejected,"malformed icon file accepted"); } raven::ModuleSet modules;auto &entry=modules.add("client.exe",client); const auto run=raven::run_native_process(modules,entry,argv[1],std::chrono::seconds(10),source); check(run.stop==raven::NativeStop::returned && !run.value,"SDK extraction fixture failed"); if (argc==6) { const auto found=std::find_if(entry.exports.begin(),entry.exports.end(),[](const auto &symbol) { return std::find(symbol.names.begin(),symbol.names.end(),"icon_digests")!=symbol.names.end(); }); check(found!=entry.exports.end(),"icon digest export missing");std::ofstream output(argv[5]); const auto data=entry.loaded.data().subspan(found->address,128); for (unsigned i=0;i<16;++i) { uint64_t digest=0;for(unsigned j=0;j<8;++j)digest|=uint64_t(data[i*8+j])<<(j*8); if (!digest) break; output<<"row="<address,16*8192); std::ofstream binary(std::string(argv[5])+".pixels",std::ios::binary); binary.write(reinterpret_cast(bytes.data()),std::streamsize(bytes.size()));check(bool(binary),"cannot write icon pixels"); } check(raven::encode_utf8(u"caf\u00e9 \U0001f600")=="caf\xc3\xa9 \xf0\x9f\x98\x80","UTF-8 filename encoding differs"); for (const auto text:{std::u16string(1,char16_t(0xd800)),std::u16string(1,char16_t(0xdc00))}) { bool rejected=false;try { raven::encode_utf8(text); } catch(uint32_t error) { rejected=error==87; } check(rejected,"unpaired filename surrogate accepted"); } for (size_t size=0;size1024 && size+1(ico).first(size)};accepted=truncated.count()!=0; } catch (const std::runtime_error &) {} check(!accepted,"truncated ICO accepted"); } auto &user=*modules.all().at("user32.dll"); raven::LoadedImage *images[]={&entry.loaded,&user.loaded}; raven::NativeProcess process(images,argv[1],std::chrono::seconds(3)); const auto function=modules.resolve({"user32.dll","PrivateExtractIconsW",0,false,0}); process.add_api_trap(raven::api_trap_address(user,"user32.dll","PrivateExtractIconsW",uint32_t(function-user.loaded.base()))); raven::NativeEnvironment environment(process,entry.loaded.base(),1,1); raven::NativeResources resources(process,environment,modules,entry); raven::DesktopObjects objects;raven::NativeDesktop desktop(process,environment,objects,resources);desktop.set_source(source); const auto page=process.map_memory(8192,PROT_READ|PROT_WRITE);process.protect_memory(page+4096,4096,PROT_NONE); const auto names=process.map_memory(4096,PROT_READ|PROT_WRITE); const auto set_name=[&](std::u16string_view path) { std::vector bytes((path.size()+1)*2); for(size_t i=0;i>8);}process.write_memory(names,bytes); }; const auto call=[&](uint64_t handles,uint64_t ids,uint32_t count=1,uint32_t flags=0) { const uint64_t args[]={names,0,32,32,handles,ids,count,flags}; check(process.call(function,args,std::chrono::seconds(1)).stop==raven::NativeStop::api,"extraction API did not trap"); check(desktop.dispatch("PrivateExtractIconsW"),"extraction API not dispatched"); const auto result=process.resume(std::chrono::seconds(1));check(result.stop==raven::NativeStop::returned,"extraction API did not return");return result.value; }; set_name(u"icons.ico"); const uint64_t poisoned[]={names,0,32,32,page,0,(1ULL<<40)|1,0}; check(process.call(function,poisoned,std::chrono::seconds(1)).stop==raven::NativeStop::api,"UINT width API did not trap"); desktop.dispatch("PrivateExtractIconsW");check(process.resume(std::chrono::seconds(1)).value==1 && objects.count()==1,"UINT argument used unspecified upper bits"); const auto poisoned_handle=process.read_memory(page,8);uint64_t owned=0;for(unsigned i=0;i<8;++i)owned|=uint64_t(poisoned_handle[i])<<(i*8); check(objects.erase(owned),"UINT width control leaked an icon"); set_name(u".\\ICONS.ICO"); check(call(page+4096-8,page+4096-12)==1 && objects.count()==1,"page-boundary extraction failed"); const auto handles=process.read_memory(page+4096-8,8);uint64_t handle=0;for(unsigned i=0;i<8;++i)handle|=uint64_t(handles[i])<<(i*8); check(objects.icon(handle) && !objects.icon(handle)->shared && objects.erase(handle) && !objects.count(),"extracted icon lifetime differs"); set_name(u"caf\u00e9.ico");check(call(0,0,0)==1,"Unicode filename failed"); set_name(u"Z:\\icons.ico");check(call(0,0,0)==UINT32_MAX && environment.last_error()==120,"unsupported namespace disguised as missing file"); set_name(u"icons.ico");check(call(page,0,1025)==UINT32_MAX && environment.last_error()==120 && !objects.count(),"extraction count budget ignored"); check(call(page,0,1,1)==UINT32_MAX && environment.last_error()==120,"unsupported monochrome flag accepted"); check(call(0,page,1)==UINT32_MAX && environment.last_error()==120,"unqualified null-handle output combination accepted"); raven::DesktopObjects tiny(1);raven::NativeDesktop limited(process,environment,tiny,resources);limited.set_source(source); const uint64_t args[]={names,0,32,32,page,0,1,0}; check(process.call(function,args,std::chrono::seconds(1)).stop==raven::NativeStop::api,"budget API did not trap"); limited.dispatch("PrivateExtractIconsW");check(process.resume(std::chrono::seconds(1)).value==UINT32_MAX && environment.last_error()==8 && !tiny.count(),"icon budget leaked a resource"); set_name(u"icons.dll"); const auto saved=process.read_memory(names+512,16); bool guarded=false; try { call(names+512,page+4092,2); } catch (const std::runtime_error &) { guarded=true; } check(guarded && !objects.count() && process.read_memory(names+512,16)==saved,"guarded IDs published handles or leaked staged icons"); process.return_api(0);process.resume(std::chrono::seconds(1)); set_name(u"icons.ico"); process.protect_memory(page,4096,PROT_READ); check(process.call(function,args,std::chrono::seconds(1)).stop==raven::NativeStop::api,"readonly API did not trap"); bool rejected=false;try { desktop.dispatch("PrivateExtractIconsW"); } catch(const std::runtime_error &) { rejected=true; } check(rejected && !objects.count(),"readonly output leaked an icon");process.return_api(0);process.resume(std::chrono::seconds(1)); } catch (const std::exception &error) { std::cerr<