From 2aa8af666f66a513f0c6a06d6239d00e9e25264b Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 7 Dec 2017 22:08:08 +0100 Subject: [PATCH 01/43] with -s there --- bin/ocra | 2 +- lib/ocra.rb | 2 +- src/Makefile | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/ocra b/bin/ocra index 81743cdb..c51754be 100644 --- a/bin/ocra +++ b/bin/ocra @@ -179,7 +179,7 @@ module Ocra a.sort.inject([]) { |r, e| r.last == e ? r : r << e } end - VERSION = "1.3.10" + VERSION = "1.4.666" IGNORE_MODULES = /\/(enumerator.so|rational.so|complex.so|thread.rb)$/ diff --git a/lib/ocra.rb b/lib/ocra.rb index f63a0ba3..43ec09e9 100644 --- a/lib/ocra.rb +++ b/lib/ocra.rb @@ -1,3 +1,3 @@ class Ocra - VERSION = '1.3.10' + VERSION = '1.4.666' end diff --git a/src/Makefile b/src/Makefile index 012ca3a4..3a99c797 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,8 +3,8 @@ OBJS = $(SRCS:.c=.o) stubicon.o CC = gcc BINDIR = $(CURDIR)/../share/ocra -CFLAGS = -Wall -O2 -DWITH_LZMA -Ilzma -s -STUB_CFLAGS = -D_CONSOLE $(CFLAGS) +CFLAGS = -Wall -O0 -ggdb +STUB_CFLAGS = -D_CONSOLE $(CFLAGS) -s STUBW_CFLAGS = -mwindows $(CFLAGS) # -D_MBCS @@ -14,7 +14,7 @@ stubicon.o: stub.rc windres -i $< -o $@ stub.exe: $(OBJS) stub.o - $(CC) $(STUB_CFLAGS) $(OBJS) stub.o -o stub + $(CC) $(STUB_CFLAGS) stub.o -o stub stubw.exe: $(OBJS) stubw.o $(CC) $(STUBW_CFLAGS) $(OBJS) stubw.o -o stubw From dacf97ad95213fa8fd9badf6dc8a31f591f499ac Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 00:01:53 +0100 Subject: [PATCH 02/43] Got it to output the NT signature ('PE') --- Rakefile | 11 +++++++++++ src/Makefile | 6 +++--- src/stub.c | 9 +++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index ddf4a9a5..d04bda9b 100644 --- a/Rakefile +++ b/Rakefile @@ -12,6 +12,17 @@ end spec.urls.each { |url| url.chomp! } +desc "task for fast iteration" +task :do_it do + sh "rake install_gem" rescue nil + sh "gem uninstall ocra" + puts "installing the bitch" + sh "gem install pkg/ocra-1.4.666.gem" + sh "rm hello.exe" rescue nil + sh "ocra hello.rb" + sh "./hello.exe" +end + task :build_stub do sh "mingw32-make -C src" cp 'src/stub.exe', 'share/ocra/stub.exe' diff --git a/src/Makefile b/src/Makefile index 3a99c797..012ca3a4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,8 +3,8 @@ OBJS = $(SRCS:.c=.o) stubicon.o CC = gcc BINDIR = $(CURDIR)/../share/ocra -CFLAGS = -Wall -O0 -ggdb -STUB_CFLAGS = -D_CONSOLE $(CFLAGS) -s +CFLAGS = -Wall -O2 -DWITH_LZMA -Ilzma -s +STUB_CFLAGS = -D_CONSOLE $(CFLAGS) STUBW_CFLAGS = -mwindows $(CFLAGS) # -D_MBCS @@ -14,7 +14,7 @@ stubicon.o: stub.rc windres -i $< -o $@ stub.exe: $(OBJS) stub.o - $(CC) $(STUB_CFLAGS) stub.o -o stub + $(CC) $(STUB_CFLAGS) $(OBJS) stub.o -o stub stubw.exe: $(OBJS) stubw.o $(CC) $(STUBW_CFLAGS) $(OBJS) stubw.o -o stubw diff --git a/src/stub.c b/src/stub.c index c162a736..d402c725 100644 --- a/src/stub.c +++ b/src/stub.c @@ -366,6 +366,13 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } +void ExamineSignature(LPVOID ptr) { + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; + PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); + printf("e_lfanew: %lu\n", dosHeader->e_lfanew); + printf("NT signature: %s\n", (char*)&ntHeader->Signature); +} + /** Process the image by checking the signature and locating the first opcode. @@ -373,6 +380,8 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm BOOL ProcessImage(LPVOID ptr, DWORD size) { LPVOID pSig = ptr + size - 4; + ExamineSignature(ptr); + if (memcmp(pSig, Signature, 4) == 0) { DEBUG("Good signature found."); From 209f994bdf258f2bb430f16949effd904ba2a33d Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 00:32:57 +0100 Subject: [PATCH 03/43] Found the place where the signature starts (just after the end of the previous EOF of the executable) But seems to be around 4 bytes of null buffer between end of file and the start of sig --- Rakefile | 7 +++++++ src/Makefile | 2 +- src/stub.c | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index d04bda9b..045b4781 100644 --- a/Rakefile +++ b/Rakefile @@ -20,7 +20,14 @@ task :do_it do sh "gem install pkg/ocra-1.4.666.gem" sh "rm hello.exe" rescue nil sh "ocra hello.rb" + sh "cp hello.exe hello-signed.exe" + puts "Now creating a signed copy in hello-signed.exe" + sh "signtool sign /f ./CARoot.pfx /p Test123 hello-signed.exe" + + puts "output of hello.exe" sh "./hello.exe" + puts "output of hello-signed.exe" + sh "./hello-signed.exe" rescue nil end task :build_stub do diff --git a/src/Makefile b/src/Makefile index 012ca3a4..6192a167 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,7 +3,7 @@ OBJS = $(SRCS:.c=.o) stubicon.o CC = gcc BINDIR = $(CURDIR)/../share/ocra -CFLAGS = -Wall -O2 -DWITH_LZMA -Ilzma -s +CFLAGS = -Wall -O2 -DWITH_LZMA -Ilzma STUB_CFLAGS = -D_CONSOLE $(CFLAGS) STUBW_CFLAGS = -mwindows $(CFLAGS) # -D_MBCS diff --git a/src/stub.c b/src/stub.c index d402c725..afdca61b 100644 --- a/src/stub.c +++ b/src/stub.c @@ -371,6 +371,8 @@ void ExamineSignature(LPVOID ptr) { PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); printf("e_lfanew: %lu\n", dosHeader->e_lfanew); printf("NT signature: %s\n", (char*)&ntHeader->Signature); + printf("size of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size); +printf("address of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress); } /** From c23b023bed72950d9e093737ebc87a90f071fa0c Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 02:22:58 +0100 Subject: [PATCH 04/43] Got isDigitallySigned and retrieveNTHeader working Still struggling with ocraSignatureLocation --- src/stub.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/src/stub.c b/src/stub.c index afdca61b..60901570 100644 --- a/src/stub.c +++ b/src/stub.c @@ -23,6 +23,8 @@ const BYTE Signature[] = { 0x41, 0xb6, 0xba, 0x4e }; #define OP_CREATE_INST_DIRECTORY 8 #define OP_MAX 9 +char isDigitallySigned(LPVOID ptr); + BOOL ProcessImage(LPVOID p, DWORD size); BOOL ProcessOpcodes(LPVOID* p); void CreateAndWaitForProcess(LPTSTR ApplicationName, LPTSTR CommandLine); @@ -366,25 +368,69 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } -void ExamineSignature(LPVOID ptr) { +LPVOID ocraSignatureLocation(LPVOID ptr); +PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr); + +PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; - PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); - printf("e_lfanew: %lu\n", dosHeader->e_lfanew); - printf("NT signature: %s\n", (char*)&ntHeader->Signature); - printf("size of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size); -printf("address of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress); + printf("got as far as finding dos header!\n"); + return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } +char isDigitallySigned(LPVOID ptr) { + PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); + printf("got as far as retrieving the NT header!\n"); + return ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size != 0; +} + +LPVOID ocraSignatureLocation(LPVOID ptr) { + PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); + DWORD offset = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress - 4; + + return ptr + offset; +} + +/* void ExamineSignature(LPVOID ptr) { */ +/* PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; */ +/* PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); */ +/* printf("e_lfanew: %lu\n", dosHeader->e_lfanew); */ +/* printf("NT signature: %s\n", (char*)&ntHeader->Signature); */ +/* printf("size of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size); */ +/* printf("address of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress); */ +/* } */ + + /** Process the image by checking the signature and locating the first opcode. */ BOOL ProcessImage(LPVOID ptr, DWORD size) { - LPVOID pSig = ptr + size - 4; - ExamineSignature(ptr); + LPVOID pSig; // = ptr + size - 4; + /* LPVOID = loc; */ + /* ExamineSignature(ptr); */ + + /* printf("about to check for sig\n"); */ + /* printf("is digitally signed? %c\n", isDigitallySigned(ptr)); */ + /* printf("checked sig!"); */ + /* if (isDigitallySigned(ptr)) { */ + /* pSig = ocraSignatureLocation(ptr) - 4; */ + /* } */ + /* else { */ + /* pSig = ptr + size - 4; */ + /* } */ + + printf("digitally signed? %d\n", isDigitallySigned(ptr)); + + if (isDigitallySigned(ptr)) { + printf("so it's signed!\n"); + pSig = ocraSignatureLocation(ptr) - 4 ; + } + else { + pSig = ptr + size - 4; + } - if (memcmp(pSig, Signature, 4) == 0) + if ((memcmp(pSig, Signature, 4) == 0)) { DEBUG("Good signature found."); DWORD OpcodeOffset = *(DWORD*)(pSig - 4); @@ -406,6 +452,7 @@ BOOL ProcessOpcodes(LPVOID* p) while (!ExitCondition) { DWORD opcode = GetInteger(p); + // printf("opcode is: %u\n", opcode); if (opcode < OP_MAX) { if (!OpcodeHandlers[opcode](p)) From 3ff1a0b32e329814582cdd8270c477a0b02b3485 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 04:43:48 +0100 Subject: [PATCH 05/43] Skip over null bytes between ocrasig and digital sig unfortunately the digital sig does not appear immediately after the ocra sig. There appears to be a random number of null bytes between them. This code skips over those null bytes until it finds the ocra sig, then returns the start of the sig --- src/Makefile | 2 +- src/stub.c | 34 +++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Makefile b/src/Makefile index 6192a167..012ca3a4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,7 +3,7 @@ OBJS = $(SRCS:.c=.o) stubicon.o CC = gcc BINDIR = $(CURDIR)/../share/ocra -CFLAGS = -Wall -O2 -DWITH_LZMA -Ilzma +CFLAGS = -Wall -O2 -DWITH_LZMA -Ilzma -s STUB_CFLAGS = -D_CONSOLE $(CFLAGS) STUBW_CFLAGS = -mwindows $(CFLAGS) # -D_MBCS diff --git a/src/stub.c b/src/stub.c index 60901570..235b7a65 100644 --- a/src/stub.c +++ b/src/stub.c @@ -373,21 +373,40 @@ PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr); PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; - printf("got as far as finding dos header!\n"); return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } char isDigitallySigned(LPVOID ptr) { PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); - printf("got as far as retrieving the NT header!\n"); return ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size != 0; } +DWORD sizeOfSignature(LPVOID ptr) { + PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); + DWORD size = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size; + + return size; +} + LPVOID ocraSignatureLocation(LPVOID ptr) { PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); - DWORD offset = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress - 4; + DWORD offset = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress; + + printf("virtual address is %d\n", offset); + printf("signature location is calculated to be: %d\n", ptr + offset); - return ptr + offset; + int ocraOffset = offset - 1;// &ptr[offset]; + + char* foo = (char *)ptr; + while(!foo[ocraOffset]) { + printf("0x%x\n", foo[ocraOffset]); + ocraOffset--; + } + // for(LPVOID ocraOffset = &ptr[offset]; !ocraOffset; ocraOffset--); + + printf("found offset is %d\n", ocraOffset); + return (LPVOID)&foo[ocraOffset - 3]; + // &ptr[offset]; } /* void ExamineSignature(LPVOID ptr) { */ @@ -419,12 +438,17 @@ BOOL ProcessImage(LPVOID ptr, DWORD size) /* else { */ /* pSig = ptr + size - 4; */ /* } */ + + printf("size of mem mapped file is %d\n", size); printf("digitally signed? %d\n", isDigitallySigned(ptr)); if (isDigitallySigned(ptr)) { printf("so it's signed!\n"); - pSig = ocraSignatureLocation(ptr) - 4 ; + pSig = ocraSignatureLocation(ptr); + DWORD sigSize = sizeOfSignature(ptr); + printf("size of signature %d\n", sizeOfSignature(ptr)); + printf("size of file - size of signature %d\n", size - sigSize); } else { pSig = ptr + size - 4; From 8079207f413f542ac31025d1804eda9e2c8ac675 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 05:05:00 +0100 Subject: [PATCH 06/43] Minor refactor to stub.c --- CARoot.pfx | Bin 0 -> 4230 bytes hello.rb | 1 + src/stub.c | 112 ++++++++++++++++++----------------------------------- 3 files changed, 38 insertions(+), 75 deletions(-) create mode 100644 CARoot.pfx create mode 100644 hello.rb diff --git a/CARoot.pfx b/CARoot.pfx new file mode 100644 index 0000000000000000000000000000000000000000..b49907e276dd872a3ba43d04d37a068c111662ab GIT binary patch literal 4230 zcmZWrcTf}VvP~caX`zP}L+>C2=~XEbX%djC^rm!z2*J<-QlukAnsiW#KqvwNA|Sm= zHAqzulwPF>H@`dY-8*mI?CiH^&z{}=@0*px(cwsdq>?x~RWOA}Br@`Z21pLf$I(fE zaCD+XT2K;48U24slo$|>(vL{{5Zwbz^*>$Ilt7Yv97V7sjv`PJ2B!R9nm8N+x<;?f zmQnUoMVf?!d<=x6FmL+Nf=Ym(o&Q8N4$jDJH*acV*KNB8F2AhWtkh!n9IFs@Fevny zNZ9frAUUmF6YGe7#*z0z$D|@I-o)Q?G|Nf+Tb-M z%AL3l4+6ue6MXxc0|YYyJ9l?nJY7ff+8w5`p#4t_-`|u?d}~Gm#}3(baa*UU*iLt% zbaFZ6?qkEI>CG+jWX|^Q{g!#33gJqcQ4Z>EdQ}s`QqcfC=_0)c6uKWez633IGPP_u zL+e{0x1z>G<`CMtRbsR>Q{h2yIo1*TiQES&sqY#~Ubn4-m0yXwT}^BF1MlenQcpbK zOY|1sua9WE(bB*0BKgF|RV-w7d=b~$&vo&U8G-b|4dL!3LOCArd^mKVD=g#7h!Q;r zBbZirc}{F|Te$0MVCvHwD>p8FYB5J|fEr%BTnedwvX-(KRxd;0RwPNwM|Zq~%9eCj z6PQO|gYO$Ny>~FN^`?5SZk}o@iUxV#Y!ofz^*yrEZANW= zR(|ERrRBpsUVw2HO4{z%L;`x5{XIjDa4v z>3sKo>@bj^Jq_Ha7g_^#%$~}V_-rr+=>$R#9vMmvsh~$>d9V8(4v}U(z~U1IIavv*o7L*~bC`Yk*E$FViG12C{8S^DTd{1; z#RDlYeY=^Z+CsHD6|ws_9+j^hP6+&LmyL0MHx6Zqz0Mbu9ZmDW0$&PF{TzLGDlQAQ zt@avY7d-QsFp^ID*59ibONud)S~7Rtl1aDg8nvHyijdxE@%#OAkn^ohi}AwEuub;U z-W5g+sW;i`9IbtU0n~v`x4KIE(CsQTCqhk~gllw-QZIfkB0GKZ4qf+wZHC?Df-&2I zE$-=&Lx45<$;Irlaq3n27yJzl6mqwg^VyJ+IU?;?9Ef!5ShIDI!GDVgen5wkxsc%q zzTO~>cM<;dHbm7;04CsNhV7P8!3%EasceAYS=H`3Ik&@30)J+?DBalSEZdtYPKCRP zZj;-YO^VyHeHi^+Ay4c7(0G%3Xjre0yfhXQ_e6}VQ~#t zfD{Ua!=WiQSCOIA+TCKYhCl|r&lrqs+VAfr%XvkPb>`g0p*cg`DJ#^N`WlI)Oxe!h z2)9C0@0~4WX2al>?o0)Jk<8Qb9=mTSl(rxBIIOE`l2ublMPEn>wRpl;z}(2nz3Wat za~q#_0;MO5vk$0ND*BlbQAn7a$o{1b!7FOYq_0m~WwWU74WnEf7|j)zp4g z@hhc>|J_!_qPF%D^YD<_!B9$i8hGA4@4{?MHSwazl|)K}TM+7QMhsTfyZ#Vu%*oPoL(MT9HF&;|Jb$R{Q%|}^bQ}Hd$#m;ee1XB&R&C}ng z`CC2iP;baw1epXwW&aX*g$+wuAyX{}cvpWZ(1tnnUT(60-}QyzqZ_^|d1yvd`7h@P z5rzVGipdM_p03Zhbz#3zJuo2v)8dI8B=!WXr+-48DQpuQ9tvs|`n;RjlqNhaX#`vebx zXdP~><+9|}J&RpsJ+Yah;MXbM({cSNJnho^HDe4s8CJ#;D&mX-fA2)bk4Is8j=rSU zzn*<}@yqxiIsHp2Pp*HQwH}oM7X{m^V#GuC5mWalhH|Hjwp0`wo>Cgc8u&6q?~!Oq zEd{)Lkf&}kyGW+jr@BWVx)Y$Q)y?V`1NBE~)wrb9UT4~X zjSI!QZMsin1skqKJIajHWJ~CASyIR02pc^XR5a(Jsr~}vQUrSR&1F?&2XNU=)6>oM z{%e}$L*Z6H)ZN`O(GndGUIEbZD8qW_bEqMQnIY{ycwS7~ky?|t0B8^wSSi&hzN}4{ zr{TMu>wHglhDML6*M?@@^scTW$!V6tzL?DM#%O?k3)2RIg|(Cv{f&3!F=n@{;P#L5 z38tmW%xKwm3B`}qF5&wH1%!O;a30mp6LiSvR>{P@R{W^J=T2(}T|bgt)?ynhHIEku zLm1+OA(3=q%E;9H_9NH=yYEWFA4~MD^nA@Ct?Ne)bA5v^ISM*TeE0=@Nl!#Ad3g+pAhmcigN74-9|OYO z(YVwhzwNODlA-1hKY3AU_no^c-$DJ;#h;hhOh>WC66X^x`0%6Evg1vcNn9%qKmhCkvH*D^k|stufC3TO z6Y)PPaiYr*83a*lM^xJr4M8+{NgP?me-@F0^KoR+L@I&=2q1=_|5kwi{{Wpo$w=Zk zOUeA_01d^F8TMtA;!AjA>3p1&*+j`>!=Wn?l`2AlQzqGGIpbuX0_VB!?o}suX)^Xm z5MY94?A@q@wU{gEHa!4&A4CR$wk;Tdr+U(3Sm}jA-lwJ1bRPsb$RFZ4_M$H3KitZ5*(nJ z&ri}w@e}-!S!6K@JpNEXlXh153E)``rxhIVC=FKMxUA zKC>xd@Dlw{0K7J+BBuIgKdAnMoT*~r{Oe9JCKq<;)6V8LgbP}ihs)~9Y-{oxlv7WD zdhVQV#8irzw@2hfWl|;CEA}55Z|1tGjl1&njw*Qi%VALGM-cWuT}2ycPTV09t>eb= zK7O`b^pbZY9*e*l&iE+hOs)F;&4;PYU5G*2QqIa2@0v9|n0on}g?=WCx%Rrg7#bMC zLn-&WO=GLZIx3gNPXE)fl$Xmnso(s4fN&=T32!HR1}sm;>T>}_Wu&fxeq9&S&Pb(j z2~-VURE%tE(~@wYu01x(3PWQA2}sXO{dkoH;PX^{tvb+vp2GVR29A3+%bB8#4qA=i z?FT|yq+GmpDaz5@6^peI&!i_)FXzquw6W-#7}t93#U>A5CIxhNH+FKj6jLe&%$zUc z{KIEj9+_jfU?eMK`|OUlwTE4`2pwL}fJ!Aj^Na1s!<15q206;rC26m}IN#%$RRwjo zt6jT_YL2Y*{FD~BFdV+i)+?oHi>z0x9-4E;q-!u;C16KaV3tktnb5pYfsak&6n)F6 zt`*FRm{T!b2K&uuK0%+J+rO-bGq|NS>9Up*s@N9PH68|J?W$6cdBr7vgZ@@>CKtPv zSlE$Gu-7!Gc`BWj)*eV-M30H}ZMb+h;SjASv|M$q0$DDDI>%I#RGF(jRvTW^M17n< zJ$4ff>;Uo_h>!=y&Zcs20^YXTzH7S^6GHOYYEg?Ov6?hDzb13wM}H7)Ud%e#z{CJU zVpM~PMnX473P%NFfoI1oj&tEF#3D_{+0G!izFXU5`gzw0EGV4aRj=r}_bdQuJFDtm zQz;&gs_#Fk#a0LJu6<>h#`l?i`9E|O^`{`ufrzdgr98hI2}NkJS9`Q{Go=Q6du8LoBMzn z_t^&;%Ls4tA6J7zr3wTcA9IW0FGB;*2$g zoZ;Bit@anDHB*<^!`&`EEzj3pQgWd$hAILv=lqY$?mn(Pa5teeZpy1{^sM__vSFX3 zWxF;L6AVY5Lu`d0YUUDr@q{Srkq?%Y3a8Q^E4INu9^U3=jNmlk1mr?Avf4vJy}Oe+ z{G=ZZOZkuvjZ#5wRUcS|N-9ZmN|J-gL?NUk>>#E#f6p{jWL%obVpmVsaE~T?w@4?5 csbtUGegfs`amqby`7)h^jDO=I?4J+$U&82}=Kufz literal 0 HcmV?d00001 diff --git a/hello.rb b/hello.rb new file mode 100644 index 00000000..5e3c993f --- /dev/null +++ b/hello.rb @@ -0,0 +1 @@ +puts "hello world!" diff --git a/src/stub.c b/src/stub.c index 235b7a65..19ba75ad 100644 --- a/src/stub.c +++ b/src/stub.c @@ -23,7 +23,10 @@ const BYTE Signature[] = { 0x41, 0xb6, 0xba, 0x4e }; #define OP_CREATE_INST_DIRECTORY 8 #define OP_MAX 9 -char isDigitallySigned(LPVOID ptr); +// manages digital signatures +LPVOID ocraSignatureLocation(LPVOID, DWORD); +PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID); +char isDigitallySigned(LPVOID); BOOL ProcessImage(LPVOID p, DWORD size); BOOL ProcessOpcodes(LPVOID* p); @@ -368,9 +371,6 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } -LPVOID ocraSignatureLocation(LPVOID ptr); -PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr); - PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); @@ -381,90 +381,52 @@ char isDigitallySigned(LPVOID ptr) { return ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size != 0; } -DWORD sizeOfSignature(LPVOID ptr) { - PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); - DWORD size = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size; - - return size; -} - -LPVOID ocraSignatureLocation(LPVOID ptr) { - PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); - DWORD offset = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress; - - printf("virtual address is %d\n", offset); - printf("signature location is calculated to be: %d\n", ptr + offset); - - int ocraOffset = offset - 1;// &ptr[offset]; - - char* foo = (char *)ptr; - while(!foo[ocraOffset]) { - printf("0x%x\n", foo[ocraOffset]); - ocraOffset--; +// Find the location of ocra's signature +// NOTE: *not* the same as the digital signature from code signing +LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { + if (!isDigitallySigned(ptr)) { + return ptr + size - 4; + } + else { + PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); + DWORD offset = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress; + int ocraOffset = offset - 1; + char* foo = (char *)ptr; + + printf("virtual address is %ld\n", offset); + + // there is unfortunately a 'buffer' of null bytes between the + // ocraSignature and the digital signature. This buffer appears to be random + // in size, so the only way we can account for it is to search backwards + // for the first non-null byte. + // NOTE: this means that the hard-codedocra signature may not end with a null byte. + while(!foo[ocraOffset]) + ocraOffset--; + + // -3 cos we're already at the first byte and we need to go back 4 bytes + return (LPVOID)&foo[ocraOffset - 3]; } - // for(LPVOID ocraOffset = &ptr[offset]; !ocraOffset; ocraOffset--); - - printf("found offset is %d\n", ocraOffset); - return (LPVOID)&foo[ocraOffset - 3]; - // &ptr[offset]; } -/* void ExamineSignature(LPVOID ptr) { */ -/* PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; */ -/* PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); */ -/* printf("e_lfanew: %lu\n", dosHeader->e_lfanew); */ -/* printf("NT signature: %s\n", (char*)&ntHeader->Signature); */ -/* printf("size of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size); */ -/* printf("address of security %ld\n", ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress); */ -/* } */ - - /** Process the image by checking the signature and locating the first opcode. */ BOOL ProcessImage(LPVOID ptr, DWORD size) { - LPVOID pSig; // = ptr + size - 4; - /* LPVOID = loc; */ - /* ExamineSignature(ptr); */ - - /* printf("about to check for sig\n"); */ - /* printf("is digitally signed? %c\n", isDigitallySigned(ptr)); */ - /* printf("checked sig!"); */ - /* if (isDigitallySigned(ptr)) { */ - /* pSig = ocraSignatureLocation(ptr) - 4; */ - /* } */ - /* else { */ - /* pSig = ptr + size - 4; */ - /* } */ - - printf("size of mem mapped file is %d\n", size); - - printf("digitally signed? %d\n", isDigitallySigned(ptr)); - - if (isDigitallySigned(ptr)) { - printf("so it's signed!\n"); - pSig = ocraSignatureLocation(ptr); - DWORD sigSize = sizeOfSignature(ptr); - printf("size of signature %d\n", sizeOfSignature(ptr)); - printf("size of file - size of signature %d\n", size - sigSize); - } - else { - pSig = ptr + size - 4; - } - + LPVOID pSig = ocraSignatureLocation(ptr, size); + if ((memcmp(pSig, Signature, 4) == 0)) { - DEBUG("Good signature found."); - DWORD OpcodeOffset = *(DWORD*)(pSig - 4); - LPVOID pSeg = ptr + OpcodeOffset; - return ProcessOpcodes(&pSeg); + DEBUG("Good signature found."); + DWORD OpcodeOffset = *(DWORD*)(pSig - 4); + LPVOID pSeg = ptr + OpcodeOffset; + return ProcessOpcodes(&pSeg); } else { - FATAL("Bad signature in executable."); - return FALSE; + FATAL("Bad signature in executable."); + return FALSE; } } @@ -476,7 +438,7 @@ BOOL ProcessOpcodes(LPVOID* p) while (!ExitCondition) { DWORD opcode = GetInteger(p); - // printf("opcode is: %u\n", opcode); + if (opcode < OP_MAX) { if (!OpcodeHandlers[opcode](p)) From be030312213aa17a5786c7208b1cfe8f62c4582e Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 05:36:51 +0100 Subject: [PATCH 07/43] remove whitespace --- src/stub.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stub.c b/src/stub.c index 19ba75ad..aab27bcd 100644 --- a/src/stub.c +++ b/src/stub.c @@ -438,7 +438,6 @@ BOOL ProcessOpcodes(LPVOID* p) while (!ExitCondition) { DWORD opcode = GetInteger(p); - if (opcode < OP_MAX) { if (!OpcodeHandlers[opcode](p)) From 2ebf89b4ddf415e590f673b36699e80f8dfc7fa5 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 05:52:09 +0100 Subject: [PATCH 08/43] remove unnecessary brackets --- src/stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stub.c b/src/stub.c index aab27bcd..012f20c3 100644 --- a/src/stub.c +++ b/src/stub.c @@ -416,7 +416,7 @@ BOOL ProcessImage(LPVOID ptr, DWORD size) { LPVOID pSig = ocraSignatureLocation(ptr, size); - if ((memcmp(pSig, Signature, 4) == 0)) + if (memcmp(pSig, Signature, 4) == 0) { DEBUG("Good signature found."); DWORD OpcodeOffset = *(DWORD*)(pSig - 4); From 34794953c14004cfb8e16dbf9ccb461b6e7f3c83 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 06:14:10 +0100 Subject: [PATCH 09/43] add better var names and add a macro * foo -> searchPtr * introduce SECURITY_ENTRY macro --- src/stub.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/stub.c b/src/stub.c index 012f20c3..8d439221 100644 --- a/src/stub.c +++ b/src/stub.c @@ -371,6 +371,8 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } +#define SECURITY_ENTRY(header) (header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) + PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); @@ -378,7 +380,7 @@ PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { char isDigitallySigned(LPVOID ptr) { PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); - return ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size != 0; + return SECURITY_ENTRY(ntHeader).Size != 0; } // Find the location of ocra's signature @@ -389,22 +391,19 @@ LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { } else { PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); - DWORD offset = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress; - int ocraOffset = offset - 1; - char* foo = (char *)ptr; - - printf("virtual address is %ld\n", offset); + DWORD offset = SECURITY_ENTRY(ntHeader).VirtualAddress - 1; + char* searchPtr = (char *)ptr; // there is unfortunately a 'buffer' of null bytes between the // ocraSignature and the digital signature. This buffer appears to be random // in size, so the only way we can account for it is to search backwards // for the first non-null byte. // NOTE: this means that the hard-codedocra signature may not end with a null byte. - while(!foo[ocraOffset]) - ocraOffset--; + while(!searchPtr[offset]) + offset--; // -3 cos we're already at the first byte and we need to go back 4 bytes - return (LPVOID)&foo[ocraOffset - 3]; + return (LPVOID)&searchPtr[offset - 3]; } } From f47c6fc69b47446fc22225d5505a3ada0c299688 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 06:23:33 +0100 Subject: [PATCH 10/43] minor sylistic changes * C style comments (/* */) * Moved some code around --- src/stub.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/stub.c b/src/stub.c index 8d439221..0a81f84c 100644 --- a/src/stub.c +++ b/src/stub.c @@ -23,10 +23,13 @@ const BYTE Signature[] = { 0x41, 0xb6, 0xba, 0x4e }; #define OP_CREATE_INST_DIRECTORY 8 #define OP_MAX 9 -// manages digital signatures +/* Manages digital signatures */ +#define SECURITY_ENTRY(header) (header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) + LPVOID ocraSignatureLocation(LPVOID, DWORD); PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID); char isDigitallySigned(LPVOID); +/******************************/ BOOL ProcessImage(LPVOID p, DWORD size); BOOL ProcessOpcodes(LPVOID* p); @@ -371,20 +374,20 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } -#define SECURITY_ENTRY(header) (header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) - PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } +/* Check whether there's an embedded digital signature */ char isDigitallySigned(LPVOID ptr) { PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); return SECURITY_ENTRY(ntHeader).Size != 0; } -// Find the location of ocra's signature -// NOTE: *not* the same as the digital signature from code signing +/* Find the location of ocra's signature + NOTE: *not* the same as the digital signature from code signing +*/ LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { if (!isDigitallySigned(ptr)) { return ptr + size - 4; @@ -394,15 +397,16 @@ LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { DWORD offset = SECURITY_ENTRY(ntHeader).VirtualAddress - 1; char* searchPtr = (char *)ptr; - // there is unfortunately a 'buffer' of null bytes between the - // ocraSignature and the digital signature. This buffer appears to be random - // in size, so the only way we can account for it is to search backwards - // for the first non-null byte. - // NOTE: this means that the hard-codedocra signature may not end with a null byte. + /* There is unfortunately a 'buffer' of null bytes between the + ocraSignature and the digital signature. This buffer appears to be random + in size, so the only way we can account for it is to search backwards + for the first non-null byte. + NOTE: this means that the hard-coded Ocra signature may not end with a null byte. + */ while(!searchPtr[offset]) offset--; - // -3 cos we're already at the first byte and we need to go back 4 bytes + /* -3 cos we're already at the first byte and we need to go back 4 bytes */ return (LPVOID)&searchPtr[offset - 3]; } } From 08eb35bc9af777bf9a2f86709acd12d3923d23f7 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 06:26:23 +0100 Subject: [PATCH 11/43] language change may not -> cannot --- src/stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stub.c b/src/stub.c index 0a81f84c..e328f0f5 100644 --- a/src/stub.c +++ b/src/stub.c @@ -401,7 +401,7 @@ LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { ocraSignature and the digital signature. This buffer appears to be random in size, so the only way we can account for it is to search backwards for the first non-null byte. - NOTE: this means that the hard-coded Ocra signature may not end with a null byte. + NOTE: this means that the hard-coded Ocra signature cannot end with a null byte. */ while(!searchPtr[offset]) offset--; From cffb0147e6c273a8bbb4c964b212fc96bbef0816 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 07:21:39 +0100 Subject: [PATCH 12/43] Make functions static --- src/stub.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/stub.c b/src/stub.c index e328f0f5..2f7ad8b3 100644 --- a/src/stub.c +++ b/src/stub.c @@ -26,9 +26,9 @@ const BYTE Signature[] = { 0x41, 0xb6, 0xba, 0x4e }; /* Manages digital signatures */ #define SECURITY_ENTRY(header) (header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) -LPVOID ocraSignatureLocation(LPVOID, DWORD); -PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID); -char isDigitallySigned(LPVOID); +static LPVOID ocraSignatureLocation(LPVOID, DWORD); +static PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID); +static char isDigitallySigned(LPVOID); /******************************/ BOOL ProcessImage(LPVOID p, DWORD size); @@ -374,13 +374,13 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } -PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { +static PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } /* Check whether there's an embedded digital signature */ -char isDigitallySigned(LPVOID ptr) { +static char isDigitallySigned(LPVOID ptr) { PIMAGE_NT_HEADERS ntHeader = retrieveNTHeader(ptr); return SECURITY_ENTRY(ntHeader).Size != 0; } @@ -388,7 +388,7 @@ char isDigitallySigned(LPVOID ptr) { /* Find the location of ocra's signature NOTE: *not* the same as the digital signature from code signing */ -LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { +static LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { if (!isDigitallySigned(ptr)) { return ptr + size - 4; } From a9e4caed467ff7f23dcadd560a4e7ecb0ae49f1b Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 8 Dec 2017 10:37:27 +0100 Subject: [PATCH 13/43] reorg rakefile --- Rakefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 045b4781..ff866fe2 100644 --- a/Rakefile +++ b/Rakefile @@ -14,8 +14,8 @@ spec.urls.each { |url| url.chomp! } desc "task for fast iteration" task :do_it do - sh "rake install_gem" rescue nil sh "gem uninstall ocra" + sh "rake install_gem" rescue nil puts "installing the bitch" sh "gem install pkg/ocra-1.4.666.gem" sh "rm hello.exe" rescue nil @@ -23,7 +23,6 @@ task :do_it do sh "cp hello.exe hello-signed.exe" puts "Now creating a signed copy in hello-signed.exe" sh "signtool sign /f ./CARoot.pfx /p Test123 hello-signed.exe" - puts "output of hello.exe" sh "./hello.exe" puts "output of hello-signed.exe" From 8677190f28b8f0777b4c9183b5a7574292493621 Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 09:44:11 +0100 Subject: [PATCH 14/43] update comments based on PR feedback from @jakedouglas --- src/stub.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/stub.c b/src/stub.c index 2f7ad8b3..c8471997 100644 --- a/src/stub.c +++ b/src/stub.c @@ -23,8 +23,10 @@ const BYTE Signature[] = { 0x41, 0xb6, 0xba, 0x4e }; #define OP_CREATE_INST_DIRECTORY 8 #define OP_MAX 9 -/* Manages digital signatures */ -#define SECURITY_ENTRY(header) (header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) +/** Manages digital signatures **/ + +/* see https://en.wikipedia.org/wiki/Portable_Executable for explanation of these header fields */ +#define SECURITY_ENTRY(header) ((header)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) static LPVOID ocraSignatureLocation(LPVOID, DWORD); static PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID); @@ -374,8 +376,12 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm return 0; } +/* The NTHeader is another name for the PE header. It is the 'modern' executable header + as opposed to the DOS_HEADER which exists for legacy reasons */ static PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; + + /* e_lfanew points to the NTHeader (aka PE Header) */ return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } From 227c3f085b6564489ec467dd98b6755b500c75ba Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 17:17:46 +0100 Subject: [PATCH 15/43] rewrite comment based on feedback from @jakedouglas --- src/stub.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stub.c b/src/stub.c index c8471997..9ae96d0f 100644 --- a/src/stub.c +++ b/src/stub.c @@ -381,7 +381,8 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm static PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; - /* e_lfanew points to the NTHeader (aka PE Header) */ + /* e_lfanew is an RVA (relative virtual offset) to the NTHeader (aka PE Header) + to get a usable pointer we add the RVA to the base address */ return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } From 20aa6b5507e8a05acfd018b21b346a0c9052f49a Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 17:19:59 +0100 Subject: [PATCH 16/43] Add code signing doc for easy review, won't necessarily keep in this repo though --- ocra_code_signing_and_windows.md | 149 +++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 ocra_code_signing_and_windows.md diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md new file mode 100644 index 00000000..d636b3ba --- /dev/null +++ b/ocra_code_signing_and_windows.md @@ -0,0 +1,149 @@ +Ocra & Code signing in Windows +=============== + +In order to distribute our app on Windows we use the [Ocra](https://github.com/larsch/ocra) project to bundle our Ruby source files together with the Ruby interpreter and libraries to form a single executable. + +Further, and as part of our build process we want to digitally sign our files. [Code signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) achieves two things: (1) It establishes our identity as a developer and (2) It ensures integrity of our code (ensuring that it hasn't +been tampered with). Also, since it establishes us as a 'trusted developer' it reduces interference from Anti-Virus software and so on. + +Unfortunately, due to their fragile nature, Ocra executables are broken by the code signing process and this has prevented us from signing our +app until now. + +In this document I'll describe how Ocra works, how code signing in Windows works and finally an approach to get code signing working with Ocra. + +(TL;DR given at the end of the document). + +## How Ocra Works + +At a high level Ocra works by writing custom data after the end of the executable image -- and then reading this custom data into memory at runtime. The custom data contains "opcodes" and embedded Ruby source files. The opcodes contain instructions like "create temp directory", "create file" (i.e extract embedded Ruby file to a temp folder on disk), "create process" and so on. It is this runtime execution of opcodes that allow the magic of Ocra to happen. + +### Building the executable + +It's a [known trick](https://edn.embarcadero.com/article/27979) that data written past the end of a Windows executable will be ignored by the Kernel, and Ocra takes advantage of this to store Ruby files as well as instructions for how to extract and run those Ruby files. + +In more detail, Ocra does the following when building an executable for a Ruby app: + +* First, the [stub.c](https://github.com/larsch/ocra/blob/master/src/stub.c) file is compiled into a `stub.exe`. This program only does two things -- it maps itself into memory and then +executes the opcodes stored in its 'custom data' section. +* Ocra then creates the output executable (e.g `hello_world.exe`) and [writes `stub.exe`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1021-L1032) to the start of the file. +* The ["opcodes"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1009-L1016) are appended to the end of the output executable. The Ruby source files (and Ruby interpreter binary itself) are embedded as long string parameters associated with the opcodes. +* For example, the Ruby interpreter itself (`ruby.exe`) is [embedded as one long string](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L901) as the [first parameter](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1161) to the `OP_CREATEFILE` opcode. +* After writing the opcodes, and [a final `OP_END`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1076) instruction that indicates the end of the opcodes, Ocra +writes an [`opcodes_offset`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1077) value. This value is a pointer to the start of the opcodes that tells the +program where to start processing. +* Finally, after the offset are the [last 4 bytes of the file](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1078). These bytes form Ocra's own "signature" and they are a [hard-coded value](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L13). This signature is **not** the same as the "digital signature" inserted by code signing and likely exists to check the executable has not been corrupted. + +After Ocra has finished building the output executable, the file will look like the following: + +``` +| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | +``` + +To reiterate, only the `stub.exe` content is used by the Kernel, everything else is ignored. The opcodes and so on are only +accessed by the program itself once it is mapped into memory. + +### Running the executable + +Once we have an output executable with the structure shown in the previous diagram, Ocra can execute it. When the executable +is run the process is as follows: + +* It opens its associated file on disk for reading and [maps itself into memory](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L309) +* It navigates to the end of the mapping and examines the [final 4 bytes](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L375-L376) -- these bytes are the "Ocra signature" +* It verifies that these bytes are equal to the [hard-coded signature](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L13) (if they are not then it will exit with a ["Bad Executable"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L385) error message) +* Once the signature is validated, it reads the [4 bytes prior to the signature](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L379) -- these bytes are the `opcodes_offset` +* It [start processing](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L381) the opcodes from the `opcodes_offset` until the [`OP_END` instruction is reached](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L678). It processes the opcodes by looking up the [associated function pointer](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L72-L87) for that opcode and executing it. + +These opcodes include instructions such as ["create a temporary file"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L469-L506) (i.e extract an embedded Ruby source file to disk) and ["create and run a process"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L604-L623) (i.e run the Ruby source file with the embedded Ruby interpreter binary). + +As is clear from above, the Ocra binary is quite a fragile thing. If the last 4 bytes are not the hard-coded signature it expects, then it will error out. + +## How Code Signing Works + +A signed file is a standard Windows executable with signature data appended +to it. A standard Windows executable file is known as a "PE" file ([Portable Execution format](https://en.wikipedia.org/wiki/Portable_Executable)). + +The structure of a PE file is as follows: + +![PE Format](https://www.dropbox.com/s/9kmm3h3axcoiijp/Screenshot%202017-12-12%2009.59.44.png?raw=1) + +From the diagram we see the format starts with an MSDOS header, this header is represented by the [IMAGE_DOS_HEADER struct](https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html) - which has a member called `e_lfanew` which stands for "long file address for the New Executable" and it points to the start of the PE header proper. + +Following this pointer we get to the the PE header (also known as an `NT header` or `COFF header`) and it is a structure of type [IMAGE_NT_HEADERS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx). It is followed by another header which contains information on executables (referred to as an [optional header](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx), but it's always present for executables). This header defines [a number of data directories](http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm). One of these directory entries is `IMAGE_DIRECTORY_ENTRY_SECURITY`, which is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the signature. Specifically it stores the location of the signature in the file (in the `VirtualAddress` field) and also its size (in the `Size` field). + +The digital signature itself is just a hash of the file that is signed with the developer's private key - and it is appended to the end of the executable. + +Because the signature appears after the EOF of the proper executable it is not treated as "executable code" (recall the [custom data](https://edn.embarcadero.com/article/27979) trick). + +To summarize, after code signing, an executable will change in the following way: + +* The `IMAGE_DIRECTORY_ENTRY_SECURITY` element in the PE header will have its `Size` field changed from zero (as is the case of an unsigned PE) to the size of the digital signature. The `VirtualAddress` field will be updated to point to the location of the digital signature. +* The digital signature (which is a hash of the original executable encrypted with the developer's private key) will be appended to the executable. + +## Why Code Signing Breaks Ocra Executables + +Ocra expects the last 4 bytes of the executable to be its own hard-coded signature. However, since the Code Signing process appends the digital signature to the end of the executable this is no longer true. As a result attempting to run a Code Signed Ocra +executable will result in the ["Bad Signature in Executable"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L385) error message. + +## Suggested Fix + +The proposed fix is conceptually quite simple: we teach Ocra to know when it's been signed and to look in a different place for its expected data (its own signature and its `opcodes_offset`, etc). + +This means Ocra now has two code paths: + +### Unsigned codepath + +(`IMAGE_DIRECTORY_ENTRY_SECURITY.Size == 0`) + +The executable layout looks exactly as Ocra expects, nothing changes: + +`| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature |` + +### Signed codepath + +(`IMAGE_DIRECTORY_ENTRY_SECURITY.Size != 0`) + +The layout has changed slightly, we have a digital signature following the ocra signature: + +`| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | dig sig |` + +We must look at the `IMAGE_DIRECTORY_ENTRY_SECURITY.VirtualAddress` header to locate the starting byte of the digital signature. This starting byte will be immediately after the Ocra signature so we tell +Ocra to go there for the Ocra signature instead of the end of the file, as it did previously. From there +Ocra is able to find the Ocra signature and also the `opcodes_offset` field (which appears immediately before) and start processing opcodes. + +### Issues + +Unfortunately, the Code Signing process does not append the Digital Signature immediately after the Ocra Signature. There are usually a few NUL bytes in between the two. These NUL bytes are likely for alignment reasons that are not documented. Nonetheless, doing a small search back from the `VirtualAddress` (start of Digital Signature) until it finds the first non-NUL bytes appears to be an effective workaround and I have not noticed any problems with this approach on multiple test files. We do need to ensure the last byte of the Ocra signature is not a NUL byte though, but since the Ocra signature is hard-coded and completely under our control this is a non-issue. + +## Conclusion + +I've tested the above approach on a number of files (including our code base) and it has worked successfully. + +I recommend we open a PR upstream and get the changes merged in as other developer's are likely to also want +Ocra Code Signing support. + +## TL;DR + +Ocra expects the executable to look like this: + +``` +| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | +``` + +But, after Code Signing it looks like this instead: + +``` +| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | dig sig | +``` + +This breaks Ocra and prevents Ocra executables working after they've been signed. + +The proposed fix is to update [stub.c](https://github.com/larsch/ocra/blob/master/src/stub.c) to use the executable headers to locate where the Digital Signature starts and tell Ocra to look for its data backwards from there instead of from the end of the file. + +## Resources + +* [Peering Inside the PE](https://msdn.microsoft.com/en-us/library/ms809762.aspx) +* [Authenticode](https://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt) +* [Winnt.h](http://www.rensselaer.org/dept/cis/software/g77-mingw32/include/winnt.h) +* [Pe Format (MSDN)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx) +* [Portable Execution Format Wikipedia Page](https://en.wikipedia.org/wiki/Portable_Executable) +* [Introduction To Code Signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) From d435c3e57e3a5647ae58b81614ea481009ae984d Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 17:57:44 +0100 Subject: [PATCH 17/43] tweaks to doc --- ocra_code_signing_and_windows.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index d636b3ba..97613dbf 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -62,13 +62,16 @@ As is clear from above, the Ocra binary is quite a fragile thing. If the last 4 A signed file is a standard Windows executable with signature data appended to it. A standard Windows executable file is known as a "PE" file ([Portable Execution format](https://en.wikipedia.org/wiki/Portable_Executable)). -The structure of a PE file is as follows: +The structure of a PE file is as follows (image taken from https://en.wikipedia.org/wiki/Portable_Executable): -![PE Format](https://www.dropbox.com/s/9kmm3h3axcoiijp/Screenshot%202017-12-12%2009.59.44.png?raw=1) +![PE Format](https://www.dropbox.com/s/cv4bbcdco57mcm5/Screenshot%202017-12-12%2017.56.44.png?raw=1) -From the diagram we see the format starts with an MSDOS header, this header is represented by the [IMAGE_DOS_HEADER struct](https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html) - which has a member called `e_lfanew` which stands for "long file address for the New Executable" and it points to the start of the PE header proper. +From the diagram we see the format starts with a legacy MSDOS header. This header is represented by the [IMAGE_DOS_HEADER struct](https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html) - which has a member called `e_lfanew` which stands for "long file address for the New Executable" and it contains the offset (known as an RVA - Relative Virtual Address) to the start of the PE header proper. -Following this pointer we get to the the PE header (also known as an `NT header` or `COFF header`) and it is a structure of type [IMAGE_NT_HEADERS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx). It is followed by another header which contains information on executables (referred to as an [optional header](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx), but it's always present for executables). This header defines [a number of data directories](http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm). One of these directory entries is `IMAGE_DIRECTORY_ENTRY_SECURITY`, which is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the signature. Specifically it stores the location of the signature in the file (in the `VirtualAddress` field) and also its size (in the `Size` field). +From this offset we get to the the PE header (also known as an `NT header` or `COFF header`) and it is a structure of type [IMAGE_NT_HEADERS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx). It is followed by another header which contains information on executables (referred to as an [optional header](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx), but it's always present for executables). This header defines [a number of data directories](http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm). + +One of these directory entries is `IMAGE_DIRECTORY_ENTRY_SECURITY`, which is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the +signature. Specifically it stores the location of the signature in the file (in the `VirtualAddress` field) and also its size (in the `Size` field). The digital signature itself is just a hash of the file that is signed with the developer's private key - and it is appended to the end of the executable. @@ -76,8 +79,9 @@ Because the signature appears after the EOF of the proper executable it is not t To summarize, after code signing, an executable will change in the following way: -* The `IMAGE_DIRECTORY_ENTRY_SECURITY` element in the PE header will have its `Size` field changed from zero (as is the case of an unsigned PE) to the size of the digital signature. The `VirtualAddress` field will be updated to point to the location of the digital signature. -* The digital signature (which is a hash of the original executable encrypted with the developer's private key) will be appended to the executable. +* The `IMAGE_DIRECTORY_ENTRY_SECURITY` element in the PE header will have its `Size` field changed from zero (as is the case of an unsigned PE) to the size of the digital signature. +* The `VirtualAddress` field will be updated to point to the location of the digital signature. +* The digital signature will be appended to the executable. ## Why Code Signing Breaks Ocra Executables @@ -142,8 +146,8 @@ The proposed fix is to update [stub.c](https://github.com/larsch/ocra/blob/maste ## Resources * [Peering Inside the PE](https://msdn.microsoft.com/en-us/library/ms809762.aspx) -* [Authenticode](https://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt) +* [Authenticode Reverse-engineered](https://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt) * [Winnt.h](http://www.rensselaer.org/dept/cis/software/g77-mingw32/include/winnt.h) -* [Pe Format (MSDN)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx) +* [PE Format (MSDN)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx) * [Portable Execution Format Wikipedia Page](https://en.wikipedia.org/wiki/Portable_Executable) * [Introduction To Code Signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) From 11c7aa842f0ccd0ae25fe8ffae4ce6db5faaa8a9 Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 18:02:38 +0100 Subject: [PATCH 18/43] Add higher res image --- ocra_code_signing_and_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index 97613dbf..edfe5629 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -64,7 +64,7 @@ to it. A standard Windows executable file is known as a "PE" file ([Portable Exe The structure of a PE file is as follows (image taken from https://en.wikipedia.org/wiki/Portable_Executable): -![PE Format](https://www.dropbox.com/s/cv4bbcdco57mcm5/Screenshot%202017-12-12%2017.56.44.png?raw=1) +![PE Format](https://www.dropbox.com/s/zds11h7e282vlv6/Screenshot%202017-12-12%2018.02.16.png?raw=1) From the diagram we see the format starts with a legacy MSDOS header. This header is represented by the [IMAGE_DOS_HEADER struct](https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html) - which has a member called `e_lfanew` which stands for "long file address for the New Executable" and it contains the offset (known as an RVA - Relative Virtual Address) to the start of the PE header proper. From 6fcf63a21f426f4fae76f74a9e2ec70a522b4664 Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 18:16:02 +0100 Subject: [PATCH 19/43] RVA means relative virtual address not relative virtual offset --- src/stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stub.c b/src/stub.c index 9ae96d0f..54995f39 100644 --- a/src/stub.c +++ b/src/stub.c @@ -381,7 +381,7 @@ int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm static PIMAGE_NT_HEADERS retrieveNTHeader(LPVOID ptr) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ptr; - /* e_lfanew is an RVA (relative virtual offset) to the NTHeader (aka PE Header) + /* e_lfanew is an RVA (relative virtual address, i.e offset) to the NTHeader to get a usable pointer we add the RVA to the base address */ return (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); } From 1c60817a80800640d54d5d898ab4c661548b7cbc Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 18:22:31 +0100 Subject: [PATCH 20/43] minor tweaks to language --- ocra_code_signing_and_windows.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index edfe5629..5f7f1c9b 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -27,11 +27,11 @@ In more detail, Ocra does the following when building an executable for a Ruby a executes the opcodes stored in its 'custom data' section. * Ocra then creates the output executable (e.g `hello_world.exe`) and [writes `stub.exe`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1021-L1032) to the start of the file. * The ["opcodes"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1009-L1016) are appended to the end of the output executable. The Ruby source files (and Ruby interpreter binary itself) are embedded as long string parameters associated with the opcodes. -* For example, the Ruby interpreter itself (`ruby.exe`) is [embedded as one long string](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L901) as the [first parameter](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1161) to the `OP_CREATEFILE` opcode. +* For example, the Ruby interpreter (`ruby.exe`) is [embedded as one long string](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L901) as the [first parameter](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1161) to the `OP_CREATEFILE` opcode. * After writing the opcodes, and [a final `OP_END`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1076) instruction that indicates the end of the opcodes, Ocra -writes an [`opcodes_offset`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1077) value. This value is a pointer to the start of the opcodes that tells the +writes an [`opcodes_offset`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1077) value. This value is a relative offset to the start of the opcodes that tells the program where to start processing. -* Finally, after the offset are the [last 4 bytes of the file](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1078). These bytes form Ocra's own "signature" and they are a [hard-coded value](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L13). This signature is **not** the same as the "digital signature" inserted by code signing and likely exists to check the executable has not been corrupted. +* Finally, after the offset, are the [last 4 bytes of the file](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1078). These bytes form Ocra's own "signature" and they are a [hard-coded value](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L13). This signature is **not** the same as the "digital signature" inserted by code signing and likely exists to check the executable has not been corrupted. After Ocra has finished building the output executable, the file will look like the following: From a8f8ba53eb68f366cc5ea865f81261fd2850b48a Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 18:57:10 +0100 Subject: [PATCH 21/43] language change --- ocra_code_signing_and_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index 5f7f1c9b..433bcdee 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -70,7 +70,7 @@ From the diagram we see the format starts with a legacy MSDOS header. This heade From this offset we get to the the PE header (also known as an `NT header` or `COFF header`) and it is a structure of type [IMAGE_NT_HEADERS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx). It is followed by another header which contains information on executables (referred to as an [optional header](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx), but it's always present for executables). This header defines [a number of data directories](http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm). -One of these directory entries is `IMAGE_DIRECTORY_ENTRY_SECURITY`, which is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the +One of these directory entries, `IMAGE_DIRECTORY_ENTRY_SECURITY`, is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the signature. Specifically it stores the location of the signature in the file (in the `VirtualAddress` field) and also its size (in the `Size` field). The digital signature itself is just a hash of the file that is signed with the developer's private key - and it is appended to the end of the executable. From 0368c889796e3564bee798f25bc33b6573adb9a2 Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 19:01:41 +0100 Subject: [PATCH 22/43] language tweak --- ocra_code_signing_and_windows.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index 433bcdee..2f3eccc0 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -116,7 +116,12 @@ Ocra is able to find the Ocra signature and also the `opcodes_offset` field (whi ### Issues -Unfortunately, the Code Signing process does not append the Digital Signature immediately after the Ocra Signature. There are usually a few NUL bytes in between the two. These NUL bytes are likely for alignment reasons that are not documented. Nonetheless, doing a small search back from the `VirtualAddress` (start of Digital Signature) until it finds the first non-NUL bytes appears to be an effective workaround and I have not noticed any problems with this approach on multiple test files. We do need to ensure the last byte of the Ocra signature is not a NUL byte though, but since the Ocra signature is hard-coded and completely under our control this is a non-issue. +Unfortunately, the Code Signing process does not append the Digital Signature immediately after the Ocra Signature. There are usually a few NUL bytes in between the two. + +These NUL bytes are likely for alignment reasons that are not documented. Nonetheless, doing a small search back from the `VirtualAddress` (start of Digital Signature) until we find the +first non-NUL bytes appears to be an effective workaround and I have not noticed any problems with this approach on multiple test files. + +Finally, we need to ensure the last byte of the Ocra signature is not a NUL byte. However, since the Ocra signature is hard-coded and completely under our control this is a non-issue. ## Conclusion From a201ed71825be0bf1906c9ab064842702d3dceed Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 19:26:41 +0100 Subject: [PATCH 23/43] include dos header in diagram --- ocra_code_signing_and_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index 2f3eccc0..ac6f9fb7 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -64,7 +64,7 @@ to it. A standard Windows executable file is known as a "PE" file ([Portable Exe The structure of a PE file is as follows (image taken from https://en.wikipedia.org/wiki/Portable_Executable): -![PE Format](https://www.dropbox.com/s/zds11h7e282vlv6/Screenshot%202017-12-12%2018.02.16.png?raw=1) +![PE Format](https://www.dropbox.com/s/mx1pz6em1slzdyt/Screenshot%202017-12-12%2019.26.03.png?raw=1) From the diagram we see the format starts with a legacy MSDOS header. This header is represented by the [IMAGE_DOS_HEADER struct](https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html) - which has a member called `e_lfanew` which stands for "long file address for the New Executable" and it contains the offset (known as an RVA - Relative Virtual Address) to the start of the PE header proper. From 5fbc59673c31a2ee31378fdc4fa4b2a2ab488164 Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 23:19:00 +0000 Subject: [PATCH 24/43] Simplify language --- ocra_code_signing_and_windows.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index ac6f9fb7..7d525886 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -3,11 +3,10 @@ Ocra & Code signing in Windows In order to distribute our app on Windows we use the [Ocra](https://github.com/larsch/ocra) project to bundle our Ruby source files together with the Ruby interpreter and libraries to form a single executable. -Further, and as part of our build process we want to digitally sign our files. [Code signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) achieves two things: (1) It establishes our identity as a developer and (2) It ensures integrity of our code (ensuring that it hasn't +We also want to digitally sign our files. [Code signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) achieves two things: (1) It establishes our identity as a developer and (2) It ensures integrity of our code (ensuring that it hasn't been tampered with). Also, since it establishes us as a 'trusted developer' it reduces interference from Anti-Virus software and so on. -Unfortunately, due to their fragile nature, Ocra executables are broken by the code signing process and this has prevented us from signing our -app until now. +Unfortunately, Ocra executables are broken by the code signing process and this has prevented us from signing our app until now. In this document I'll describe how Ocra works, how code signing in Windows works and finally an approach to get code signing working with Ocra. From 7b9996e928ebe21493975ccddac87898ae6b21dc Mon Sep 17 00:00:00 2001 From: John Mair Date: Tue, 12 Dec 2017 23:41:45 +0000 Subject: [PATCH 25/43] signed -> encrypted --- ocra_code_signing_and_windows.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index 7d525886..dc9c2692 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -43,7 +43,7 @@ accessed by the program itself once it is mapped into memory. ### Running the executable -Once we have an output executable with the structure shown in the previous diagram, Ocra can execute it. When the executable +Once we have an output executable with the structure shown in the prevqious diagram, Ocra can execute it. When the executable is run the process is as follows: * It opens its associated file on disk for reading and [maps itself into memory](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L309) @@ -72,7 +72,7 @@ From this offset we get to the the PE header (also known as an `NT header` or `C One of these directory entries, `IMAGE_DIRECTORY_ENTRY_SECURITY`, is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the signature. Specifically it stores the location of the signature in the file (in the `VirtualAddress` field) and also its size (in the `Size` field). -The digital signature itself is just a hash of the file that is signed with the developer's private key - and it is appended to the end of the executable. +The digital signature itself is just a hash of the file that is encrypted with the developer's private key - and it is appended to the end of the executable. Because the signature appears after the EOF of the proper executable it is not treated as "executable code" (recall the [custom data](https://edn.embarcadero.com/article/27979) trick). From 288b98a35e841b016bd4706b93fa8d5a6dfac174 Mon Sep 17 00:00:00 2001 From: John Mair Date: Wed, 13 Dec 2017 19:41:59 +0100 Subject: [PATCH 26/43] fix typo --- ocra_code_signing_and_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index dc9c2692..800f4e64 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -43,7 +43,7 @@ accessed by the program itself once it is mapped into memory. ### Running the executable -Once we have an output executable with the structure shown in the prevqious diagram, Ocra can execute it. When the executable +Once we have an output executable with the structure shown in the previous diagram, Ocra can execute it. When the executable is run the process is as follows: * It opens its associated file on disk for reading and [maps itself into memory](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L309) From 0f04ea510cee59c9dda2d65ad3be52b950d45379 Mon Sep 17 00:00:00 2001 From: John Mair Date: Wed, 13 Dec 2017 22:49:52 +0100 Subject: [PATCH 27/43] remove repetition --- ocra_code_signing_and_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md index 800f4e64..2c2234eb 100644 --- a/ocra_code_signing_and_windows.md +++ b/ocra_code_signing_and_windows.md @@ -14,7 +14,7 @@ In this document I'll describe how Ocra works, how code signing in Windows works ## How Ocra Works -At a high level Ocra works by writing custom data after the end of the executable image -- and then reading this custom data into memory at runtime. The custom data contains "opcodes" and embedded Ruby source files. The opcodes contain instructions like "create temp directory", "create file" (i.e extract embedded Ruby file to a temp folder on disk), "create process" and so on. It is this runtime execution of opcodes that allow the magic of Ocra to happen. +At a high level Ocra works by writing custom data after the end of the executable image -- and then reading this into memory at runtime. The custom data contains "opcodes" and embedded Ruby source files. The opcodes contain instructions like "create temp directory", "create file" (i.e extract embedded Ruby file to a temp folder on disk), "create process" and so on. It is this runtime execution of opcodes that allow the magic of Ocra to happen. ### Building the executable From 6063b11a062d6fcf56c34baceedb1514553a158e Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 02:37:18 +0100 Subject: [PATCH 28/43] Commit FakeCodeSigner --- test/fake_code_signer.rb | 121 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 test/fake_code_signer.rb diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb new file mode 100644 index 00000000..b265bd0d --- /dev/null +++ b/test/fake_code_signer.rb @@ -0,0 +1,121 @@ +class FakeCodeSigner + FAKE_SIG = "fake signature" + + def initialize(input:, output:, padding: 4) + @input = input + @output = output + @padding = padding + @image = File.binread(input) + end + + def sign + if pe_header.security_size !=0 + puts "Binary already signed, nothing to do!" + return + elsif @input == @output + puts "input and output files must be different!" + return + end + + @image[pe_header.security_offset, 4] = raw_bytes(@image.size + @padding) + + @image[pe_header.security_offset + 4, 4] = raw_bytes(FAKE_SIG.size) + @image << padding_string << FAKE_SIG + + File.binwrite(@output, @image) + end + + private + + def padding_string + "\x0" * @padding + end + + def raw_bytes(int) + [int].pack("L") + end + + def pe_header + @pe_header ||= PEHeader.new(@image) + end +end + +class PEHeader + # size of a DWORD is 2 words (4 bytes) + DWORD_SIZE = 4 + + # struct IMAGE_DOS_HEADER + # https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html + DOS_HEADER_SIZE = 64 + + # first field from IMAGE_NT_HEADERS + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx + PE_SIGNATURE_SIZE = 4 + + # struct IMAGE_FILE_HEADER + # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx + IMAGE_FILE_HEADER_SIZE = 20 + + # struct IMAGE_OPTIONAL_HEADER + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx + IMAGE_OPTIONAL_HEADER_SIZE = 224 + + # http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm + NUMBER_OF_DATA_DIRECTORY_ENTRIES = 16 + + # struct IMAGE_DATA_DIRECTORY + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx + DATA_DIRECTORY_ENTRY_SIZE = 4 + + def initialize(image) + @image = image + end + + # security is the 4th element in the data directory array + def security_offset + image_data_directory_offset + data_directory_entry_size * 4 + end + + # location of the digital signature + def security_address + deref(security_offset) + end + + # size of the digital signature + def security_size + deref(security_offset + 4) + end + + private + + # the only pointer type we support is an unsigned long (DWORD) + def deref(ptr) + @image[ptr, 4].unpack("L").first + end + + def e_lfanew_offset + DOS_HEADER_SIZE - DWORD_SIZE + end + + def pe_header_offset + deref(e_lfanew_offset) + end + + def image_optional_header_offset + pe_header_offset + PE_SIGNATURE_SIZE + IMAGE_FILE_HEADER_SIZE + end + + # 2 DWORDs + def data_directory_entry_size + 4 * 2 + end + + def data_directories_array_size + data_directory_entry_size * NUMBER_OF_DATA_DIRECTORY_ENTRIES + end + + def image_data_directory_offset + image_optional_header_offset + IMAGE_OPTIONAL_HEADER_SIZE - + data_directories_array_size + end +end From 261d7513dc5f159c933edf838762c8f399d801be Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 02:57:33 +0100 Subject: [PATCH 29/43] Add specs for code signing --- test/test_ocra.rb | 1228 +++++++++++++++++++++++---------------------- 1 file changed, 622 insertions(+), 606 deletions(-) diff --git a/test/test_ocra.rb b/test/test_ocra.rb index c75b7dbe..7c531caf 100644 --- a/test/test_ocra.rb +++ b/test/test_ocra.rb @@ -4,6 +4,7 @@ require "fileutils" require "rbconfig" require "pathname" +require File.join(File.dirname(__FILE__), "fake_code_signer") begin require "rubygems" @@ -135,618 +136,633 @@ def test_helloworld end end - # Should be able to build executables with LZMA compression - def test_lzma - with_fixture 'helloworld' do - assert system("ruby", ocra, "helloworld.rb", "--quiet", "--lzma") - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - assert system("helloworld.exe") - end - end - end - - # Test that executables can writing a file to the current working - # directory. - def test_writefile - with_fixture 'writefile' do - assert system("ruby", ocra, "writefile.rb", *DefaultArgs) - assert File.exist?("output.txt") # Make sure ocra ran the script during build - pristine_env "writefile.exe" do - assert File.exist?("writefile.exe") - assert system("writefile.exe") - assert File.exist?("output.txt") - assert_equal "output", File.read("output.txt") - end - end - end - - # With --no-dep-run, ocra should not run script during build - def test_nodeprun - with_fixture 'writefile' do - assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--no-dep-run"])) - assert !File.exist?("output.txt") - pristine_env "writefile.exe" do - assert File.exist?("writefile.exe") - assert system("writefile.exe") - assert File.exist?("output.txt") - assert_equal "output", File.read("output.txt") - end - end - end - - # With dep run disabled but including all core libs, should be able - # to use ruby standard libraries (i.e. cgi) - def test_rubycoreincl - with_fixture 'rubycoreincl' do - assert system("ruby", ocra, "rubycoreincl.rb", *(DefaultArgs + ["--no-dep-run", "--add-all-core"])) - pristine_env "rubycoreincl.exe" do - assert File.exist?("rubycoreincl.exe") - assert system("rubycoreincl.exe") - assert File.exist?("output.txt") - assert_equal "3 < 5", File.read("output.txt") - end - end - end - - # With dep run disabled but including corelibs and using a Bundler Gemfile, specified gems should - # be automatically included and usable in packaged app - def test_gemfile - with_fixture 'bundlerusage' do - assert system("ruby", ocra, "bundlerusage.rb", "Gemfile", *(DefaultArgs + ["--no-dep-run", "--add-all-core", "--gemfile", "Gemfile", "--gem-all"])) - pristine_env "bundlerusage.exe" do - assert system("bundlerusage.exe") - end - end - end - - # With --debug-extract option, exe should unpack to local directory and leave it in place - def test_debug_extract - with_fixture 'helloworld' do - assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--debug-extract"])) - pristine_env "helloworld.exe" do - assert_equal 0, Dir["ocr*"].size - assert system("helloworld.exe") - assert_equal 1, Dir["ocr*"].size - end - end - end - - # Test that the --output option allows us to specify a different exe name - def test_output_option - with_fixture 'helloworld' do - assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--output", "goodbyeworld.exe"])) - assert !File.exist?("helloworld.exe") - assert File.exist?("goodbyeworld.exe") - end - end - - # Test that we can specify a directory to be recursively included - def test_directory_on_cmd_line - with_fixture 'subdir' do - assert system("ruby", ocra, "subdir.rb", "a", *DefaultArgs) - pristine_env "subdir.exe" do - assert system("subdir.exe") - end - end - end - - # Test that scripts can exit with a specific exit status code. - def test_exitstatus - with_fixture 'exitstatus' do - assert system("ruby", ocra, "exitstatus.rb", *DefaultArgs) - pristine_env "exitstatus.exe" do - system("exitstatus.exe") - assert_equal 167, $?.exitstatus - end - end - end - - # Test that arguments are passed correctly to scripts. - def test_arguments1 - with_fixture 'arguments' do - assert system("ruby", ocra, "arguments.rb", *DefaultArgs) - assert File.exist?("arguments.exe") - pristine_env "arguments.exe" do - system("arguments.exe foo \"bar baz \\\"quote\\\"\"") - assert_equal 5, $?.exitstatus - end - end - end - - # Test that arguments are passed correctly to scripts (specified at - # compile time). - def test_arguments2 - with_fixture 'arguments' do - args = DefaultArgs + ["--", "foo", "bar baz \"quote\"" ] - assert system("ruby", ocra, "arguments.rb", *args) - assert File.exist?("arguments.exe") - pristine_env "arguments.exe" do - system("arguments.exe") - assert_equal 5, $?.exitstatus - end - end - end - - # Test that arguments are passed correctly to scripts (specified at - # compile time). - def test_arguments3 - with_fixture 'arguments' do - args = DefaultArgs + ["--", "foo"] - assert system("ruby", ocra, "arguments.rb", *args) - assert File.exist?("arguments.exe") - pristine_env "arguments.exe" do - system("arguments.exe \"bar baz \\\"quote\\\"\"") - assert_equal 5, $?.exitstatus - end - end - end - - # Test that arguments are passed correctly at build time. - def test_buildarg - with_fixture "buildarg" do - args = DefaultArgs + [ "--", "--some-option" ] - assert system("ruby", ocra, "buildarg.rb", *args) - assert File.exist?("buildarg.exe") - pristine_env "buildarg.exe" do - assert system("buildarg.exe") - end - end - end - - # Test that the standard output from a script can be redirected to a - # file. - def test_stdout_redir - with_fixture 'stdoutredir' do - assert system("ruby", ocra, "stdoutredir.rb", *DefaultArgs) - assert File.exist?("stdoutredir.exe") - pristine_env "stdoutredir.exe" do - system("stdoutredir.exe > output.txt") - assert File.exist?("output.txt") - assert_equal "Hello, World!\n", File.read("output.txt") - end - end - end - - # Test that the standard input to a script can be redirected from a - # file. - def test_stdin_redir - with_fixture 'stdinredir' do - assert system("ruby", ocra, "stdinredir.rb", *DefaultArgs) - assert File.exist?("stdinredir.exe") - # Kernel.system("ruby -e \"system 'stdinredir.exe '.' do - pristine_env "gdbmdll.exe" do - system("gdbmdll.exe") - assert_equal 104, $?.exitstatus - end - end - end - end - - # Test that scripts can require a file relative to the location of - # the script and that such files are correctly added to the - # executable. - def test_relative_require - with_fixture 'relativerequire' do - assert system("ruby", ocra, "relativerequire.rb", *DefaultArgs) - assert File.exist?("relativerequire.exe") - pristine_env "relativerequire.exe" do - system("relativerequire.exe") - assert_equal 160, $?.exitstatus - end - end - end - - # Test that autoloaded files which are not actually loaded while - # running the script through Ocra are included in the resulting - # executable. - def test_autoload - with_fixture 'autoload' do - assert system("ruby", ocra, "autoload.rb", *DefaultArgs) - assert File.exist?("autoload.exe") - pristine_env "autoload.exe" do - assert system("autoload.exe") - end - end - end - - # Test that autoload statement which point to non-existing files are - # ignored by Ocra (a warning may be logged). - def test_autoload_missing - with_fixture 'autoloadmissing' do - args = DefaultArgs.dup - args.push '--no-warnings' - assert system("ruby", ocra, "autoloadmissing.rb", *args) - assert File.exist?("autoloadmissing.exe") - pristine_env "autoloadmissing.exe" do - assert system("autoloadmissing.exe") - end - end - end - - # Test that Ocra picks up autoload statement nested in modules. - def test_autoload_nested - with_fixture 'autoloadnested' do - assert system("ruby", ocra, "autoloadnested.rb", *DefaultArgs) - assert File.exist?("autoloadnested.exe") - pristine_env "autoloadnested.exe" do - assert system("autoloadnested.exe") - end - end - end - - # Should find features via relative require paths, after script - # changes to the right directory (Only valid for Ruby < 1.9.2). - def test_relative_require_chdir_path - with_fixture "relloadpath" do - each_path_combo "bin/chdir1.rb" do |script| - assert system('ruby', ocra, script, *DefaultArgs) - assert File.exist?('chdir1.exe') - pristine_env "chdir1.exe" do - assert system('chdir1.exe') - end - end - end - end - - # Should find features via relative require paths prefixed with - # './', after script changes to the right directory. - def test_relative_require_chdir_dotpath - with_fixture "relloadpath" do - each_path_combo "bin/chdir2.rb" do |script| - assert system('ruby', ocra, script, *DefaultArgs) - assert File.exist?('chdir2.exe') - pristine_env "chdir2.exe" do - assert system('chdir2.exe') - end - end - end - end - - # Should pick up files from relative load paths specified using the - # -I option when invoking Ocra, and invoking from same directory as - # script. - def test_relative_require_i - with_fixture 'relloadpath' do - each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| - assert system('ruby', '-I', loadpaths[0], '-I', loadpaths[1], ocra, script, *DefaultArgs) - assert File.exist?('external.exe') - pristine_env "external.exe" do - assert system('external.exe') - end - end - end - end - - # Should pick up files from relative load path specified using the - # RUBYLIB environment variable. - def test_relative_require_rubylib - with_fixture 'relloadpath' do - each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| - with_env 'RUBYLIB' => loadpaths.join(';') do - assert system('ruby', ocra, script, *DefaultArgs) - end - assert File.exist?('external.exe') - pristine_env "external.exe" do - assert system('external.exe') - end - end - end - end - - # Should pick up file when script modifies $LOAD_PATH by adding - # dirname of script. - def test_loadpath_mangling_dirname - with_fixture 'relloadpath' do - each_path_combo "bin/loadpath0.rb" do |script| - assert system('ruby', ocra, script, *DefaultArgs) - assert File.exist?('loadpath0.exe') - pristine_env "loadpath0.exe" do - assert system('loadpath0.exe') - end - end - end - end - - # Should pick up file when script modifies $LOAD_PATH by adding - # relative paths, and invoking from same directory. - def test_loadpath_mangling_path - with_fixture 'relloadpath' do - each_path_combo "bin/loadpath1.rb" do |script| - assert system('ruby', ocra, script, *DefaultArgs) - assert File.exist?('loadpath1.exe') - pristine_env "loadpath1.exe" do - assert system('loadpath1.exe') - end - end - end - end - - # Should pick up file when script modifies $LOAD_PATH by adding - # relative paths with './'-prefix - def test_loadpath_mangling_dotpath - with_fixture 'relloadpath' do - each_path_combo "bin/loadpath2.rb" do |script| - assert system('ruby', ocra, script, *DefaultArgs) - assert File.exist?('loadpath2.exe') - pristine_env "loadpath2.exe" do - assert system('loadpath2.exe') - end - end - end - end - - # Should pick up file when script modifies $LOAD_PATH by adding - # absolute paths. - def test_loadpath_mangling_abspath - with_fixture 'relloadpath' do - each_path_combo "bin/loadpath3.rb" do |script| - assert system('ruby', ocra, script, *DefaultArgs) - assert File.exist?('loadpath3.exe') - pristine_env "loadpath3.exe" do - assert system('loadpath3.exe') - end - end - end - end - - # Test that ocra.rb accepts --version and outputs the version number. - def test_version - assert_match(/^Ocra \d+(\.\d)+(.[a-z]+\d+)?$/, `ruby \"#{ocra}\" --version`) - end - - # Test that ocra.rb accepts --icon. - def test_icon - with_fixture 'helloworld' do - icofile = File.join(OcraRoot, 'src', 'vit-ruby.ico') - assert system("ruby", ocra, '--icon', icofile, "helloworld.rb", *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - assert system("helloworld.exe") - end - end - end + # # Should be able to build executables with LZMA compression + # def test_lzma + # with_fixture 'helloworld' do + # assert system("ruby", ocra, "helloworld.rb", "--quiet", "--lzma") + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + + # # Test that executables can writing a file to the current working + # # directory. + # def test_writefile + # with_fixture 'writefile' do + # assert system("ruby", ocra, "writefile.rb", *DefaultArgs) + # assert File.exist?("output.txt") # Make sure ocra ran the script during build + # pristine_env "writefile.exe" do + # assert File.exist?("writefile.exe") + # assert system("writefile.exe") + # assert File.exist?("output.txt") + # assert_equal "output", File.read("output.txt") + # end + # end + # end + + # # With --no-dep-run, ocra should not run script during build + # def test_nodeprun + # with_fixture 'writefile' do + # assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--no-dep-run"])) + # assert !File.exist?("output.txt") + # pristine_env "writefile.exe" do + # assert File.exist?("writefile.exe") + # assert system("writefile.exe") + # assert File.exist?("output.txt") + # assert_equal "output", File.read("output.txt") + # end + # end + # end + + # # With dep run disabled but including all core libs, should be able + # # to use ruby standard libraries (i.e. cgi) + # def test_rubycoreincl + # with_fixture 'rubycoreincl' do + # assert system("ruby", ocra, "rubycoreincl.rb", *(DefaultArgs + ["--no-dep-run", "--add-all-core"])) + # pristine_env "rubycoreincl.exe" do + # assert File.exist?("rubycoreincl.exe") + # assert system("rubycoreincl.exe") + # assert File.exist?("output.txt") + # assert_equal "3 < 5", File.read("output.txt") + # end + # end + # end + + # # With dep run disabled but including corelibs and using a Bundler Gemfile, specified gems should + # # be automatically included and usable in packaged app + # def test_gemfile + # with_fixture 'bundlerusage' do + # assert system("ruby", ocra, "bundlerusage.rb", "Gemfile", *(DefaultArgs + ["--no-dep-run", "--add-all-core", "--gemfile", "Gemfile", "--gem-all"])) + # pristine_env "bundlerusage.exe" do + # assert system("bundlerusage.exe") + # end + # end + # end + + # # With --debug-extract option, exe should unpack to local directory and leave it in place + # def test_debug_extract + # with_fixture 'helloworld' do + # assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--debug-extract"])) + # pristine_env "helloworld.exe" do + # assert_equal 0, Dir["ocr*"].size + # assert system("helloworld.exe") + # assert_equal 1, Dir["ocr*"].size + # end + # end + # end + + # # Test that the --output option allows us to specify a different exe name + # def test_output_option + # with_fixture 'helloworld' do + # assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--output", "goodbyeworld.exe"])) + # assert !File.exist?("helloworld.exe") + # assert File.exist?("goodbyeworld.exe") + # end + # end + + # # Test that we can specify a directory to be recursively included + # def test_directory_on_cmd_line + # with_fixture 'subdir' do + # assert system("ruby", ocra, "subdir.rb", "a", *DefaultArgs) + # pristine_env "subdir.exe" do + # assert system("subdir.exe") + # end + # end + # end + + # # Test that scripts can exit with a specific exit status code. + # def test_exitstatus + # with_fixture 'exitstatus' do + # assert system("ruby", ocra, "exitstatus.rb", *DefaultArgs) + # pristine_env "exitstatus.exe" do + # system("exitstatus.exe") + # assert_equal 167, $?.exitstatus + # end + # end + # end + + # # Test that arguments are passed correctly to scripts. + # def test_arguments1 + # with_fixture 'arguments' do + # assert system("ruby", ocra, "arguments.rb", *DefaultArgs) + # assert File.exist?("arguments.exe") + # pristine_env "arguments.exe" do + # system("arguments.exe foo \"bar baz \\\"quote\\\"\"") + # assert_equal 5, $?.exitstatus + # end + # end + # end + + # # Test that arguments are passed correctly to scripts (specified at + # # compile time). + # def test_arguments2 + # with_fixture 'arguments' do + # args = DefaultArgs + ["--", "foo", "bar baz \"quote\"" ] + # assert system("ruby", ocra, "arguments.rb", *args) + # assert File.exist?("arguments.exe") + # pristine_env "arguments.exe" do + # system("arguments.exe") + # assert_equal 5, $?.exitstatus + # end + # end + # end + + # # Test that arguments are passed correctly to scripts (specified at + # # compile time). + # def test_arguments3 + # with_fixture 'arguments' do + # args = DefaultArgs + ["--", "foo"] + # assert system("ruby", ocra, "arguments.rb", *args) + # assert File.exist?("arguments.exe") + # pristine_env "arguments.exe" do + # system("arguments.exe \"bar baz \\\"quote\\\"\"") + # assert_equal 5, $?.exitstatus + # end + # end + # end + + # # Test that arguments are passed correctly at build time. + # def test_buildarg + # with_fixture "buildarg" do + # args = DefaultArgs + [ "--", "--some-option" ] + # assert system("ruby", ocra, "buildarg.rb", *args) + # assert File.exist?("buildarg.exe") + # pristine_env "buildarg.exe" do + # assert system("buildarg.exe") + # end + # end + # end + + # # Test that the standard output from a script can be redirected to a + # # file. + # def test_stdout_redir + # with_fixture 'stdoutredir' do + # assert system("ruby", ocra, "stdoutredir.rb", *DefaultArgs) + # assert File.exist?("stdoutredir.exe") + # pristine_env "stdoutredir.exe" do + # system("stdoutredir.exe > output.txt") + # assert File.exist?("output.txt") + # assert_equal "Hello, World!\n", File.read("output.txt") + # end + # end + # end + + # # Test that the standard input to a script can be redirected from a + # # file. + # def test_stdin_redir + # with_fixture 'stdinredir' do + # assert system("ruby", ocra, "stdinredir.rb", *DefaultArgs) + # assert File.exist?("stdinredir.exe") + # # Kernel.system("ruby -e \"system 'stdinredir.exe '.' do + # pristine_env "gdbmdll.exe" do + # system("gdbmdll.exe") + # assert_equal 104, $?.exitstatus + # end + # end + # end + # end + + # # Test that scripts can require a file relative to the location of + # # the script and that such files are correctly added to the + # # executable. + # def test_relative_require + # with_fixture 'relativerequire' do + # assert system("ruby", ocra, "relativerequire.rb", *DefaultArgs) + # assert File.exist?("relativerequire.exe") + # pristine_env "relativerequire.exe" do + # system("relativerequire.exe") + # assert_equal 160, $?.exitstatus + # end + # end + # end + + # # Test that autoloaded files which are not actually loaded while + # # running the script through Ocra are included in the resulting + # # executable. + # def test_autoload + # with_fixture 'autoload' do + # assert system("ruby", ocra, "autoload.rb", *DefaultArgs) + # assert File.exist?("autoload.exe") + # pristine_env "autoload.exe" do + # assert system("autoload.exe") + # end + # end + # end + + # # Test that autoload statement which point to non-existing files are + # # ignored by Ocra (a warning may be logged). + # def test_autoload_missing + # with_fixture 'autoloadmissing' do + # args = DefaultArgs.dup + # args.push '--no-warnings' + # assert system("ruby", ocra, "autoloadmissing.rb", *args) + # assert File.exist?("autoloadmissing.exe") + # pristine_env "autoloadmissing.exe" do + # assert system("autoloadmissing.exe") + # end + # end + # end + + # # Test that Ocra picks up autoload statement nested in modules. + # def test_autoload_nested + # with_fixture 'autoloadnested' do + # assert system("ruby", ocra, "autoloadnested.rb", *DefaultArgs) + # assert File.exist?("autoloadnested.exe") + # pristine_env "autoloadnested.exe" do + # assert system("autoloadnested.exe") + # end + # end + # end + + # # Should find features via relative require paths, after script + # # changes to the right directory (Only valid for Ruby < 1.9.2). + # def test_relative_require_chdir_path + # with_fixture "relloadpath" do + # each_path_combo "bin/chdir1.rb" do |script| + # assert system('ruby', ocra, script, *DefaultArgs) + # assert File.exist?('chdir1.exe') + # pristine_env "chdir1.exe" do + # assert system('chdir1.exe') + # end + # end + # end + # end + + # # Should find features via relative require paths prefixed with + # # './', after script changes to the right directory. + # def test_relative_require_chdir_dotpath + # with_fixture "relloadpath" do + # each_path_combo "bin/chdir2.rb" do |script| + # assert system('ruby', ocra, script, *DefaultArgs) + # assert File.exist?('chdir2.exe') + # pristine_env "chdir2.exe" do + # assert system('chdir2.exe') + # end + # end + # end + # end + + # # Should pick up files from relative load paths specified using the + # # -I option when invoking Ocra, and invoking from same directory as + # # script. + # def test_relative_require_i + # with_fixture 'relloadpath' do + # each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| + # assert system('ruby', '-I', loadpaths[0], '-I', loadpaths[1], ocra, script, *DefaultArgs) + # assert File.exist?('external.exe') + # pristine_env "external.exe" do + # assert system('external.exe') + # end + # end + # end + # end + + # # Should pick up files from relative load path specified using the + # # RUBYLIB environment variable. + # def test_relative_require_rubylib + # with_fixture 'relloadpath' do + # each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| + # with_env 'RUBYLIB' => loadpaths.join(';') do + # assert system('ruby', ocra, script, *DefaultArgs) + # end + # assert File.exist?('external.exe') + # pristine_env "external.exe" do + # assert system('external.exe') + # end + # end + # end + # end + + # # Should pick up file when script modifies $LOAD_PATH by adding + # # dirname of script. + # def test_loadpath_mangling_dirname + # with_fixture 'relloadpath' do + # each_path_combo "bin/loadpath0.rb" do |script| + # assert system('ruby', ocra, script, *DefaultArgs) + # assert File.exist?('loadpath0.exe') + # pristine_env "loadpath0.exe" do + # assert system('loadpath0.exe') + # end + # end + # end + # end + + # # Should pick up file when script modifies $LOAD_PATH by adding + # # relative paths, and invoking from same directory. + # def test_loadpath_mangling_path + # with_fixture 'relloadpath' do + # each_path_combo "bin/loadpath1.rb" do |script| + # assert system('ruby', ocra, script, *DefaultArgs) + # assert File.exist?('loadpath1.exe') + # pristine_env "loadpath1.exe" do + # assert system('loadpath1.exe') + # end + # end + # end + # end + + # # Should pick up file when script modifies $LOAD_PATH by adding + # # relative paths with './'-prefix + # def test_loadpath_mangling_dotpath + # with_fixture 'relloadpath' do + # each_path_combo "bin/loadpath2.rb" do |script| + # assert system('ruby', ocra, script, *DefaultArgs) + # assert File.exist?('loadpath2.exe') + # pristine_env "loadpath2.exe" do + # assert system('loadpath2.exe') + # end + # end + # end + # end + + # # Should pick up file when script modifies $LOAD_PATH by adding + # # absolute paths. + # def test_loadpath_mangling_abspath + # with_fixture 'relloadpath' do + # each_path_combo "bin/loadpath3.rb" do |script| + # assert system('ruby', ocra, script, *DefaultArgs) + # assert File.exist?('loadpath3.exe') + # pristine_env "loadpath3.exe" do + # assert system('loadpath3.exe') + # end + # end + # end + # end + + # # Test that ocra.rb accepts --version and outputs the version number. + # def test_version + # assert_match(/^Ocra \d+(\.\d)+(.[a-z]+\d+)?$/, `ruby \"#{ocra}\" --version`) + # end + + # # Test that ocra.rb accepts --icon. + # def test_icon + # with_fixture 'helloworld' do + # icofile = File.join(OcraRoot, 'src', 'vit-ruby.ico') + # assert system("ruby", ocra, '--icon', icofile, "helloworld.rb", *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + + # # Test that additional non-script files can be added to the + # # executable and used by the script. + # def test_resource + # with_fixture 'resource' do + # assert system("ruby", ocra, "resource.rb", "resource.txt", "res/resource.txt", *DefaultArgs) + # assert File.exist?("resource.exe") + # pristine_env "resource.exe" do + # assert system("resource.exe") + # end + # end + # end + + # # Test that when exceptions are thrown, no executable will be built. + # def test_exception + # with_fixture 'exception' do + # system("ruby \"#{ocra}\" exception.rb #{DefaultArgs.join(' ')} 2>NUL") + # assert $?.exitstatus != 0 + # assert !File.exist?("exception.exe") + # end + # end + + # # Test that the RUBYOPT environment variable is preserved. + # def test_rubyopt + # with_fixture 'environment' do + # with_env "RUBYOPT" => "-rtime" do + # assert system("ruby", ocra, "environment.rb", *DefaultArgs) + # pristine_env "environment.exe" do + # assert system("environment.exe") + # env = Marshal.load(File.open("environment", "rb") { |f| f.read }) + # assert_equal "-rtime", env['RUBYOPT'] + # end + # end + # end + # end + + # def test_exit + # with_fixture 'exit' do + # assert system("ruby", ocra, "exit.rb", *DefaultArgs) + # pristine_env "exit.exe" do + # assert File.exist?("exit.exe") + # assert system("exit.exe") + # end + # end + # end + + # def test_ocra_executable_env + # with_fixture 'environment' do + # assert system("ruby", ocra, "environment.rb", *DefaultArgs) + # pristine_env "environment.exe" do + # assert system("environment.exe") + # env = Marshal.load(File.open("environment", "rb") { |f| f.read }) + # expected_path = File.expand_path("environment.exe").tr('/','\\') + # assert_equal expected_path, env['OCRA_EXECUTABLE'] + # end + # end + # end + + # def test_hierarchy + # with_fixture 'hierarchy' do + # assert system("ruby", ocra, "hierarchy.rb", "assets/**/*", *DefaultArgs) + # pristine_env "hierarchy.exe" do + # assert system("hierarchy.exe") + # end + # end + # end + + # def test_temp_with_space + # with_fixture 'helloworld' do + # assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) + # tempdir = File.expand_path("temporary directory") + # mkdir_p tempdir + # pristine_env "helloworld.exe" do + # with_env "TMP" => tempdir.tr('/','\\') do + # assert system("helloworld.exe") + # end + # end + # end + # end + + # # Should be able to build executable when specifying absolute path + # # to the script from somewhere else. + # def test_abspath + # with_fixture "helloworld" do + # script_path = File.expand_path("helloworld.rb") + # with_tmpdir do + # assert system("ruby", ocra, script_path, *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + # end + + # def test_abspath_outside + # with_fixture "helloworld" do + # mkdir "build" + # cd "build" do + # assert system("ruby", ocra, File.expand_path("../helloworld.rb"), *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + # end + + # def test_relpath + # with_fixture "helloworld" do + # assert system("ruby", ocra, "./helloworld.rb", *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + + # def test_relpath_outside + # with_fixture "helloworld" do + # mkdir "build" + # cd "build" do + # assert system("ruby", ocra, "../helloworld.rb", *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + # end + + # # Should accept hierachical source code layout + # def test_srcroot + # with_fixture "srcroot" do + # assert system("ruby", ocra, "bin/srcroot.rb", "share/data.txt", *DefaultArgs) + # assert File.exist?("srcroot.exe") + # pristine_env "srcroot.exe" do + # exe = File.expand_path("srcroot.exe") + # cd ENV["SystemRoot"] do + # assert system(exe) + # end + # end + # end + # end + + # # Should be able to build executables when script changes directory. + # def test_chdir + # with_fixture "chdir" do + # assert system("ruby", ocra, "chdir.rb", *DefaultArgs) + # assert File.exist?("chdir.exe") + # pristine_env "chdir.exe" do + # exe = File.expand_path("chdir.exe") + # cd ENV["SystemRoot"] do + # assert system(exe) + # end + # end + # end + # end + + # # Test that the --chdir-first option changes directory before exe starts script + # def test_chdir_first + # with_fixture 'writefile' do + # # Control test; make sure the writefile script works as expected under default options + # assert system("ruby", ocra, "writefile.rb", *(DefaultArgs)) + # pristine_env "writefile.exe" do + # assert !File.exist?("output.txt") + # assert system("writefile.exe") + # assert File.exist?("output.txt") + # end + + # assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--chdir-first"])) + # pristine_env "writefile.exe" do + # assert !File.exist?("output.txt") + # assert system("writefile.exe") + # # If the script ran in its inst directory, then our working dir still shouldn't have any output.txt + # assert !File.exist?("output.txt") + # end + # end + # end + + # # Would be nice if OCRA could build from source located beneath the + # # Ruby installation too. + # def test_exec_prefix + # path = File.join(RbConfig::CONFIG["exec_prefix"], "ocratempsrc") + # with_fixture "helloworld", path do + # assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # assert system("helloworld.exe") + # end + # end + # end + + # def test_explicit_in_exec_prefix + # return unless File.directory?(RbConfig::CONFIG["exec_prefix"] + "/include") + # path = File.join(RbConfig::CONFIG["exec_prefix"], "include", "**", "*.h") + # number_of_files = Dir[path].size + # assert number_of_files > 3 + # with_fixture "check_includes" do + # assert system("ruby", ocra, "check_includes.rb", path, *DefaultArgs) + # assert File.exist?("check_includes.exe") + # pristine_env "check_includes.exe" do + # assert system("check_includes.exe", number_of_files.to_s) + # end + # end + # end + + # # Hello world test. Test that we can build and run executables. + # def test_nonexistent_temp + # with_fixture 'helloworld' do + # assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) + # assert File.exist?("helloworld.exe") + # pristine_env "helloworld.exe" do + # with_env "TEMP" => "c:\\thispathdoesnotexist12345", "TMP" => "c:\\thispathdoesnotexist12345" do + # assert File.exist?("helloworld.exe") + # system("helloworld.exe 2>NUL") + # assert File.exist?("helloworld.exe") + # end + # end + # end + # end - # Test that additional non-script files can be added to the - # executable and used by the script. - def test_resource - with_fixture 'resource' do - assert system("ruby", ocra, "resource.rb", "resource.txt", "res/resource.txt", *DefaultArgs) - assert File.exist?("resource.exe") - pristine_env "resource.exe" do - assert system("resource.exe") - end - end - end - - # Test that when exceptions are thrown, no executable will be built. - def test_exception - with_fixture 'exception' do - system("ruby \"#{ocra}\" exception.rb #{DefaultArgs.join(' ')} 2>NUL") - assert $?.exitstatus != 0 - assert !File.exist?("exception.exe") - end - end - - # Test that the RUBYOPT environment variable is preserved. - def test_rubyopt - with_fixture 'environment' do - with_env "RUBYOPT" => "-rtime" do - assert system("ruby", ocra, "environment.rb", *DefaultArgs) - pristine_env "environment.exe" do - assert system("environment.exe") - env = Marshal.load(File.open("environment", "rb") { |f| f.read }) - assert_equal "-rtime", env['RUBYOPT'] - end - end - end - end - - def test_exit - with_fixture 'exit' do - assert system("ruby", ocra, "exit.rb", *DefaultArgs) - pristine_env "exit.exe" do - assert File.exist?("exit.exe") - assert system("exit.exe") - end - end - end - - def test_ocra_executable_env - with_fixture 'environment' do - assert system("ruby", ocra, "environment.rb", *DefaultArgs) - pristine_env "environment.exe" do - assert system("environment.exe") - env = Marshal.load(File.open("environment", "rb") { |f| f.read }) - expected_path = File.expand_path("environment.exe").tr('/','\\') - assert_equal expected_path, env['OCRA_EXECUTABLE'] - end - end - end - - def test_hierarchy - with_fixture 'hierarchy' do - assert system("ruby", ocra, "hierarchy.rb", "assets/**/*", *DefaultArgs) - pristine_env "hierarchy.exe" do - assert system("hierarchy.exe") - end - end - end - - def test_temp_with_space + # Hello world test. Test that we can build and run executables. + def test_codesigning_support with_fixture 'helloworld' do - assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) - tempdir = File.expand_path("temporary directory") - mkdir_p tempdir - pristine_env "helloworld.exe" do - with_env "TMP" => tempdir.tr('/','\\') do - assert system("helloworld.exe") - end - end - end - end - - # Should be able to build executable when specifying absolute path - # to the script from somewhere else. - def test_abspath - with_fixture "helloworld" do - script_path = File.expand_path("helloworld.rb") - with_tmpdir do - assert system("ruby", ocra, script_path, *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - assert system("helloworld.exe") - end - end - end - end - - def test_abspath_outside - with_fixture "helloworld" do - mkdir "build" - cd "build" do - assert system("ruby", ocra, File.expand_path("../helloworld.rb"), *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - assert system("helloworld.exe") - end - end - end - end - - def test_relpath - with_fixture "helloworld" do - assert system("ruby", ocra, "./helloworld.rb", *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - assert system("helloworld.exe") - end - end - end + each_path_combo "helloworld.rb" do |script| + assert system("ruby", ocra, script, *DefaultArgs) + FakeCodeSigner.new(input: "helloworld.exe", + output: "helloworld-signed.exe").sign - def test_relpath_outside - with_fixture "helloworld" do - mkdir "build" - cd "build" do - assert system("ruby", ocra, "../helloworld.rb", *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do + pristine_env "helloworld.exe", "helloworld-signed.exe" do assert system("helloworld.exe") + assert system("helloworld-signed.exe") end end end end - - # Should accept hierachical source code layout - def test_srcroot - with_fixture "srcroot" do - assert system("ruby", ocra, "bin/srcroot.rb", "share/data.txt", *DefaultArgs) - assert File.exist?("srcroot.exe") - pristine_env "srcroot.exe" do - exe = File.expand_path("srcroot.exe") - cd ENV["SystemRoot"] do - assert system(exe) - end - end - end - end - - # Should be able to build executables when script changes directory. - def test_chdir - with_fixture "chdir" do - assert system("ruby", ocra, "chdir.rb", *DefaultArgs) - assert File.exist?("chdir.exe") - pristine_env "chdir.exe" do - exe = File.expand_path("chdir.exe") - cd ENV["SystemRoot"] do - assert system(exe) - end - end - end - end - - # Test that the --chdir-first option changes directory before exe starts script - def test_chdir_first - with_fixture 'writefile' do - # Control test; make sure the writefile script works as expected under default options - assert system("ruby", ocra, "writefile.rb", *(DefaultArgs)) - pristine_env "writefile.exe" do - assert !File.exist?("output.txt") - assert system("writefile.exe") - assert File.exist?("output.txt") - end - - assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--chdir-first"])) - pristine_env "writefile.exe" do - assert !File.exist?("output.txt") - assert system("writefile.exe") - # If the script ran in its inst directory, then our working dir still shouldn't have any output.txt - assert !File.exist?("output.txt") - end - end - end - - # Would be nice if OCRA could build from source located beneath the - # Ruby installation too. - def test_exec_prefix - path = File.join(RbConfig::CONFIG["exec_prefix"], "ocratempsrc") - with_fixture "helloworld", path do - assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - assert system("helloworld.exe") - end - end - end - - def test_explicit_in_exec_prefix - return unless File.directory?(RbConfig::CONFIG["exec_prefix"] + "/include") - path = File.join(RbConfig::CONFIG["exec_prefix"], "include", "**", "*.h") - number_of_files = Dir[path].size - assert number_of_files > 3 - with_fixture "check_includes" do - assert system("ruby", ocra, "check_includes.rb", path, *DefaultArgs) - assert File.exist?("check_includes.exe") - pristine_env "check_includes.exe" do - assert system("check_includes.exe", number_of_files.to_s) - end - end - end - - # Hello world test. Test that we can build and run executables. - def test_nonexistent_temp - with_fixture 'helloworld' do - assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) - assert File.exist?("helloworld.exe") - pristine_env "helloworld.exe" do - with_env "TEMP" => "c:\\thispathdoesnotexist12345", "TMP" => "c:\\thispathdoesnotexist12345" do - assert File.exist?("helloworld.exe") - system("helloworld.exe 2>NUL") - assert File.exist?("helloworld.exe") - end - end - end - end - end From 74075833f752cda4578e8afc982f7e65f1029c36 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 03:24:25 +0100 Subject: [PATCH 30/43] Extract out PEHeader and write more docs --- test/fake_code_signer.rb | 103 +-- test/fake_code_signer/pe_header.rb | 92 +++ test/test_ocra.rb | 1231 ++++++++++++++-------------- 3 files changed, 727 insertions(+), 699 deletions(-) create mode 100644 test/fake_code_signer/pe_header.rb diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index b265bd0d..97f5b7c1 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -1,3 +1,20 @@ +require File.join File.dirname(__FILE__), "fake_code_signer/pe_header" + +# This class digitally signs an executable. +# In reality, it doesn't create a "valid" digital signature - but it sets up the executable +# headers correctly with the appropriate size and offsets. It also appends the "signature" to +# the end of the executable, complete with 'padding' between the end of the file and sig. +# Although this signature is not 'valid' in the sense that it meets the authenticode standard +# it still meets the expectations of Ocra which only needs the appended "signature" to match +# the size and location as specificed in the header. +# +# The purpose of this class is to create a "signed" executable so that we can test Ocra's +# ability to build executables that are resilient to authenticode code signing, i.e that +# Ocra executables still work after they've been digitally signed. +# +# For more information see: +# The PE Format deep dive: https://msdn.microsoft.com/en-us/library/ms809762.aspx +# Reverse engineering the authenticode format: https://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt class FakeCodeSigner FAKE_SIG = "fake signature" @@ -10,11 +27,9 @@ def initialize(input:, output:, padding: 4) def sign if pe_header.security_size !=0 - puts "Binary already signed, nothing to do!" - return + raise "Binary already signed, nothing to do!" elsif @input == @output - puts "input and output files must be different!" - return + raise "input and output files must be different!" end @image[pe_header.security_offset, 4] = raw_bytes(@image.size + @padding) @@ -39,83 +54,3 @@ def pe_header @pe_header ||= PEHeader.new(@image) end end - -class PEHeader - # size of a DWORD is 2 words (4 bytes) - DWORD_SIZE = 4 - - # struct IMAGE_DOS_HEADER - # https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html - DOS_HEADER_SIZE = 64 - - # first field from IMAGE_NT_HEADERS - # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx - PE_SIGNATURE_SIZE = 4 - - # struct IMAGE_FILE_HEADER - # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx - IMAGE_FILE_HEADER_SIZE = 20 - - # struct IMAGE_OPTIONAL_HEADER - # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx - IMAGE_OPTIONAL_HEADER_SIZE = 224 - - # http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm - NUMBER_OF_DATA_DIRECTORY_ENTRIES = 16 - - # struct IMAGE_DATA_DIRECTORY - # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx - DATA_DIRECTORY_ENTRY_SIZE = 4 - - def initialize(image) - @image = image - end - - # security is the 4th element in the data directory array - def security_offset - image_data_directory_offset + data_directory_entry_size * 4 - end - - # location of the digital signature - def security_address - deref(security_offset) - end - - # size of the digital signature - def security_size - deref(security_offset + 4) - end - - private - - # the only pointer type we support is an unsigned long (DWORD) - def deref(ptr) - @image[ptr, 4].unpack("L").first - end - - def e_lfanew_offset - DOS_HEADER_SIZE - DWORD_SIZE - end - - def pe_header_offset - deref(e_lfanew_offset) - end - - def image_optional_header_offset - pe_header_offset + PE_SIGNATURE_SIZE + IMAGE_FILE_HEADER_SIZE - end - - # 2 DWORDs - def data_directory_entry_size - 4 * 2 - end - - def data_directories_array_size - data_directory_entry_size * NUMBER_OF_DATA_DIRECTORY_ENTRIES - end - - def image_data_directory_offset - image_optional_header_offset + IMAGE_OPTIONAL_HEADER_SIZE - - data_directories_array_size - end -end diff --git a/test/fake_code_signer/pe_header.rb b/test/fake_code_signer/pe_header.rb new file mode 100644 index 00000000..c817dab2 --- /dev/null +++ b/test/fake_code_signer/pe_header.rb @@ -0,0 +1,92 @@ +class FakeCodeSigner + # This class exists to navigate and manipulate the headers of the Windows + # executable format, PE, see: https://en.wikipedia.org/wiki/Portable_Executable + # also https://msdn.microsoft.com/en-us/library/ms809762.aspx + class PEHeader + # size of a DWORD is 2 words (4 bytes) + DWORD_SIZE = 4 + + # struct IMAGE_DOS_HEADER + # https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html + DOS_HEADER_SIZE = 64 + + # first field from IMAGE_NT_HEADERS + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx + PE_SIGNATURE_SIZE = 4 + + # struct IMAGE_FILE_HEADER + # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx + IMAGE_FILE_HEADER_SIZE = 20 + + # struct IMAGE_OPTIONAL_HEADER + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx + IMAGE_OPTIONAL_HEADER_SIZE = 224 + + # http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm + NUMBER_OF_DATA_DIRECTORY_ENTRIES = 16 + + # struct IMAGE_DATA_DIRECTORY + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx + DATA_DIRECTORY_ENTRY_SIZE = 4 + + def initialize(image) + @image = image + end + + # security is the 4th element in the data directory array + # see http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm + def security_offset + image_data_directory_offset + DATA_DIRECTORY_ENTRY_SIZE * 4 + end + + # location of the digital signature + def security_address + deref(security_offset) + end + + # size of the digital signature + def security_size + deref(security_offset + 4) + end + + private + + # dereferences a pointer + # the only pointer type we support is an unsigned long (DWORD) + def deref(ptr) + @image[ptr, 4].unpack("L").first + end + + # offset of e_lfanew (which stores the offset of the actual PE header) + # see: https://msdn.microsoft.com/en-us/library/ms809762.aspx for more info + def e_lfanew_offset + DOS_HEADER_SIZE - DWORD_SIZE + end + + # We dereference e_lfanew_offset to get the actual pe_header_offset + def pe_header_offset + deref(e_lfanew_offset) + end + + # offset of the IMAGE_OPTIONAL_HEADER struct + # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx + def image_optional_header_offset + pe_header_offset + PE_SIGNATURE_SIZE + IMAGE_FILE_HEADER_SIZE + end + + # DataDirectory is an array + # and is the last element of the IMAGE_OPTIONAL_HEADER struct + # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx + def data_directories_array_size + DATA_DIRECTORY_ENTRY_SIZE * NUMBER_OF_DATA_DIRECTORY_ENTRIES + end + + # Since the DataDirectory is the last item in the optional header + # struct we find the DD offset by navigating to the end of the struct + # and then going back `data_directories_array_size` bytes + def image_data_directory_offset + image_optional_header_offset + IMAGE_OPTIONAL_HEADER_SIZE - + data_directories_array_size + end + end +end diff --git a/test/test_ocra.rb b/test/test_ocra.rb index 7c531caf..15072cda 100644 --- a/test/test_ocra.rb +++ b/test/test_ocra.rb @@ -136,627 +136,628 @@ def test_helloworld end end - # # Should be able to build executables with LZMA compression - # def test_lzma - # with_fixture 'helloworld' do - # assert system("ruby", ocra, "helloworld.rb", "--quiet", "--lzma") - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - - # # Test that executables can writing a file to the current working - # # directory. - # def test_writefile - # with_fixture 'writefile' do - # assert system("ruby", ocra, "writefile.rb", *DefaultArgs) - # assert File.exist?("output.txt") # Make sure ocra ran the script during build - # pristine_env "writefile.exe" do - # assert File.exist?("writefile.exe") - # assert system("writefile.exe") - # assert File.exist?("output.txt") - # assert_equal "output", File.read("output.txt") - # end - # end - # end - - # # With --no-dep-run, ocra should not run script during build - # def test_nodeprun - # with_fixture 'writefile' do - # assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--no-dep-run"])) - # assert !File.exist?("output.txt") - # pristine_env "writefile.exe" do - # assert File.exist?("writefile.exe") - # assert system("writefile.exe") - # assert File.exist?("output.txt") - # assert_equal "output", File.read("output.txt") - # end - # end - # end - - # # With dep run disabled but including all core libs, should be able - # # to use ruby standard libraries (i.e. cgi) - # def test_rubycoreincl - # with_fixture 'rubycoreincl' do - # assert system("ruby", ocra, "rubycoreincl.rb", *(DefaultArgs + ["--no-dep-run", "--add-all-core"])) - # pristine_env "rubycoreincl.exe" do - # assert File.exist?("rubycoreincl.exe") - # assert system("rubycoreincl.exe") - # assert File.exist?("output.txt") - # assert_equal "3 < 5", File.read("output.txt") - # end - # end - # end - - # # With dep run disabled but including corelibs and using a Bundler Gemfile, specified gems should - # # be automatically included and usable in packaged app - # def test_gemfile - # with_fixture 'bundlerusage' do - # assert system("ruby", ocra, "bundlerusage.rb", "Gemfile", *(DefaultArgs + ["--no-dep-run", "--add-all-core", "--gemfile", "Gemfile", "--gem-all"])) - # pristine_env "bundlerusage.exe" do - # assert system("bundlerusage.exe") - # end - # end - # end - - # # With --debug-extract option, exe should unpack to local directory and leave it in place - # def test_debug_extract - # with_fixture 'helloworld' do - # assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--debug-extract"])) - # pristine_env "helloworld.exe" do - # assert_equal 0, Dir["ocr*"].size - # assert system("helloworld.exe") - # assert_equal 1, Dir["ocr*"].size - # end - # end - # end - - # # Test that the --output option allows us to specify a different exe name - # def test_output_option - # with_fixture 'helloworld' do - # assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--output", "goodbyeworld.exe"])) - # assert !File.exist?("helloworld.exe") - # assert File.exist?("goodbyeworld.exe") - # end - # end - - # # Test that we can specify a directory to be recursively included - # def test_directory_on_cmd_line - # with_fixture 'subdir' do - # assert system("ruby", ocra, "subdir.rb", "a", *DefaultArgs) - # pristine_env "subdir.exe" do - # assert system("subdir.exe") - # end - # end - # end - - # # Test that scripts can exit with a specific exit status code. - # def test_exitstatus - # with_fixture 'exitstatus' do - # assert system("ruby", ocra, "exitstatus.rb", *DefaultArgs) - # pristine_env "exitstatus.exe" do - # system("exitstatus.exe") - # assert_equal 167, $?.exitstatus - # end - # end - # end - - # # Test that arguments are passed correctly to scripts. - # def test_arguments1 - # with_fixture 'arguments' do - # assert system("ruby", ocra, "arguments.rb", *DefaultArgs) - # assert File.exist?("arguments.exe") - # pristine_env "arguments.exe" do - # system("arguments.exe foo \"bar baz \\\"quote\\\"\"") - # assert_equal 5, $?.exitstatus - # end - # end - # end - - # # Test that arguments are passed correctly to scripts (specified at - # # compile time). - # def test_arguments2 - # with_fixture 'arguments' do - # args = DefaultArgs + ["--", "foo", "bar baz \"quote\"" ] - # assert system("ruby", ocra, "arguments.rb", *args) - # assert File.exist?("arguments.exe") - # pristine_env "arguments.exe" do - # system("arguments.exe") - # assert_equal 5, $?.exitstatus - # end - # end - # end - - # # Test that arguments are passed correctly to scripts (specified at - # # compile time). - # def test_arguments3 - # with_fixture 'arguments' do - # args = DefaultArgs + ["--", "foo"] - # assert system("ruby", ocra, "arguments.rb", *args) - # assert File.exist?("arguments.exe") - # pristine_env "arguments.exe" do - # system("arguments.exe \"bar baz \\\"quote\\\"\"") - # assert_equal 5, $?.exitstatus - # end - # end - # end - - # # Test that arguments are passed correctly at build time. - # def test_buildarg - # with_fixture "buildarg" do - # args = DefaultArgs + [ "--", "--some-option" ] - # assert system("ruby", ocra, "buildarg.rb", *args) - # assert File.exist?("buildarg.exe") - # pristine_env "buildarg.exe" do - # assert system("buildarg.exe") - # end - # end - # end - - # # Test that the standard output from a script can be redirected to a - # # file. - # def test_stdout_redir - # with_fixture 'stdoutredir' do - # assert system("ruby", ocra, "stdoutredir.rb", *DefaultArgs) - # assert File.exist?("stdoutredir.exe") - # pristine_env "stdoutredir.exe" do - # system("stdoutredir.exe > output.txt") - # assert File.exist?("output.txt") - # assert_equal "Hello, World!\n", File.read("output.txt") - # end - # end - # end - - # # Test that the standard input to a script can be redirected from a - # # file. - # def test_stdin_redir - # with_fixture 'stdinredir' do - # assert system("ruby", ocra, "stdinredir.rb", *DefaultArgs) - # assert File.exist?("stdinredir.exe") - # # Kernel.system("ruby -e \"system 'stdinredir.exe '.' do - # pristine_env "gdbmdll.exe" do - # system("gdbmdll.exe") - # assert_equal 104, $?.exitstatus - # end - # end - # end - # end - - # # Test that scripts can require a file relative to the location of - # # the script and that such files are correctly added to the - # # executable. - # def test_relative_require - # with_fixture 'relativerequire' do - # assert system("ruby", ocra, "relativerequire.rb", *DefaultArgs) - # assert File.exist?("relativerequire.exe") - # pristine_env "relativerequire.exe" do - # system("relativerequire.exe") - # assert_equal 160, $?.exitstatus - # end - # end - # end - - # # Test that autoloaded files which are not actually loaded while - # # running the script through Ocra are included in the resulting - # # executable. - # def test_autoload - # with_fixture 'autoload' do - # assert system("ruby", ocra, "autoload.rb", *DefaultArgs) - # assert File.exist?("autoload.exe") - # pristine_env "autoload.exe" do - # assert system("autoload.exe") - # end - # end - # end - - # # Test that autoload statement which point to non-existing files are - # # ignored by Ocra (a warning may be logged). - # def test_autoload_missing - # with_fixture 'autoloadmissing' do - # args = DefaultArgs.dup - # args.push '--no-warnings' - # assert system("ruby", ocra, "autoloadmissing.rb", *args) - # assert File.exist?("autoloadmissing.exe") - # pristine_env "autoloadmissing.exe" do - # assert system("autoloadmissing.exe") - # end - # end - # end - - # # Test that Ocra picks up autoload statement nested in modules. - # def test_autoload_nested - # with_fixture 'autoloadnested' do - # assert system("ruby", ocra, "autoloadnested.rb", *DefaultArgs) - # assert File.exist?("autoloadnested.exe") - # pristine_env "autoloadnested.exe" do - # assert system("autoloadnested.exe") - # end - # end - # end - - # # Should find features via relative require paths, after script - # # changes to the right directory (Only valid for Ruby < 1.9.2). - # def test_relative_require_chdir_path - # with_fixture "relloadpath" do - # each_path_combo "bin/chdir1.rb" do |script| - # assert system('ruby', ocra, script, *DefaultArgs) - # assert File.exist?('chdir1.exe') - # pristine_env "chdir1.exe" do - # assert system('chdir1.exe') - # end - # end - # end - # end - - # # Should find features via relative require paths prefixed with - # # './', after script changes to the right directory. - # def test_relative_require_chdir_dotpath - # with_fixture "relloadpath" do - # each_path_combo "bin/chdir2.rb" do |script| - # assert system('ruby', ocra, script, *DefaultArgs) - # assert File.exist?('chdir2.exe') - # pristine_env "chdir2.exe" do - # assert system('chdir2.exe') - # end - # end - # end - # end - - # # Should pick up files from relative load paths specified using the - # # -I option when invoking Ocra, and invoking from same directory as - # # script. - # def test_relative_require_i - # with_fixture 'relloadpath' do - # each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| - # assert system('ruby', '-I', loadpaths[0], '-I', loadpaths[1], ocra, script, *DefaultArgs) - # assert File.exist?('external.exe') - # pristine_env "external.exe" do - # assert system('external.exe') - # end - # end - # end - # end - - # # Should pick up files from relative load path specified using the - # # RUBYLIB environment variable. - # def test_relative_require_rubylib - # with_fixture 'relloadpath' do - # each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| - # with_env 'RUBYLIB' => loadpaths.join(';') do - # assert system('ruby', ocra, script, *DefaultArgs) - # end - # assert File.exist?('external.exe') - # pristine_env "external.exe" do - # assert system('external.exe') - # end - # end - # end - # end - - # # Should pick up file when script modifies $LOAD_PATH by adding - # # dirname of script. - # def test_loadpath_mangling_dirname - # with_fixture 'relloadpath' do - # each_path_combo "bin/loadpath0.rb" do |script| - # assert system('ruby', ocra, script, *DefaultArgs) - # assert File.exist?('loadpath0.exe') - # pristine_env "loadpath0.exe" do - # assert system('loadpath0.exe') - # end - # end - # end - # end - - # # Should pick up file when script modifies $LOAD_PATH by adding - # # relative paths, and invoking from same directory. - # def test_loadpath_mangling_path - # with_fixture 'relloadpath' do - # each_path_combo "bin/loadpath1.rb" do |script| - # assert system('ruby', ocra, script, *DefaultArgs) - # assert File.exist?('loadpath1.exe') - # pristine_env "loadpath1.exe" do - # assert system('loadpath1.exe') - # end - # end - # end - # end - - # # Should pick up file when script modifies $LOAD_PATH by adding - # # relative paths with './'-prefix - # def test_loadpath_mangling_dotpath - # with_fixture 'relloadpath' do - # each_path_combo "bin/loadpath2.rb" do |script| - # assert system('ruby', ocra, script, *DefaultArgs) - # assert File.exist?('loadpath2.exe') - # pristine_env "loadpath2.exe" do - # assert system('loadpath2.exe') - # end - # end - # end - # end - - # # Should pick up file when script modifies $LOAD_PATH by adding - # # absolute paths. - # def test_loadpath_mangling_abspath - # with_fixture 'relloadpath' do - # each_path_combo "bin/loadpath3.rb" do |script| - # assert system('ruby', ocra, script, *DefaultArgs) - # assert File.exist?('loadpath3.exe') - # pristine_env "loadpath3.exe" do - # assert system('loadpath3.exe') - # end - # end - # end - # end - - # # Test that ocra.rb accepts --version and outputs the version number. - # def test_version - # assert_match(/^Ocra \d+(\.\d)+(.[a-z]+\d+)?$/, `ruby \"#{ocra}\" --version`) - # end - - # # Test that ocra.rb accepts --icon. - # def test_icon - # with_fixture 'helloworld' do - # icofile = File.join(OcraRoot, 'src', 'vit-ruby.ico') - # assert system("ruby", ocra, '--icon', icofile, "helloworld.rb", *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - - # # Test that additional non-script files can be added to the - # # executable and used by the script. - # def test_resource - # with_fixture 'resource' do - # assert system("ruby", ocra, "resource.rb", "resource.txt", "res/resource.txt", *DefaultArgs) - # assert File.exist?("resource.exe") - # pristine_env "resource.exe" do - # assert system("resource.exe") - # end - # end - # end - - # # Test that when exceptions are thrown, no executable will be built. - # def test_exception - # with_fixture 'exception' do - # system("ruby \"#{ocra}\" exception.rb #{DefaultArgs.join(' ')} 2>NUL") - # assert $?.exitstatus != 0 - # assert !File.exist?("exception.exe") - # end - # end - - # # Test that the RUBYOPT environment variable is preserved. - # def test_rubyopt - # with_fixture 'environment' do - # with_env "RUBYOPT" => "-rtime" do - # assert system("ruby", ocra, "environment.rb", *DefaultArgs) - # pristine_env "environment.exe" do - # assert system("environment.exe") - # env = Marshal.load(File.open("environment", "rb") { |f| f.read }) - # assert_equal "-rtime", env['RUBYOPT'] - # end - # end - # end - # end - - # def test_exit - # with_fixture 'exit' do - # assert system("ruby", ocra, "exit.rb", *DefaultArgs) - # pristine_env "exit.exe" do - # assert File.exist?("exit.exe") - # assert system("exit.exe") - # end - # end - # end - - # def test_ocra_executable_env - # with_fixture 'environment' do - # assert system("ruby", ocra, "environment.rb", *DefaultArgs) - # pristine_env "environment.exe" do - # assert system("environment.exe") - # env = Marshal.load(File.open("environment", "rb") { |f| f.read }) - # expected_path = File.expand_path("environment.exe").tr('/','\\') - # assert_equal expected_path, env['OCRA_EXECUTABLE'] - # end - # end - # end - - # def test_hierarchy - # with_fixture 'hierarchy' do - # assert system("ruby", ocra, "hierarchy.rb", "assets/**/*", *DefaultArgs) - # pristine_env "hierarchy.exe" do - # assert system("hierarchy.exe") - # end - # end - # end - - # def test_temp_with_space - # with_fixture 'helloworld' do - # assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) - # tempdir = File.expand_path("temporary directory") - # mkdir_p tempdir - # pristine_env "helloworld.exe" do - # with_env "TMP" => tempdir.tr('/','\\') do - # assert system("helloworld.exe") - # end - # end - # end - # end - - # # Should be able to build executable when specifying absolute path - # # to the script from somewhere else. - # def test_abspath - # with_fixture "helloworld" do - # script_path = File.expand_path("helloworld.rb") - # with_tmpdir do - # assert system("ruby", ocra, script_path, *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - # end - - # def test_abspath_outside - # with_fixture "helloworld" do - # mkdir "build" - # cd "build" do - # assert system("ruby", ocra, File.expand_path("../helloworld.rb"), *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - # end - - # def test_relpath - # with_fixture "helloworld" do - # assert system("ruby", ocra, "./helloworld.rb", *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - - # def test_relpath_outside - # with_fixture "helloworld" do - # mkdir "build" - # cd "build" do - # assert system("ruby", ocra, "../helloworld.rb", *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - # end - - # # Should accept hierachical source code layout - # def test_srcroot - # with_fixture "srcroot" do - # assert system("ruby", ocra, "bin/srcroot.rb", "share/data.txt", *DefaultArgs) - # assert File.exist?("srcroot.exe") - # pristine_env "srcroot.exe" do - # exe = File.expand_path("srcroot.exe") - # cd ENV["SystemRoot"] do - # assert system(exe) - # end - # end - # end - # end - - # # Should be able to build executables when script changes directory. - # def test_chdir - # with_fixture "chdir" do - # assert system("ruby", ocra, "chdir.rb", *DefaultArgs) - # assert File.exist?("chdir.exe") - # pristine_env "chdir.exe" do - # exe = File.expand_path("chdir.exe") - # cd ENV["SystemRoot"] do - # assert system(exe) - # end - # end - # end - # end - - # # Test that the --chdir-first option changes directory before exe starts script - # def test_chdir_first - # with_fixture 'writefile' do - # # Control test; make sure the writefile script works as expected under default options - # assert system("ruby", ocra, "writefile.rb", *(DefaultArgs)) - # pristine_env "writefile.exe" do - # assert !File.exist?("output.txt") - # assert system("writefile.exe") - # assert File.exist?("output.txt") - # end - - # assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--chdir-first"])) - # pristine_env "writefile.exe" do - # assert !File.exist?("output.txt") - # assert system("writefile.exe") - # # If the script ran in its inst directory, then our working dir still shouldn't have any output.txt - # assert !File.exist?("output.txt") - # end - # end - # end - - # # Would be nice if OCRA could build from source located beneath the - # # Ruby installation too. - # def test_exec_prefix - # path = File.join(RbConfig::CONFIG["exec_prefix"], "ocratempsrc") - # with_fixture "helloworld", path do - # assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # assert system("helloworld.exe") - # end - # end - # end - - # def test_explicit_in_exec_prefix - # return unless File.directory?(RbConfig::CONFIG["exec_prefix"] + "/include") - # path = File.join(RbConfig::CONFIG["exec_prefix"], "include", "**", "*.h") - # number_of_files = Dir[path].size - # assert number_of_files > 3 - # with_fixture "check_includes" do - # assert system("ruby", ocra, "check_includes.rb", path, *DefaultArgs) - # assert File.exist?("check_includes.exe") - # pristine_env "check_includes.exe" do - # assert system("check_includes.exe", number_of_files.to_s) - # end - # end - # end - - # # Hello world test. Test that we can build and run executables. - # def test_nonexistent_temp - # with_fixture 'helloworld' do - # assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) - # assert File.exist?("helloworld.exe") - # pristine_env "helloworld.exe" do - # with_env "TEMP" => "c:\\thispathdoesnotexist12345", "TMP" => "c:\\thispathdoesnotexist12345" do - # assert File.exist?("helloworld.exe") - # system("helloworld.exe 2>NUL") - # assert File.exist?("helloworld.exe") - # end - # end - # end - # end + # Should be able to build executables with LZMA compression + def test_lzma + with_fixture 'helloworld' do + assert system("ruby", ocra, "helloworld.rb", "--quiet", "--lzma") + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + + # Test that executables can writing a file to the current working + # directory. + def test_writefile + with_fixture 'writefile' do + assert system("ruby", ocra, "writefile.rb", *DefaultArgs) + assert File.exist?("output.txt") # Make sure ocra ran the script during build + pristine_env "writefile.exe" do + assert File.exist?("writefile.exe") + assert system("writefile.exe") + assert File.exist?("output.txt") + assert_equal "output", File.read("output.txt") + end + end + end + + # With --no-dep-run, ocra should not run script during build + def test_nodeprun + with_fixture 'writefile' do + assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--no-dep-run"])) + assert !File.exist?("output.txt") + pristine_env "writefile.exe" do + assert File.exist?("writefile.exe") + assert system("writefile.exe") + assert File.exist?("output.txt") + assert_equal "output", File.read("output.txt") + end + end + end + + # With dep run disabled but including all core libs, should be able + # to use ruby standard libraries (i.e. cgi) + def test_rubycoreincl + with_fixture 'rubycoreincl' do + assert system("ruby", ocra, "rubycoreincl.rb", *(DefaultArgs + ["--no-dep-run", "--add-all-core"])) + pristine_env "rubycoreincl.exe" do + assert File.exist?("rubycoreincl.exe") + assert system("rubycoreincl.exe") + assert File.exist?("output.txt") + assert_equal "3 < 5", File.read("output.txt") + end + end + end + + # With dep run disabled but including corelibs and using a Bundler Gemfile, specified gems should + # be automatically included and usable in packaged app + def test_gemfile + with_fixture 'bundlerusage' do + assert system("ruby", ocra, "bundlerusage.rb", "Gemfile", *(DefaultArgs + ["--no-dep-run", "--add-all-core", "--gemfile", "Gemfile", "--gem-all"])) + pristine_env "bundlerusage.exe" do + assert system("bundlerusage.exe") + end + end + end + + # With --debug-extract option, exe should unpack to local directory and leave it in place + def test_debug_extract + with_fixture 'helloworld' do + assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--debug-extract"])) + pristine_env "helloworld.exe" do + assert_equal 0, Dir["ocr*"].size + assert system("helloworld.exe") + assert_equal 1, Dir["ocr*"].size + end + end + end + + # Test that the --output option allows us to specify a different exe name + def test_output_option + with_fixture 'helloworld' do + assert system("ruby", ocra, "helloworld.rb", *(DefaultArgs + ["--output", "goodbyeworld.exe"])) + assert !File.exist?("helloworld.exe") + assert File.exist?("goodbyeworld.exe") + end + end + + # Test that we can specify a directory to be recursively included + def test_directory_on_cmd_line + with_fixture 'subdir' do + assert system("ruby", ocra, "subdir.rb", "a", *DefaultArgs) + pristine_env "subdir.exe" do + assert system("subdir.exe") + end + end + end + + # Test that scripts can exit with a specific exit status code. + def test_exitstatus + with_fixture 'exitstatus' do + assert system("ruby", ocra, "exitstatus.rb", *DefaultArgs) + pristine_env "exitstatus.exe" do + system("exitstatus.exe") + assert_equal 167, $?.exitstatus + end + end + end + + # Test that arguments are passed correctly to scripts. + def test_arguments1 + with_fixture 'arguments' do + assert system("ruby", ocra, "arguments.rb", *DefaultArgs) + assert File.exist?("arguments.exe") + pristine_env "arguments.exe" do + system("arguments.exe foo \"bar baz \\\"quote\\\"\"") + assert_equal 5, $?.exitstatus + end + end + end + + # Test that arguments are passed correctly to scripts (specified at + # compile time). + def test_arguments2 + with_fixture 'arguments' do + args = DefaultArgs + ["--", "foo", "bar baz \"quote\"" ] + assert system("ruby", ocra, "arguments.rb", *args) + assert File.exist?("arguments.exe") + pristine_env "arguments.exe" do + system("arguments.exe") + assert_equal 5, $?.exitstatus + end + end + end + + # Test that arguments are passed correctly to scripts (specified at + # compile time). + def test_arguments3 + with_fixture 'arguments' do + args = DefaultArgs + ["--", "foo"] + assert system("ruby", ocra, "arguments.rb", *args) + assert File.exist?("arguments.exe") + pristine_env "arguments.exe" do + system("arguments.exe \"bar baz \\\"quote\\\"\"") + assert_equal 5, $?.exitstatus + end + end + end + + # Test that arguments are passed correctly at build time. + def test_buildarg + with_fixture "buildarg" do + args = DefaultArgs + [ "--", "--some-option" ] + assert system("ruby", ocra, "buildarg.rb", *args) + assert File.exist?("buildarg.exe") + pristine_env "buildarg.exe" do + assert system("buildarg.exe") + end + end + end + + # Test that the standard output from a script can be redirected to a + # file. + def test_stdout_redir + with_fixture 'stdoutredir' do + assert system("ruby", ocra, "stdoutredir.rb", *DefaultArgs) + assert File.exist?("stdoutredir.exe") + pristine_env "stdoutredir.exe" do + system("stdoutredir.exe > output.txt") + assert File.exist?("output.txt") + assert_equal "Hello, World!\n", File.read("output.txt") + end + end + end + + # Test that the standard input to a script can be redirected from a + # file. + def test_stdin_redir + with_fixture 'stdinredir' do + assert system("ruby", ocra, "stdinredir.rb", *DefaultArgs) + assert File.exist?("stdinredir.exe") + # Kernel.system("ruby -e \"system 'stdinredir.exe '.' do + pristine_env "gdbmdll.exe" do + system("gdbmdll.exe") + assert_equal 104, $?.exitstatus + end + end + end + end + + # Test that scripts can require a file relative to the location of + # the script and that such files are correctly added to the + # executable. + def test_relative_require + with_fixture 'relativerequire' do + assert system("ruby", ocra, "relativerequire.rb", *DefaultArgs) + assert File.exist?("relativerequire.exe") + pristine_env "relativerequire.exe" do + system("relativerequire.exe") + assert_equal 160, $?.exitstatus + end + end + end + + # Test that autoloaded files which are not actually loaded while + # running the script through Ocra are included in the resulting + # executable. + def test_autoload + with_fixture 'autoload' do + assert system("ruby", ocra, "autoload.rb", *DefaultArgs) + assert File.exist?("autoload.exe") + pristine_env "autoload.exe" do + assert system("autoload.exe") + end + end + end + + # Test that autoload statement which point to non-existing files are + # ignored by Ocra (a warning may be logged). + def test_autoload_missing + with_fixture 'autoloadmissing' do + args = DefaultArgs.dup + args.push '--no-warnings' + assert system("ruby", ocra, "autoloadmissing.rb", *args) + assert File.exist?("autoloadmissing.exe") + pristine_env "autoloadmissing.exe" do + assert system("autoloadmissing.exe") + end + end + end + + # Test that Ocra picks up autoload statement nested in modules. + def test_autoload_nested + with_fixture 'autoloadnested' do + assert system("ruby", ocra, "autoloadnested.rb", *DefaultArgs) + assert File.exist?("autoloadnested.exe") + pristine_env "autoloadnested.exe" do + assert system("autoloadnested.exe") + end + end + end + + # Should find features via relative require paths, after script + # changes to the right directory (Only valid for Ruby < 1.9.2). + def test_relative_require_chdir_path + with_fixture "relloadpath" do + each_path_combo "bin/chdir1.rb" do |script| + assert system('ruby', ocra, script, *DefaultArgs) + assert File.exist?('chdir1.exe') + pristine_env "chdir1.exe" do + assert system('chdir1.exe') + end + end + end + end + + # Should find features via relative require paths prefixed with + # './', after script changes to the right directory. + def test_relative_require_chdir_dotpath + with_fixture "relloadpath" do + each_path_combo "bin/chdir2.rb" do |script| + assert system('ruby', ocra, script, *DefaultArgs) + assert File.exist?('chdir2.exe') + pristine_env "chdir2.exe" do + assert system('chdir2.exe') + end + end + end + end + + # Should pick up files from relative load paths specified using the + # -I option when invoking Ocra, and invoking from same directory as + # script. + def test_relative_require_i + with_fixture 'relloadpath' do + each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| + assert system('ruby', '-I', loadpaths[0], '-I', loadpaths[1], ocra, script, *DefaultArgs) + assert File.exist?('external.exe') + pristine_env "external.exe" do + assert system('external.exe') + end + end + end + end + + # Should pick up files from relative load path specified using the + # RUBYLIB environment variable. + def test_relative_require_rubylib + with_fixture 'relloadpath' do + each_path_combo "bin/external.rb", "lib", "bin/sub" do |script, *loadpaths| + with_env 'RUBYLIB' => loadpaths.join(';') do + assert system('ruby', ocra, script, *DefaultArgs) + end + assert File.exist?('external.exe') + pristine_env "external.exe" do + assert system('external.exe') + end + end + end + end + + # Should pick up file when script modifies $LOAD_PATH by adding + # dirname of script. + def test_loadpath_mangling_dirname + with_fixture 'relloadpath' do + each_path_combo "bin/loadpath0.rb" do |script| + assert system('ruby', ocra, script, *DefaultArgs) + assert File.exist?('loadpath0.exe') + pristine_env "loadpath0.exe" do + assert system('loadpath0.exe') + end + end + end + end + + # Should pick up file when script modifies $LOAD_PATH by adding + # relative paths, and invoking from same directory. + def test_loadpath_mangling_path + with_fixture 'relloadpath' do + each_path_combo "bin/loadpath1.rb" do |script| + assert system('ruby', ocra, script, *DefaultArgs) + assert File.exist?('loadpath1.exe') + pristine_env "loadpath1.exe" do + assert system('loadpath1.exe') + end + end + end + end + + # Should pick up file when script modifies $LOAD_PATH by adding + # relative paths with './'-prefix + def test_loadpath_mangling_dotpath + with_fixture 'relloadpath' do + each_path_combo "bin/loadpath2.rb" do |script| + assert system('ruby', ocra, script, *DefaultArgs) + assert File.exist?('loadpath2.exe') + pristine_env "loadpath2.exe" do + assert system('loadpath2.exe') + end + end + end + end + + # Should pick up file when script modifies $LOAD_PATH by adding + # absolute paths. + def test_loadpath_mangling_abspath + with_fixture 'relloadpath' do + each_path_combo "bin/loadpath3.rb" do |script| + assert system('ruby', ocra, script, *DefaultArgs) + assert File.exist?('loadpath3.exe') + pristine_env "loadpath3.exe" do + assert system('loadpath3.exe') + end + end + end + end + + # Test that ocra.rb accepts --version and outputs the version number. + def test_version + assert_match(/^Ocra \d+(\.\d)+(.[a-z]+\d+)?$/, `ruby \"#{ocra}\" --version`) + end + + # Test that ocra.rb accepts --icon. + def test_icon + with_fixture 'helloworld' do + icofile = File.join(OcraRoot, 'src', 'vit-ruby.ico') + assert system("ruby", ocra, '--icon', icofile, "helloworld.rb", *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + + # Test that additional non-script files can be added to the + # executable and used by the script. + def test_resource + with_fixture 'resource' do + assert system("ruby", ocra, "resource.rb", "resource.txt", "res/resource.txt", *DefaultArgs) + assert File.exist?("resource.exe") + pristine_env "resource.exe" do + assert system("resource.exe") + end + end + end + + # Test that when exceptions are thrown, no executable will be built. + def test_exception + with_fixture 'exception' do + system("ruby \"#{ocra}\" exception.rb #{DefaultArgs.join(' ')} 2>NUL") + assert $?.exitstatus != 0 + assert !File.exist?("exception.exe") + end + end + + # Test that the RUBYOPT environment variable is preserved. + def test_rubyopt + with_fixture 'environment' do + with_env "RUBYOPT" => "-rtime" do + assert system("ruby", ocra, "environment.rb", *DefaultArgs) + pristine_env "environment.exe" do + assert system("environment.exe") + env = Marshal.load(File.open("environment", "rb") { |f| f.read }) + assert_equal "-rtime", env['RUBYOPT'] + end + end + end + end + + def test_exit + with_fixture 'exit' do + assert system("ruby", ocra, "exit.rb", *DefaultArgs) + pristine_env "exit.exe" do + assert File.exist?("exit.exe") + assert system("exit.exe") + end + end + end + + def test_ocra_executable_env + with_fixture 'environment' do + assert system("ruby", ocra, "environment.rb", *DefaultArgs) + pristine_env "environment.exe" do + assert system("environment.exe") + env = Marshal.load(File.open("environment", "rb") { |f| f.read }) + expected_path = File.expand_path("environment.exe").tr('/','\\') + assert_equal expected_path, env['OCRA_EXECUTABLE'] + end + end + end + + def test_hierarchy + with_fixture 'hierarchy' do + assert system("ruby", ocra, "hierarchy.rb", "assets/**/*", *DefaultArgs) + pristine_env "hierarchy.exe" do + assert system("hierarchy.exe") + end + end + end + + def test_temp_with_space + with_fixture 'helloworld' do + assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) + tempdir = File.expand_path("temporary directory") + mkdir_p tempdir + pristine_env "helloworld.exe" do + with_env "TMP" => tempdir.tr('/','\\') do + assert system("helloworld.exe") + end + end + end + end + + # Should be able to build executable when specifying absolute path + # to the script from somewhere else. + def test_abspath + with_fixture "helloworld" do + script_path = File.expand_path("helloworld.rb") + with_tmpdir do + assert system("ruby", ocra, script_path, *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + end + + def test_abspath_outside + with_fixture "helloworld" do + mkdir "build" + cd "build" do + assert system("ruby", ocra, File.expand_path("../helloworld.rb"), *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + end + + def test_relpath + with_fixture "helloworld" do + assert system("ruby", ocra, "./helloworld.rb", *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + + def test_relpath_outside + with_fixture "helloworld" do + mkdir "build" + cd "build" do + assert system("ruby", ocra, "../helloworld.rb", *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + end + + # Should accept hierachical source code layout + def test_srcroot + with_fixture "srcroot" do + assert system("ruby", ocra, "bin/srcroot.rb", "share/data.txt", *DefaultArgs) + assert File.exist?("srcroot.exe") + pristine_env "srcroot.exe" do + exe = File.expand_path("srcroot.exe") + cd ENV["SystemRoot"] do + assert system(exe) + end + end + end + end + + # Should be able to build executables when script changes directory. + def test_chdir + with_fixture "chdir" do + assert system("ruby", ocra, "chdir.rb", *DefaultArgs) + assert File.exist?("chdir.exe") + pristine_env "chdir.exe" do + exe = File.expand_path("chdir.exe") + cd ENV["SystemRoot"] do + assert system(exe) + end + end + end + end + + # Test that the --chdir-first option changes directory before exe starts script + def test_chdir_first + with_fixture 'writefile' do + # Control test; make sure the writefile script works as expected under default options + assert system("ruby", ocra, "writefile.rb", *(DefaultArgs)) + pristine_env "writefile.exe" do + assert !File.exist?("output.txt") + assert system("writefile.exe") + assert File.exist?("output.txt") + end + + assert system("ruby", ocra, "writefile.rb", *(DefaultArgs + ["--chdir-first"])) + pristine_env "writefile.exe" do + assert !File.exist?("output.txt") + assert system("writefile.exe") + # If the script ran in its inst directory, then our working dir still shouldn't have any output.txt + assert !File.exist?("output.txt") + end + end + end + + # Would be nice if OCRA could build from source located beneath the + # Ruby installation too. + def test_exec_prefix + path = File.join(RbConfig::CONFIG["exec_prefix"], "ocratempsrc") + with_fixture "helloworld", path do + assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + assert system("helloworld.exe") + end + end + end + + def test_explicit_in_exec_prefix + return unless File.directory?(RbConfig::CONFIG["exec_prefix"] + "/include") + path = File.join(RbConfig::CONFIG["exec_prefix"], "include", "**", "*.h") + number_of_files = Dir[path].size + assert number_of_files > 3 + with_fixture "check_includes" do + assert system("ruby", ocra, "check_includes.rb", path, *DefaultArgs) + assert File.exist?("check_includes.exe") + pristine_env "check_includes.exe" do + assert system("check_includes.exe", number_of_files.to_s) + end + end + end # Hello world test. Test that we can build and run executables. + def test_nonexistent_temp + with_fixture 'helloworld' do + assert system("ruby", ocra, "helloworld.rb", *DefaultArgs) + assert File.exist?("helloworld.exe") + pristine_env "helloworld.exe" do + with_env "TEMP" => "c:\\thispathdoesnotexist12345", "TMP" => "c:\\thispathdoesnotexist12345" do + assert File.exist?("helloworld.exe") + system("helloworld.exe 2>NUL") + assert File.exist?("helloworld.exe") + end + end + end + end + + # Test that code-signed executables still work def test_codesigning_support with_fixture 'helloworld' do each_path_combo "helloworld.rb" do |script| assert system("ruby", ocra, script, *DefaultArgs) - FakeCodeSigner.new(input: "helloworld.exe", - output: "helloworld-signed.exe").sign + FakeCodeSigner.new(input: "helloworld.exe", + output: "helloworld-signed.exe", + padding: rand(20)).sign pristine_env "helloworld.exe", "helloworld-signed.exe" do assert system("helloworld.exe") From 988ee441224c2cd033567d3069923a8276259f61 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 03:39:56 +0100 Subject: [PATCH 31/43] DATA_DIRECTORY_ENTRY_SIZE is 8 (2 DWORDs) not 4 --- test/fake_code_signer/pe_header.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fake_code_signer/pe_header.rb b/test/fake_code_signer/pe_header.rb index c817dab2..51a5f939 100644 --- a/test/fake_code_signer/pe_header.rb +++ b/test/fake_code_signer/pe_header.rb @@ -27,7 +27,7 @@ class PEHeader # struct IMAGE_DATA_DIRECTORY # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx - DATA_DIRECTORY_ENTRY_SIZE = 4 + DATA_DIRECTORY_ENTRY_SIZE = 8 def initialize(image) @image = image From cf62394e4ccbb5c5ee489d194333219bc1956581 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 04:10:45 +0100 Subject: [PATCH 32/43] Remove changes friendly to local development --- Rakefile | 17 ----------------- bin/ocra | 2 +- hello.rb | 1 - lib/ocra.rb | 2 +- 4 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 hello.rb diff --git a/Rakefile b/Rakefile index ff866fe2..ddf4a9a5 100644 --- a/Rakefile +++ b/Rakefile @@ -12,23 +12,6 @@ end spec.urls.each { |url| url.chomp! } -desc "task for fast iteration" -task :do_it do - sh "gem uninstall ocra" - sh "rake install_gem" rescue nil - puts "installing the bitch" - sh "gem install pkg/ocra-1.4.666.gem" - sh "rm hello.exe" rescue nil - sh "ocra hello.rb" - sh "cp hello.exe hello-signed.exe" - puts "Now creating a signed copy in hello-signed.exe" - sh "signtool sign /f ./CARoot.pfx /p Test123 hello-signed.exe" - puts "output of hello.exe" - sh "./hello.exe" - puts "output of hello-signed.exe" - sh "./hello-signed.exe" rescue nil -end - task :build_stub do sh "mingw32-make -C src" cp 'src/stub.exe', 'share/ocra/stub.exe' diff --git a/bin/ocra b/bin/ocra index c51754be..81743cdb 100644 --- a/bin/ocra +++ b/bin/ocra @@ -179,7 +179,7 @@ module Ocra a.sort.inject([]) { |r, e| r.last == e ? r : r << e } end - VERSION = "1.4.666" + VERSION = "1.3.10" IGNORE_MODULES = /\/(enumerator.so|rational.so|complex.so|thread.rb)$/ diff --git a/hello.rb b/hello.rb deleted file mode 100644 index 5e3c993f..00000000 --- a/hello.rb +++ /dev/null @@ -1 +0,0 @@ -puts "hello world!" diff --git a/lib/ocra.rb b/lib/ocra.rb index 43ec09e9..f63a0ba3 100644 --- a/lib/ocra.rb +++ b/lib/ocra.rb @@ -1,3 +1,3 @@ class Ocra - VERSION = '1.4.666' + VERSION = '1.3.10' end From 05568b081d095d8229d7662c2d7262685ca6a457 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 04:13:26 +0100 Subject: [PATCH 33/43] Remove more unnecessary files --- CARoot.pfx | Bin 4230 -> 0 bytes ocra_code_signing_and_windows.md | 157 ------------------------------- 2 files changed, 157 deletions(-) delete mode 100644 CARoot.pfx delete mode 100644 ocra_code_signing_and_windows.md diff --git a/CARoot.pfx b/CARoot.pfx deleted file mode 100644 index b49907e276dd872a3ba43d04d37a068c111662ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4230 zcmZWrcTf}VvP~caX`zP}L+>C2=~XEbX%djC^rm!z2*J<-QlukAnsiW#KqvwNA|Sm= zHAqzulwPF>H@`dY-8*mI?CiH^&z{}=@0*px(cwsdq>?x~RWOA}Br@`Z21pLf$I(fE zaCD+XT2K;48U24slo$|>(vL{{5Zwbz^*>$Ilt7Yv97V7sjv`PJ2B!R9nm8N+x<;?f zmQnUoMVf?!d<=x6FmL+Nf=Ym(o&Q8N4$jDJH*acV*KNB8F2AhWtkh!n9IFs@Fevny zNZ9frAUUmF6YGe7#*z0z$D|@I-o)Q?G|Nf+Tb-M z%AL3l4+6ue6MXxc0|YYyJ9l?nJY7ff+8w5`p#4t_-`|u?d}~Gm#}3(baa*UU*iLt% zbaFZ6?qkEI>CG+jWX|^Q{g!#33gJqcQ4Z>EdQ}s`QqcfC=_0)c6uKWez633IGPP_u zL+e{0x1z>G<`CMtRbsR>Q{h2yIo1*TiQES&sqY#~Ubn4-m0yXwT}^BF1MlenQcpbK zOY|1sua9WE(bB*0BKgF|RV-w7d=b~$&vo&U8G-b|4dL!3LOCArd^mKVD=g#7h!Q;r zBbZirc}{F|Te$0MVCvHwD>p8FYB5J|fEr%BTnedwvX-(KRxd;0RwPNwM|Zq~%9eCj z6PQO|gYO$Ny>~FN^`?5SZk}o@iUxV#Y!ofz^*yrEZANW= zR(|ERrRBpsUVw2HO4{z%L;`x5{XIjDa4v z>3sKo>@bj^Jq_Ha7g_^#%$~}V_-rr+=>$R#9vMmvsh~$>d9V8(4v}U(z~U1IIavv*o7L*~bC`Yk*E$FViG12C{8S^DTd{1; z#RDlYeY=^Z+CsHD6|ws_9+j^hP6+&LmyL0MHx6Zqz0Mbu9ZmDW0$&PF{TzLGDlQAQ zt@avY7d-QsFp^ID*59ibONud)S~7Rtl1aDg8nvHyijdxE@%#OAkn^ohi}AwEuub;U z-W5g+sW;i`9IbtU0n~v`x4KIE(CsQTCqhk~gllw-QZIfkB0GKZ4qf+wZHC?Df-&2I zE$-=&Lx45<$;Irlaq3n27yJzl6mqwg^VyJ+IU?;?9Ef!5ShIDI!GDVgen5wkxsc%q zzTO~>cM<;dHbm7;04CsNhV7P8!3%EasceAYS=H`3Ik&@30)J+?DBalSEZdtYPKCRP zZj;-YO^VyHeHi^+Ay4c7(0G%3Xjre0yfhXQ_e6}VQ~#t zfD{Ua!=WiQSCOIA+TCKYhCl|r&lrqs+VAfr%XvkPb>`g0p*cg`DJ#^N`WlI)Oxe!h z2)9C0@0~4WX2al>?o0)Jk<8Qb9=mTSl(rxBIIOE`l2ublMPEn>wRpl;z}(2nz3Wat za~q#_0;MO5vk$0ND*BlbQAn7a$o{1b!7FOYq_0m~WwWU74WnEf7|j)zp4g z@hhc>|J_!_qPF%D^YD<_!B9$i8hGA4@4{?MHSwazl|)K}TM+7QMhsTfyZ#Vu%*oPoL(MT9HF&;|Jb$R{Q%|}^bQ}Hd$#m;ee1XB&R&C}ng z`CC2iP;baw1epXwW&aX*g$+wuAyX{}cvpWZ(1tnnUT(60-}QyzqZ_^|d1yvd`7h@P z5rzVGipdM_p03Zhbz#3zJuo2v)8dI8B=!WXr+-48DQpuQ9tvs|`n;RjlqNhaX#`vebx zXdP~><+9|}J&RpsJ+Yah;MXbM({cSNJnho^HDe4s8CJ#;D&mX-fA2)bk4Is8j=rSU zzn*<}@yqxiIsHp2Pp*HQwH}oM7X{m^V#GuC5mWalhH|Hjwp0`wo>Cgc8u&6q?~!Oq zEd{)Lkf&}kyGW+jr@BWVx)Y$Q)y?V`1NBE~)wrb9UT4~X zjSI!QZMsin1skqKJIajHWJ~CASyIR02pc^XR5a(Jsr~}vQUrSR&1F?&2XNU=)6>oM z{%e}$L*Z6H)ZN`O(GndGUIEbZD8qW_bEqMQnIY{ycwS7~ky?|t0B8^wSSi&hzN}4{ zr{TMu>wHglhDML6*M?@@^scTW$!V6tzL?DM#%O?k3)2RIg|(Cv{f&3!F=n@{;P#L5 z38tmW%xKwm3B`}qF5&wH1%!O;a30mp6LiSvR>{P@R{W^J=T2(}T|bgt)?ynhHIEku zLm1+OA(3=q%E;9H_9NH=yYEWFA4~MD^nA@Ct?Ne)bA5v^ISM*TeE0=@Nl!#Ad3g+pAhmcigN74-9|OYO z(YVwhzwNODlA-1hKY3AU_no^c-$DJ;#h;hhOh>WC66X^x`0%6Evg1vcNn9%qKmhCkvH*D^k|stufC3TO z6Y)PPaiYr*83a*lM^xJr4M8+{NgP?me-@F0^KoR+L@I&=2q1=_|5kwi{{Wpo$w=Zk zOUeA_01d^F8TMtA;!AjA>3p1&*+j`>!=Wn?l`2AlQzqGGIpbuX0_VB!?o}suX)^Xm z5MY94?A@q@wU{gEHa!4&A4CR$wk;Tdr+U(3Sm}jA-lwJ1bRPsb$RFZ4_M$H3KitZ5*(nJ z&ri}w@e}-!S!6K@JpNEXlXh153E)``rxhIVC=FKMxUA zKC>xd@Dlw{0K7J+BBuIgKdAnMoT*~r{Oe9JCKq<;)6V8LgbP}ihs)~9Y-{oxlv7WD zdhVQV#8irzw@2hfWl|;CEA}55Z|1tGjl1&njw*Qi%VALGM-cWuT}2ycPTV09t>eb= zK7O`b^pbZY9*e*l&iE+hOs)F;&4;PYU5G*2QqIa2@0v9|n0on}g?=WCx%Rrg7#bMC zLn-&WO=GLZIx3gNPXE)fl$Xmnso(s4fN&=T32!HR1}sm;>T>}_Wu&fxeq9&S&Pb(j z2~-VURE%tE(~@wYu01x(3PWQA2}sXO{dkoH;PX^{tvb+vp2GVR29A3+%bB8#4qA=i z?FT|yq+GmpDaz5@6^peI&!i_)FXzquw6W-#7}t93#U>A5CIxhNH+FKj6jLe&%$zUc z{KIEj9+_jfU?eMK`|OUlwTE4`2pwL}fJ!Aj^Na1s!<15q206;rC26m}IN#%$RRwjo zt6jT_YL2Y*{FD~BFdV+i)+?oHi>z0x9-4E;q-!u;C16KaV3tktnb5pYfsak&6n)F6 zt`*FRm{T!b2K&uuK0%+J+rO-bGq|NS>9Up*s@N9PH68|J?W$6cdBr7vgZ@@>CKtPv zSlE$Gu-7!Gc`BWj)*eV-M30H}ZMb+h;SjASv|M$q0$DDDI>%I#RGF(jRvTW^M17n< zJ$4ff>;Uo_h>!=y&Zcs20^YXTzH7S^6GHOYYEg?Ov6?hDzb13wM}H7)Ud%e#z{CJU zVpM~PMnX473P%NFfoI1oj&tEF#3D_{+0G!izFXU5`gzw0EGV4aRj=r}_bdQuJFDtm zQz;&gs_#Fk#a0LJu6<>h#`l?i`9E|O^`{`ufrzdgr98hI2}NkJS9`Q{Go=Q6du8LoBMzn z_t^&;%Ls4tA6J7zr3wTcA9IW0FGB;*2$g zoZ;Bit@anDHB*<^!`&`EEzj3pQgWd$hAILv=lqY$?mn(Pa5teeZpy1{^sM__vSFX3 zWxF;L6AVY5Lu`d0YUUDr@q{Srkq?%Y3a8Q^E4INu9^U3=jNmlk1mr?Avf4vJy}Oe+ z{G=ZZOZkuvjZ#5wRUcS|N-9ZmN|J-gL?NUk>>#E#f6p{jWL%obVpmVsaE~T?w@4?5 csbtUGegfs`amqby`7)h^jDO=I?4J+$U&82}=Kufz diff --git a/ocra_code_signing_and_windows.md b/ocra_code_signing_and_windows.md deleted file mode 100644 index 2c2234eb..00000000 --- a/ocra_code_signing_and_windows.md +++ /dev/null @@ -1,157 +0,0 @@ -Ocra & Code signing in Windows -=============== - -In order to distribute our app on Windows we use the [Ocra](https://github.com/larsch/ocra) project to bundle our Ruby source files together with the Ruby interpreter and libraries to form a single executable. - -We also want to digitally sign our files. [Code signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) achieves two things: (1) It establishes our identity as a developer and (2) It ensures integrity of our code (ensuring that it hasn't -been tampered with). Also, since it establishes us as a 'trusted developer' it reduces interference from Anti-Virus software and so on. - -Unfortunately, Ocra executables are broken by the code signing process and this has prevented us from signing our app until now. - -In this document I'll describe how Ocra works, how code signing in Windows works and finally an approach to get code signing working with Ocra. - -(TL;DR given at the end of the document). - -## How Ocra Works - -At a high level Ocra works by writing custom data after the end of the executable image -- and then reading this into memory at runtime. The custom data contains "opcodes" and embedded Ruby source files. The opcodes contain instructions like "create temp directory", "create file" (i.e extract embedded Ruby file to a temp folder on disk), "create process" and so on. It is this runtime execution of opcodes that allow the magic of Ocra to happen. - -### Building the executable - -It's a [known trick](https://edn.embarcadero.com/article/27979) that data written past the end of a Windows executable will be ignored by the Kernel, and Ocra takes advantage of this to store Ruby files as well as instructions for how to extract and run those Ruby files. - -In more detail, Ocra does the following when building an executable for a Ruby app: - -* First, the [stub.c](https://github.com/larsch/ocra/blob/master/src/stub.c) file is compiled into a `stub.exe`. This program only does two things -- it maps itself into memory and then -executes the opcodes stored in its 'custom data' section. -* Ocra then creates the output executable (e.g `hello_world.exe`) and [writes `stub.exe`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1021-L1032) to the start of the file. -* The ["opcodes"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1009-L1016) are appended to the end of the output executable. The Ruby source files (and Ruby interpreter binary itself) are embedded as long string parameters associated with the opcodes. -* For example, the Ruby interpreter (`ruby.exe`) is [embedded as one long string](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L901) as the [first parameter](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1161) to the `OP_CREATEFILE` opcode. -* After writing the opcodes, and [a final `OP_END`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1076) instruction that indicates the end of the opcodes, Ocra -writes an [`opcodes_offset`](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1077) value. This value is a relative offset to the start of the opcodes that tells the -program where to start processing. -* Finally, after the offset, are the [last 4 bytes of the file](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/bin/ocra#L1078). These bytes form Ocra's own "signature" and they are a [hard-coded value](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L13). This signature is **not** the same as the "digital signature" inserted by code signing and likely exists to check the executable has not been corrupted. - -After Ocra has finished building the output executable, the file will look like the following: - -``` -| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | -``` - -To reiterate, only the `stub.exe` content is used by the Kernel, everything else is ignored. The opcodes and so on are only -accessed by the program itself once it is mapped into memory. - -### Running the executable - -Once we have an output executable with the structure shown in the previous diagram, Ocra can execute it. When the executable -is run the process is as follows: - -* It opens its associated file on disk for reading and [maps itself into memory](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L309) -* It navigates to the end of the mapping and examines the [final 4 bytes](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L375-L376) -- these bytes are the "Ocra signature" -* It verifies that these bytes are equal to the [hard-coded signature](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L13) (if they are not then it will exit with a ["Bad Executable"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L385) error message) -* Once the signature is validated, it reads the [4 bytes prior to the signature](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L379) -- these bytes are the `opcodes_offset` -* It [start processing](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L381) the opcodes from the `opcodes_offset` until the [`OP_END` instruction is reached](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L678). It processes the opcodes by looking up the [associated function pointer](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L72-L87) for that opcode and executing it. - -These opcodes include instructions such as ["create a temporary file"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L469-L506) (i.e extract an embedded Ruby source file to disk) and ["create and run a process"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L604-L623) (i.e run the Ruby source file with the embedded Ruby interpreter binary). - -As is clear from above, the Ocra binary is quite a fragile thing. If the last 4 bytes are not the hard-coded signature it expects, then it will error out. - -## How Code Signing Works - -A signed file is a standard Windows executable with signature data appended -to it. A standard Windows executable file is known as a "PE" file ([Portable Execution format](https://en.wikipedia.org/wiki/Portable_Executable)). - -The structure of a PE file is as follows (image taken from https://en.wikipedia.org/wiki/Portable_Executable): - -![PE Format](https://www.dropbox.com/s/mx1pz6em1slzdyt/Screenshot%202017-12-12%2019.26.03.png?raw=1) - -From the diagram we see the format starts with a legacy MSDOS header. This header is represented by the [IMAGE_DOS_HEADER struct](https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html) - which has a member called `e_lfanew` which stands for "long file address for the New Executable" and it contains the offset (known as an RVA - Relative Virtual Address) to the start of the PE header proper. - -From this offset we get to the the PE header (also known as an `NT header` or `COFF header`) and it is a structure of type [IMAGE_NT_HEADERS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx). It is followed by another header which contains information on executables (referred to as an [optional header](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx), but it's always present for executables). This header defines [a number of data directories](http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm). - -One of these directory entries, `IMAGE_DIRECTORY_ENTRY_SECURITY`, is used to store [information](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx) about the -signature. Specifically it stores the location of the signature in the file (in the `VirtualAddress` field) and also its size (in the `Size` field). - -The digital signature itself is just a hash of the file that is encrypted with the developer's private key - and it is appended to the end of the executable. - -Because the signature appears after the EOF of the proper executable it is not treated as "executable code" (recall the [custom data](https://edn.embarcadero.com/article/27979) trick). - -To summarize, after code signing, an executable will change in the following way: - -* The `IMAGE_DIRECTORY_ENTRY_SECURITY` element in the PE header will have its `Size` field changed from zero (as is the case of an unsigned PE) to the size of the digital signature. -* The `VirtualAddress` field will be updated to point to the location of the digital signature. -* The digital signature will be appended to the executable. - -## Why Code Signing Breaks Ocra Executables - -Ocra expects the last 4 bytes of the executable to be its own hard-coded signature. However, since the Code Signing process appends the digital signature to the end of the executable this is no longer true. As a result attempting to run a Code Signed Ocra -executable will result in the ["Bad Signature in Executable"](https://github.com/larsch/ocra/blob/9c5fe287887f16db0c00ad54a549ff383f4c8d91/src/stub.c#L385) error message. - -## Suggested Fix - -The proposed fix is conceptually quite simple: we teach Ocra to know when it's been signed and to look in a different place for its expected data (its own signature and its `opcodes_offset`, etc). - -This means Ocra now has two code paths: - -### Unsigned codepath - -(`IMAGE_DIRECTORY_ENTRY_SECURITY.Size == 0`) - -The executable layout looks exactly as Ocra expects, nothing changes: - -`| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature |` - -### Signed codepath - -(`IMAGE_DIRECTORY_ENTRY_SECURITY.Size != 0`) - -The layout has changed slightly, we have a digital signature following the ocra signature: - -`| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | dig sig |` - -We must look at the `IMAGE_DIRECTORY_ENTRY_SECURITY.VirtualAddress` header to locate the starting byte of the digital signature. This starting byte will be immediately after the Ocra signature so we tell -Ocra to go there for the Ocra signature instead of the end of the file, as it did previously. From there -Ocra is able to find the Ocra signature and also the `opcodes_offset` field (which appears immediately before) and start processing opcodes. - -### Issues - -Unfortunately, the Code Signing process does not append the Digital Signature immediately after the Ocra Signature. There are usually a few NUL bytes in between the two. - -These NUL bytes are likely for alignment reasons that are not documented. Nonetheless, doing a small search back from the `VirtualAddress` (start of Digital Signature) until we find the -first non-NUL bytes appears to be an effective workaround and I have not noticed any problems with this approach on multiple test files. - -Finally, we need to ensure the last byte of the Ocra signature is not a NUL byte. However, since the Ocra signature is hard-coded and completely under our control this is a non-issue. - -## Conclusion - -I've tested the above approach on a number of files (including our code base) and it has worked successfully. - -I recommend we open a PR upstream and get the changes merged in as other developer's are likely to also want -Ocra Code Signing support. - -## TL;DR - -Ocra expects the executable to look like this: - -``` -| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | -``` - -But, after Code Signing it looks like this instead: - -``` -| stub.exe content | opcodes | op_end opcode | opcodes offset | ocra signature | dig sig | -``` - -This breaks Ocra and prevents Ocra executables working after they've been signed. - -The proposed fix is to update [stub.c](https://github.com/larsch/ocra/blob/master/src/stub.c) to use the executable headers to locate where the Digital Signature starts and tell Ocra to look for its data backwards from there instead of from the end of the file. - -## Resources - -* [Peering Inside the PE](https://msdn.microsoft.com/en-us/library/ms809762.aspx) -* [Authenticode Reverse-engineered](https://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt) -* [Winnt.h](http://www.rensselaer.org/dept/cis/software/g77-mingw32/include/winnt.h) -* [PE Format (MSDN)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx) -* [Portable Execution Format Wikipedia Page](https://en.wikipedia.org/wiki/Portable_Executable) -* [Introduction To Code Signing](https://msdn.microsoft.com/en-us/library/ms537361(v=vs.85).aspx) From 4005673549b4417f2623aebb45cb75b1b9d139b0 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 04:17:56 +0100 Subject: [PATCH 34/43] Revert to original spacing (3 not 2) --- src/stub.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/stub.c b/src/stub.c index 54995f39..09a23247 100644 --- a/src/stub.c +++ b/src/stub.c @@ -424,19 +424,19 @@ static LPVOID ocraSignatureLocation(LPVOID ptr, DWORD size) { */ BOOL ProcessImage(LPVOID ptr, DWORD size) { - LPVOID pSig = ocraSignatureLocation(ptr, size); + LPVOID pSig = ocraSignatureLocation(ptr, size); - if (memcmp(pSig, Signature, 4) == 0) + if (memcmp(pSig, Signature, 4) == 0) { - DEBUG("Good signature found."); - DWORD OpcodeOffset = *(DWORD*)(pSig - 4); - LPVOID pSeg = ptr + OpcodeOffset; - return ProcessOpcodes(&pSeg); + DEBUG("Good signature found."); + DWORD OpcodeOffset = *(DWORD*)(pSig - 4); + LPVOID pSeg = ptr + OpcodeOffset; + return ProcessOpcodes(&pSeg); } else { - FATAL("Bad signature in executable."); - return FALSE; + FATAL("Bad signature in executable."); + return FALSE; } } From d9f208b9fe10d716bdc559921aa067b3a28d92d0 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 04:48:10 +0100 Subject: [PATCH 35/43] Add more comments to fake_code_signer.rb --- test/fake_code_signer.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index 97f5b7c1..a166a083 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -32,9 +32,17 @@ def sign raise "input and output files must be different!" end + # Below we access an instance of the IMAGE_DATA_DIRECTORY struct. + # This instance is called IMAGE_DIRECTORY_ENTRY_SECURITY and it contains information about the digital signature + # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx + + # write the offset (address) of the digital signature to the security header (VirtualAddress field) @image[pe_header.security_offset, 4] = raw_bytes(@image.size + @padding) + # write the size of the digital signature to the security header (Size field) @image[pe_header.security_offset + 4, 4] = raw_bytes(FAKE_SIG.size) + + # append the "digital signature" to the end of the executable, complete with padding @image << padding_string << FAKE_SIG File.binwrite(@output, @image) From 32b7d2e3b81e54563fd902d640445b4587b08c53 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 05:40:57 +0100 Subject: [PATCH 36/43] use DWORD_SIZE in more places --- test/fake_code_signer/pe_header.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fake_code_signer/pe_header.rb b/test/fake_code_signer/pe_header.rb index 51a5f939..0d19908f 100644 --- a/test/fake_code_signer/pe_header.rb +++ b/test/fake_code_signer/pe_header.rb @@ -46,7 +46,7 @@ def security_address # size of the digital signature def security_size - deref(security_offset + 4) + deref(security_offset + DWORD_SIZE) end private @@ -54,7 +54,7 @@ def security_size # dereferences a pointer # the only pointer type we support is an unsigned long (DWORD) def deref(ptr) - @image[ptr, 4].unpack("L").first + @image[ptr, DWORD_SIZE].unpack("L").first end # offset of e_lfanew (which stores the offset of the actual PE header) From 43590f51ed018adcb3cec150bcc3d2b9da1261af Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 05:52:31 +0100 Subject: [PATCH 37/43] add explicit offset methods --- test/fake_code_signer.rb | 8 +++----- test/fake_code_signer/pe_header.rb | 10 ++++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index a166a083..2e817e9c 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -26,10 +26,8 @@ def initialize(input:, output:, padding: 4) end def sign - if pe_header.security_size !=0 + if pe_header.security_size != 0 raise "Binary already signed, nothing to do!" - elsif @input == @output - raise "input and output files must be different!" end # Below we access an instance of the IMAGE_DATA_DIRECTORY struct. @@ -37,10 +35,10 @@ def sign # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx # write the offset (address) of the digital signature to the security header (VirtualAddress field) - @image[pe_header.security_offset, 4] = raw_bytes(@image.size + @padding) + @image[pe_header.security_address_offset, PEHeader::DWORD_SIZE] = raw_bytes(@image.size + @padding) # write the size of the digital signature to the security header (Size field) - @image[pe_header.security_offset + 4, 4] = raw_bytes(FAKE_SIG.size) + @image[pe_header.security_size_offset, PEHeader::DWORD_SIZE] = raw_bytes(FAKE_SIG.size) # append the "digital signature" to the end of the executable, complete with padding @image << padding_string << FAKE_SIG diff --git a/test/fake_code_signer/pe_header.rb b/test/fake_code_signer/pe_header.rb index 0d19908f..29445f81 100644 --- a/test/fake_code_signer/pe_header.rb +++ b/test/fake_code_signer/pe_header.rb @@ -39,14 +39,20 @@ def security_offset image_data_directory_offset + DATA_DIRECTORY_ENTRY_SIZE * 4 end + alias security_address_offset security_offset + + def security_size_offset + security_offset + DWORD_SIZE + end + # location of the digital signature def security_address - deref(security_offset) + deref(security_address_offset) end # size of the digital signature def security_size - deref(security_offset + DWORD_SIZE) + deref(security_size_offset) end private From 5997c12c07d6ac03c56e9fcc2a161c8aa849ff40 Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 07:51:58 +0100 Subject: [PATCH 38/43] Rename pe_header -> pe_wrapper as it now does more than just manages the header, it also appends data --- test/fake_code_signer.rb | 22 +++++----- .../{pe_header.rb => pe_wrapper.rb} | 40 ++++++++++++++----- 2 files changed, 39 insertions(+), 23 deletions(-) rename test/fake_code_signer/{pe_header.rb => pe_wrapper.rb} (83%) diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index 2e817e9c..59cd085f 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -1,4 +1,4 @@ -require File.join File.dirname(__FILE__), "fake_code_signer/pe_header" +require File.join File.dirname(__FILE__), "fake_code_signer/pe_wrapper" # This class digitally signs an executable. # In reality, it doesn't create a "valid" digital signature - but it sets up the executable @@ -26,22 +26,22 @@ def initialize(input:, output:, padding: 4) end def sign - if pe_header.security_size != 0 + if pe_wrapper.security_size != 0 raise "Binary already signed, nothing to do!" end # Below we access an instance of the IMAGE_DATA_DIRECTORY struct. # This instance is called IMAGE_DIRECTORY_ENTRY_SECURITY and it contains information about the digital signature # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx - + # write the offset (address) of the digital signature to the security header (VirtualAddress field) - @image[pe_header.security_address_offset, PEHeader::DWORD_SIZE] = raw_bytes(@image.size + @padding) + pe_wrapper.security_address = @image.size + @padding # write the size of the digital signature to the security header (Size field) - @image[pe_header.security_size_offset, PEHeader::DWORD_SIZE] = raw_bytes(FAKE_SIG.size) - + pe_wrapper.security_size = FAKE_SIG.size + # append the "digital signature" to the end of the executable, complete with padding - @image << padding_string << FAKE_SIG + pe_wrapper.append_data(padding_string + FAKE_SIG) File.binwrite(@output, @image) end @@ -52,11 +52,7 @@ def padding_string "\x0" * @padding end - def raw_bytes(int) - [int].pack("L") - end - - def pe_header - @pe_header ||= PEHeader.new(@image) + def pe_wrapper + @pe_wrapper ||= PEWrapper.new(@image) end end diff --git a/test/fake_code_signer/pe_header.rb b/test/fake_code_signer/pe_wrapper.rb similarity index 83% rename from test/fake_code_signer/pe_header.rb rename to test/fake_code_signer/pe_wrapper.rb index 29445f81..478ecca2 100644 --- a/test/fake_code_signer/pe_header.rb +++ b/test/fake_code_signer/pe_wrapper.rb @@ -1,8 +1,8 @@ class FakeCodeSigner - # This class exists to navigate and manipulate the headers of the Windows + # This class exists to navigate and manipulate the data of the Windows # executable format, PE, see: https://en.wikipedia.org/wiki/Portable_Executable # also https://msdn.microsoft.com/en-us/library/ms809762.aspx - class PEHeader + class PEWrapper # size of a DWORD is 2 words (4 bytes) DWORD_SIZE = 4 @@ -33,16 +33,14 @@ def initialize(image) @image = image end - # security is the 4th element in the data directory array - # see http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm - def security_offset - image_data_directory_offset + DATA_DIRECTORY_ENTRY_SIZE * 4 + # set digital signature address in security header + def security_address=(offset) + @image[security_address_offset, DWORD_SIZE] = raw_bytes(offset) end - alias security_address_offset security_offset - - def security_size_offset - security_offset + DWORD_SIZE + # set digital signature size in security header + def security_size=(size) + @image[security_size_offset, DWORD_SIZE] = raw_bytes(size) end # location of the digital signature @@ -55,8 +53,30 @@ def security_size deref(security_size_offset) end + # append data to end of executable + def append_data(byte_string) + @image << byte_string + end + private + # convert an integer to a raw byte string + def raw_bytes(int) + [int].pack("L") + end + + # security is the 4th element in the data directory array + # see http://bytepointer.com/resources/pietrek_in_depth_look_into_pe_format_pt1_figures.htm + def security_offset + image_data_directory_offset + DATA_DIRECTORY_ENTRY_SIZE * 4 + end + + alias security_address_offset security_offset + + def security_size_offset + security_offset + DWORD_SIZE + end + # dereferences a pointer # the only pointer type we support is an unsigned long (DWORD) def deref(ptr) From a013074725c87a5d791c8b36337b050311b5817b Mon Sep 17 00:00:00 2001 From: John Mair Date: Thu, 14 Dec 2017 08:24:05 +0100 Subject: [PATCH 39/43] Introduce PEWrapper#to_s --- test/fake_code_signer.rb | 3 ++- test/fake_code_signer/pe_wrapper.rb | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index 59cd085f..55905648 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -43,7 +43,8 @@ def sign # append the "digital signature" to the end of the executable, complete with padding pe_wrapper.append_data(padding_string + FAKE_SIG) - File.binwrite(@output, @image) + # Write out the "signed" image + File.binwrite(@output, pe_wrapper.to_s) end private diff --git a/test/fake_code_signer/pe_wrapper.rb b/test/fake_code_signer/pe_wrapper.rb index 478ecca2..56f1f5b9 100644 --- a/test/fake_code_signer/pe_wrapper.rb +++ b/test/fake_code_signer/pe_wrapper.rb @@ -58,6 +58,11 @@ def append_data(byte_string) @image << byte_string end + # string representation of this object + def to_s + @image + end + private # convert an integer to a raw byte string From fe53059b376ac1c9cfd485884c1d10282b74f344 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 15 Dec 2017 00:46:20 +0100 Subject: [PATCH 40/43] Make comments more explicit --- test/fake_code_signer/pe_wrapper.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/fake_code_signer/pe_wrapper.rb b/test/fake_code_signer/pe_wrapper.rb index 56f1f5b9..b2964422 100644 --- a/test/fake_code_signer/pe_wrapper.rb +++ b/test/fake_code_signer/pe_wrapper.rb @@ -14,7 +14,7 @@ class PEWrapper # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx PE_SIGNATURE_SIZE = 4 - # struct IMAGE_FILE_HEADER + # struct IMAGE_FILE_HEADER (second field from IMAGE_NT_HEADERS) # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx IMAGE_FILE_HEADER_SIZE = 20 @@ -95,11 +95,14 @@ def e_lfanew_offset end # We dereference e_lfanew_offset to get the actual pe_header_offset + # the pe_header is represented by the IMAGE_NT_HEADERS struct def pe_header_offset deref(e_lfanew_offset) end # offset of the IMAGE_OPTIONAL_HEADER struct + # IMAGE_OPTIONAL_HEADER is the third field in IMAGE_NT_HEADERS, so we add + # the size of the previous two fields to locate it. # see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx def image_optional_header_offset pe_header_offset + PE_SIGNATURE_SIZE + IMAGE_FILE_HEADER_SIZE From 250b76ca79caeb9b7df8cab34b0c0c4dc88aa418 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 15 Dec 2017 00:46:57 +0100 Subject: [PATCH 41/43] input -> input_file, h/t @jakedouglas --- test/fake_code_signer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index 55905648..81d74bca 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -18,9 +18,9 @@ class FakeCodeSigner FAKE_SIG = "fake signature" - def initialize(input:, output:, padding: 4) - @input = input - @output = output + def initialize(input_file:, output_file:, padding: 4) + @input = input_file + @output = output_file @padding = padding @image = File.binread(input) end From 460eb52bcfa19d1fcb59aa0a7f98c4b49cb5339a Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 15 Dec 2017 00:48:04 +0100 Subject: [PATCH 42/43] update specs with new names --- test/test_ocra.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_ocra.rb b/test/test_ocra.rb index 15072cda..1d7efa03 100644 --- a/test/test_ocra.rb +++ b/test/test_ocra.rb @@ -755,8 +755,8 @@ def test_codesigning_support with_fixture 'helloworld' do each_path_combo "helloworld.rb" do |script| assert system("ruby", ocra, script, *DefaultArgs) - FakeCodeSigner.new(input: "helloworld.exe", - output: "helloworld-signed.exe", + FakeCodeSigner.new(input_file: "helloworld.exe", + output_file: "helloworld-signed.exe", padding: rand(20)).sign pristine_env "helloworld.exe", "helloworld-signed.exe" do From 335acf76330738ba6354a102448c544b90d26a87 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 15 Dec 2017 00:51:39 +0100 Subject: [PATCH 43/43] use input_file not input --- test/fake_code_signer.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/fake_code_signer.rb b/test/fake_code_signer.rb index 81d74bca..f94775ee 100644 --- a/test/fake_code_signer.rb +++ b/test/fake_code_signer.rb @@ -19,10 +19,9 @@ class FakeCodeSigner FAKE_SIG = "fake signature" def initialize(input_file:, output_file:, padding: 4) - @input = input_file - @output = output_file + @output_file = output_file @padding = padding - @image = File.binread(input) + @image = File.binread(input_file) end def sign @@ -44,7 +43,7 @@ def sign pe_wrapper.append_data(padding_string + FAKE_SIG) # Write out the "signed" image - File.binwrite(@output, pe_wrapper.to_s) + File.binwrite(@output_file, pe_wrapper.to_s) end private