add old go tooling

This commit is contained in:
2023-05-31 16:17:03 +07:00
commit 54f1f3eb38
57 changed files with 5791 additions and 0 deletions

4
macho-go/doc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
compiled/
*.html
!template.html

View File

@ -0,0 +1,147 @@
#lang pollen
(define (sidenote label . xs)
`(splice-me
(label ((for ,label) (class "margin-toggle sidenote-number")))
(input ((id ,label) (class "margin-toggle")(type "checkbox")))
(span ((class "sidenote")) ,@xs)))
h1{Bind and protect Mach-O Binary}
section{
p{
Our product protects the iOS application. To do that, we modifies the original application such that without our library, the binary fails to start. We also detect jailbroken device, frida hooking device and crash the app if detected.
}
p{
We write ourself the wrapper, which modifies the original binary. The wrapper also create a code{LC_LOAD_DYLIB} command to load our library on runtime.
}
}
h2{Building the library}
section{
p{
We build the library as a dynamic library and distribute as a framework, code{bcell.framework/bcell}.
}
}
h3{Run code before the binary}
section{
p{
We ultilize the load order of code{dyld} to load our library and run our library code without requiring the user to call our library. We put our code{main} function inside the code{S_MOD_INIT_FUNC_POINTERS} section for code{dyld} to call when our library is loaded. We annotate the function with code{__attribute__((constructor))} to achive the effect.
}
pre{
void __attribute__((constructor)) main()
}
p{
Then the application must tell code{dyld} to load our library. We add a load command code{LC_LOAD_DYLIB} with the path code{@rpath/bcell.framework/bcell}. The framework folder is copied into Frameworks folder of the application.
}
}
h3{Remove unecessary information}
section{
h4{LC_FUNCTION_STARTS and LC_DATA_IN_CODE}
p{
}
h4{Classic linker symbols data}
p{
}
}
h3{Erasing initialization pointers}
section{
p{
Initialization pointers are pointers to functions, these functions are called by code{dyld} before code{__start}. These pointers are located inside section marked code{S_MOD_INIT_FUNC_POINTERS}. Conveniently this section can be modified as writable at runtime. Because ASLR sidenote["aslr"]{images loaded in memory with a random offset} adds a small offset from the prefered image address on load, so hardcoded pointers are misplaced. code{dyld} takes the concrete value at this place and call the function. To make the value points correctly into the function, code{dyld} add the pointers with ASLR offset before dispatching the function sidenote["aslr dyld"]{One may ask "why don't ◊code{dyld} get the value and add the ASLR offset later". We do not know, but the current design of code{dyld} is as presented}.
}
p{
Because this section is writable at runtime, we can modify the value at runtime. That is, we erase this section data, or replace with random bytes. When our library run, if every is fine, we restore the section with the correct pointers' value (with ASLR offset). Erasing this section is easy, just replace the bytes in the Mach-O binary at the section marked code{S_MOD_INIT_FUNC_POINTERS}.
}
}
h3{Remove lazy bind information}
section{
p{
}
}
h3{Restore on runtime}
section{
h4{Finding the mapped image on memory and ASLR offset}
p{
code{dyld} exposes some functions for traversing the list of loaded image. We use this to find the mapped executable image.
}
pre{
#if __ARM_ARCH_7A__
typedef struct mach_header macho_header;
typedef struct nlist symbol_t;
#elif __ARM_ARCH_7S__
typedef struct mach_header macho_header;
typedef struct nlist symbol_t;
#elif __ARM_ARCH_ISA_A64
typedef struct mach_header_64 macho_header;
typedef struct nlist_64 symbol_t;
#endif
for (unsigned int i = 0; i < _dyld_image_count(); i++) {
macho_header *mach_hdr = NULL;
mach_hdr = (macho_header *)_dyld_get_image_header(i);
if (mach_hdr == NULL) {
continue;
}
if (mach_hdr->filetype != MH_EXECUTE) {
continue;
}
return mach_hdr;
}
}
p{
Finding the ASLR offset is easy, we need only the prefered image base. The prefered image base is the vmaddr of a code{LC_SEGMENT} where fileoff == 0 but filesize != 0.
}
pre{
for _, segment := range segments {
if segment.Fileoff() == 0 && segment.Filesize() != 0 {
return segment.Vmaddr()
}
}
}
p{
We can save this prefered image base when we modify the binary or we can traverse the load commands from the mapped image header found. The ASLR offset is the subtraction of the mapped image header address and the prefered image base.
}
pre{
const uint64_t aslr_offset = (uint64_t)mach_hdr - (uint64_t)prefered_image_base;
}
h4{Restoring Initialization pointers}
p{
Restoring these pointers are easy, we need to locate the section on memory, set the section as writable, find the ASLR offset and fill the section with appropriate function pointers. Pointers should be written in the correct order as they originally appear due to functions using data initialized by previous functions.
}
p{
After finding the mapped image header on memory, we traverse the load commands looking for section marked code{S_MOD_INIT_FUNC_POINTERS}. Next we set the section as writable, with size page aligned.
}
pre{
vm_protect(mach_task_self(),
(vm_address_t)addr, size,
false, VM_PROT_WRITE | VM_PROT_READ)
}
p{
After that we write the pointers value with ASLR offset added to the section. And the Initialization pointers are restored.
}
h4{Restoring lazy bind}
p{
}
}

View File

@ -0,0 +1,23 @@
#lang pollen
h3{Manually sign the binary}
h3{Resign the binary}
section{
p{
Resign the binary is an important step after modifing a binary. The idevice will reject a falsy signed binary. To install a modified binary, we need to resign the binary. This problem is not easy because Apple checks the application certificate online. We must register a certificate online and use it to sign our application. Registering a certificate online can only be done through Xcode.
}
p{
Signin with Apple if haven't done so. Use Xcode and create a project, set signer to yourself and sign type to development. Build and install the app on a device. Go to the build folder, collect the embedded.mobileprovision file. Find the identity used to sign the binary and sign with that identity.
}
◊pre{
security find-identity -p codesigning -v
# copy the text or hash --> IDENTITY
security cms -D -i embedded.mobileprovision > profile.plist
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
codesign -fs IDENTITY --entitlements entitlements.plist BINARY
}
}

349
macho-go/doc/index.html.pm Normal file
View File

@ -0,0 +1,349 @@
#lang pollen
(require txexpr)
(require pollen/decode)
(define-meta template "template.html")
(define (sidenote label . xs)
`(splice-me
(label ((for ,label) (class "margin-toggle sidenote-number")))
(input ((id ,label) (class "margin-toggle")(type "checkbox")))
(span ((class "sidenote")) ,@xs)))
◊(define (stupid-sidenote label . xs)
`(stupid-sidenote
(label ((for ,label) (class "margin-toggle sidenote-number")))
(input ((id ,label) (class "margin-toggle")(type "checkbox")))
(span ((class "sidenote")) ,@xs)))
(define (splice xs)
(apply append (for/list ([x (in-list xs)])
(if (and (txexpr? x) (member (get-tag x) '(splice-me)))
(get-elements x)
(list x)))))
◊(define (root . xs)
(decode `(decoded-root ,@xs)
#:txexpr-elements-proc (compose1 detect-paragraphs splice)
#:exclude-tags '(pre)
))
h1{Mach-O Binary Bindings}
h2{Mach-O Binary Format}
section{
p{
Mach-O binary is the executable file format that MacOS, iOS and tvOS use. The file is splited into 3 main parts. The header, the load commands, and the data. While the binary has 3 main parts, we only need to focus on the header and load commands part. The data part are referenced by load commands part.
}
}
h3{Header}
section{
p{
The header provides summary information to the binary. First comes the file magic then the file's archtecture, number and size of load commands, and a bitmask flag.
}
◊p{
The Mach-O binary comes in 2 different set of magic bytes, 32-bit, 64-bit with endianess in either little or big.
}
◊pre{
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
}
◊p{
The file architecture is denoted by two numbers. One specifies the main architecture (intel 386, arm, mips, ...), one specifies the sub-architecture (armv7, arm64e, ...). ◊sidenote["machine-arch-enum"]{◊code{cctools/include/mach/machine.h}}
}
◊p{
The Mach-O binary uses load commands to structure the binary information. The header stores the number of load commands and size (in bytes) for parsing and validation.
}
◊p{
The header also has a bitmask flag value for others information, e.g PIE.
}
}
◊h3{Load Commands}
◊section{
◊p{
Load commands are the main part of a Mach-O binary. These load commands are read by ◊code{dyld} linker to load the binary into memory. Each load commands are prepend by a simple header indicate the type and size. The body of each load commands different with each type.
}
◊p{
At the time of writing, there are more than 50 load command types. But we only go into a few types that are necessary to understand the Mach-O Binary Binding.
}
}
◊h4{LC_SEGMENT and LC_SECTION}
◊section{
◊p{
◊code{LC_SEGMENT} defines a region for virtual mapping into the memory on Runtime. It defines the virtual address and size for ◊code{dyld} to map. A segment contains a list of sections defined as ◊code{LC_SECTION}. ◊code{LC_SECTION}s are located below the ◊code{LC_SEGMENT}. Each section contains a data pointer to instruct ◊code{dyld} to load bytes into memory.
}
◊p{
◊code{LC_SEGMENT} are loaded into memory with default Read-Write-Executable marked by ◊code{initprot}. We can modify the memory region on runtime using ◊code{vm_protect}. However, protection bit cannot exceed ◊code{maxprot}.
}
◊p{
◊code{LC_SECTION} may contain special data, such sections are marked by the ◊code{type} field. A section containing C literal strings are marked as ◊code{S_CSTRING_LITERALS}.
}
}
◊h4{LC_LOAD_DYLIB and LC_RPATH}
◊section{
◊p{
The Mach-O binary defines the dynamically linked library using ◊code{LC_LOAD_DYLIB}. When ◊code{dyld} reads this load command, it loads the library into the memory. ◊code{LC_LOAD_DYLIB} contains a path to the linked library, e.g. ◊code{/usr/lib/libSystem.B.dylib}. The path can be absolute ◊code{/absolute/lib}, relative ◊code{../relative/lib} or prefixed. Prefixed path are path prefixed by either ◊code{@executable_path}, ◊code{@loader_path} or ◊code{@rpath}. ◊code{@executable_path} points to the directory where the executable is placed. ◊code{@loader_path} points to ◊code{dyld} parent directory. ◊code{@rpath} are defined using ◊code{LC_RPATH}.
}
◊p{
◊code{LC_RPATH} adds to the set of paths to find a library path with ◊code{@rpath}. Often, we see a ◊code{LC_RPATH = @executable_path/Frameworks} defined. This path is commonly used in iOS applications as third-party libraries are placed inside ◊code{Frameworks} folder, and specify ◊code{@rpath} to load libraries are shorter to write (and understand). ◊sidenote["rpath sucks"]{Not yet research into same library name put in different @rpath}
}
}
◊h4{LC_ID_DYLIB}
◊section{
◊p{
This is a special command indicating the path to put the library when compiled. The compilation may fail if the library is placed wrongly. Renaming the library also fail the compilation. ◊sidenote["id dylib"]{Not 100% sure}
}
}
◊h4{LC_DYLD_INFO or LC_DYLD_INFO_ONLY}
◊section{
◊p{
This command appears only once in a Mach-O binary and contains pointers to the encoded list of external (imported) symbols and exported symbols. External symbols reside on third-party library and must be bound by ◊code{dyld}. There are 3 types of external symbols, normal bind, weak bind, and lazy bind.
}
◊p{
External symbols are defined using byte-stream. Reading and decode the byte-stream reveals the symbol's name, the library hosting the symbol.
For normal bind symbols, code{dyld} read and decode the byte-stream when loading the binary to memory and write the symbol's function address to a section marked ◊code{S_NON_LAZY_SYMBOL_POINTERS}.
For lazy bind symbols, the decoding process is done through PLT ◊sidenote["PLT"]{Procedural Linkage Table}. Each lazy bind symbol have a helper stub calling ◊code{dyld_stub_binder} with a number. The number is an offset to lazy bind symbols byte-stream. When parsed, the symbol's name and the library hosting the symbol is known. With these two information, code{dyld} look up the symbol's name in the export table of the hosting library and return the address to the function. ◊code{dyld} also rewrite the PLT table on success so the next time the same function is called, it will be a direct call.
◊pre{
__LAZY:
...
dword _stub_write
...
foo:
push argument_of_write
jmp [__LAZY + offset_to_stub_write]
_stub_write:
push OFFSET_WRITE_IN_BYTE_STREAM
jmp _dyld_stub_binder
_dyld_stub_binder:
call fastBindLazySymbol
call eax
# where fastBindLazySymbol resolve the address of the symbol and
# replace the __LAZY _stub_write pointer with the symbol's address.
}
}
}
h4{LC_CODE_SIGNATURE}
section{
p{
The *OS system requires the running binary to be signed. Signed Mach-O binary has a load command code{LC_CODE_SIGNATURE} points to a compressed data containing the vendor information, the key used signing and the certificate. sidenote["codesign"]{Yet to read how to parse this information}
}
}
h4{LC_ENCRYPTION_INFO}
section{
p{
To prevent people copying binaries, Apple introduced PlayFair, a mechanism to prevent digital infringement. With PlayFair, the app is encrypted with a key installed on the chip when downloaded from AppStore. The content is decrypted at runtime using the hardware key. When running the binary in another machine, the app content can't be decrypted and fail to launch.
}
◊p{
◊code{LC_ENCRYPTION_INFO} points to the region of the encrypted data and indicate if the content is encrypted or decrypted. It should only be encrypted when distributed through AppStore. In later section, we introduce how one can decrypt the content with a Jailbroken iOS device.
}
}
◊h4{LC_FUNCTION_STARTS and LC_DATA_IN_CODE}
◊section{
◊p{
}
}
◊h4{LC_SYMTAB and LC_DYSYMTAB}
◊section{
◊p{
}
}
◊h2{Fat Binary Format}
◊section{
◊p{
Apple has a binary format for combining different architecture of an application into one, called Fat binary. The binary format is simple, it defines a list of Mach-O binaries. The Mach-O binaries inside are located with an alignment of 2. Infact:
}
◊pre{
func GetAlignment(h *macho.Header) uint32 {
switch h.Cputype() {
case CPU_TYPE_ARM, CPU_TYPE_ARM64:
return 0xe // log2(0x4000)
case CPU_TYPE_POWERPC,
CPU_TYPE_POWERPC64,
CPU_TYPE_I386,
CPU_TYPE_X86_64:
return 0xc // log2(0x1000)
default:
return 0xd // log2(0x2000)
}
}
}
}
◊h2{ipa file}
◊section{
◊p{
In iOS applications are distributed using .ipa file format. The .ipa file is a .zip file with a certain structure. A valid ipa file must have a folder ◊code{*.app}. Inside the folder must have two files, ◊code{Info.plist} and a binary. The binary name must conform to the ◊code{Info.plist}'s code{CFBundleExecutable} field. code{Info.plist} file format is (UTF8) XML or binary encoded, both format can be read using python's ◊a['((href "https://docs.python.org/3/library/plistlib.html"))]{plistlib}.
}
h3{Info.plist}
p{
}
}
h2{Jailbreak and Mach-O hacks}
h3{Jailbreak the iOS}
section{
p{
Jailbreak can be done by using tools like a['((href "https://checkra.in/"))]{◊code{checkra1n}}. At the time of writing, Jailbreak can be done easily for devices iPhone5s to iPhone X and support for iOS 12 to 14. This jailbreak is semi-tethered, means that a reboot breaks the jailbreak.
}
◊p{
Jailbreak disable some security features by patching the iOS kernel. It enables USB access, re-mounts ◊code{/} as both readable and writable, removes sandbox, etc. Jailbreaking tool often come with some other applications, e.g openssh, Cydia. A device with Cydia installed is Jailbroken. In custom jailbreaking tool, we can omit the installation of Cydia. However, most jailbroken devices are jailbroken using public tooling (like checkra1n), and by default, it comes with Cydia installed.
}
◊p{
Semi-tethered jailbreak losts the kernel patch after a reboot, but applications installed when the jailbreak was active remain. In these cases, the device must run the jailbreak again to access jailbreak features.
}
◊p{
Jailbreak tools install ◊code{openssh} to provide access through USB connection. ◊code{checkra1n} install and run openssh at port 44. After connecting the device with the USB cable, we proxy the USB connection to ◊code{localhost}, now ◊code{ssh} to the device is possible. To proxy the USB connection, we use ◊a['((href "https://libimobiledevice.org/"))]{code{iproxy}} sidenote["iproxy"]{code{brew install libimobiledevice}}.
}
pre{
iproxy 4444 44
ssh root@localhost -p 4444
# default root password: alpine
}
p{
In old books and article the path to installed applications are wrong. Use this path: code{/var/containers/Bundle/Application/ID/}, where ID is a long hash for each application.sidenote["app path"]{old path: code{/var/mobile/Applications/ID/}}
}
}
h3{Decrypt the binary}
section{
p{
As mentioned in code{LC_ENCRYPTION_INFO}, binaries downloaded from AppStore is encrypted with a hardware key. It is said that the key can't be retrieved, and the only to decrypt the binary is to let it run and dump the binary after decryption process. Here we demonstrate the use of ◊a['((href "https://github.com/stefanesser/dumpdecrypted"))]{dumpdecrypted}.
}
p{
Compile the library, add code{-miphoneos-version-min=<iOS>} as iOS version of the idevice. Sign the output binary. Use scp to copy the binary to the idevice. ssh into the idevice and run the application with the library.
}
pre{
set DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib
/var/containers/Bundle/Application/ID/APPNAME.app/BINARY
}
p{
A binary BINARY.decrypted will be written out with contents decrypted. This method is the most simple way to decrypt an application but still cannot install to another machine (even after resigning). This method only decrypt the main application, however the third-party libraries are also encrypted and not decrypted. We can overcome this with code{frida-ios-dump}. This tool uses Frida, which will be discussed in later section.
}
p{
code{dyld} maps the encrypted section using code{mremap_encrypted} sidenote["mremap_encrypted"]{code{dyld/dyld3/Loading.cpp}}. Another tool using this method to decrypt is a['((href "https://github.com/JohnCoates/flexdecrypt"))]{flexdecrypt}.
}
◊pre{
#if (__arm__ || __arm64__) && !TARGET_OS_SIMULATOR
// tell kernel about fairplay encrypted regions
uint32_t fpTextOffset;
uint32_t fpSize;
if ( image->isFairPlayEncrypted(fpTextOffset, fpSize) ) {
const mach_header* mh = (mach_header*)loadAddress;
int result = ::mremap_encrypted(((uint8_t*)mh) + fpTextOffset, fpSize, 1, mh->cputype, mh->cpusubtype);
if ( result != 0 ) {
diag.error("could not register fairplay decryption, mremap_encrypted() => %d", result);
::vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize);
return;
}
}
#endif
}
}
◊h3{Frida hooking}
◊section{
◊p{
Frida is a hooking framework. Using Frida, one can easily intercept the running application. Frida supports both jailbroken and non-jailbroken devices. Frida has two components, the server and the client. The server is installed in our machine, the client is attached to the application.
}
◊pre{
python3 -m pip install frida-tools
}
◊p{
The Frida ◊a['((href "https://frida.re/docs/ios/"))]{documentation} gives some instructions for using Frida with jailbroken or non-jailbroken devices. In our experience, we setup Frida manually, for both jailbroken and non-jailbroken devices, as stated below.
}
h4{Setup for jailbroken devices}
p{
Jailbroken devices with Cydia can easily install Frida by adding Frida from Cydia store. The installed Frida should have its ABI compatible with the device. Frida can also be installed by download, sign, copy (scp) onto the device and run the Frida client.
}
h4{Setup for non-jailbroken devices}
p{
Non-jailbroken devices cannot install Frida client. Frida server uses Frida client to inject Frida Gadget into the application on jailbroken devices. For non-jailbroken devices, we must setup Frida Gadget directly inside the application. We can do this by using a['((href "https://github.com/Tyilo/insert_dylib"))]{insert_dylib}. Build insert_dylib, download FridaGadget.dylib.
}
For an application having the ipa file (.app folder).
◊pre{
insert_dylib --strip-codesig --inplace '@executable_path/Frameworks/FridaGadget.dylib' BINARY
}
Copy FridaGadget.dylib into Frameworks folder. Resign BINARY and sign FridaGadget.dylib. Use Xcode to install the application. Frida can connect and intercept the application after the application started.
}
◊h4{Using Frida}
◊p{
Frida intercepts the application and provides a scripting utility. We can use the scripting engine to inspect memory, change registers' value. The iOS ecosystem often use Objective-C (Swift also use Objective-C at low-level) and Frida also supports conversion from Objective-C data type to raw data type, e.g. NSString to raw string.
}
h3{Others tools}
section{
p{
There are many other tools to pentest/reverse/intercept/... iOS applications. We haven't tested these tools but they are worth mentioning.
}
◊ul{
◊li{◊a['((href "http://www.cydiasubstrate.com/"))]{CydiaSubstrate} - Hook framework}
li{a['((href "https://github.com/akemin-dayo/AppSync"))]{AppSync} - Utilities for jailbreak devices}
◊li{◊a['((href "https://github.com/jmpews/Dobby"))]{Dobby} - Hook framework (stale)}
li{a['((href "https://github.com/KJCracks/Clutch"))]{Clutch} - Dump decrypted}
◊li{◊a['((href "https://github.com/nygard/class-dump"))]{class-dump} - Dump Objective-C class metadata}
li{a['((href "https://github.com/asLody/whale"))]{whale} - Hook framework}
◊li{◊a['((href "https://github.com/alexzielenski/optool"))]{optool} - Mach-O binary utilities}
li{a['((href "https://github.com/steakknife/unsign"))]{unsign} - unsign Mach-O binary}
}
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>◊select['h1 doc]</title>
<link rel="stylesheet" href="tufte.css">
</head>
<article>
◊(->html doc)
</article>

469
macho-go/doc/tufte.css Normal file
View File

@ -0,0 +1,469 @@
@charset "UTF-8";
/* Import ET Book styles
adapted from https://github.com/edwardtufte/et-book/blob/gh-pages/et-book.css */
@font-face {
font-family: "et-book";
src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot");
src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff") format("woff"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf") format("truetype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg#etbookromanosf") format("svg");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "et-book";
src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot");
src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff") format("woff"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf") format("truetype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg#etbookromanosf") format("svg");
font-weight: normal;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "et-book";
src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot");
src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff") format("woff"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf") format("truetype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg#etbookromanosf") format("svg");
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "et-book-roman-old-style";
src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot");
src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff") format("woff"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf") format("truetype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg#etbookromanosf") format("svg");
font-weight: normal;
font-style: normal;
font-display: swap;
}
/* Tufte CSS styles */
html {
font-size: 15px;
}
body {
width: 87.5%;
margin-left: auto;
margin-right: auto;
padding-left: 12.5%;
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
background-color: #fffff8;
color: #111;
max-width: 1400px;
counter-reset: sidenote-counter;
}
h1 {
font-weight: 400;
margin-top: 4rem;
margin-bottom: 1.5rem;
font-size: 3.2rem;
line-height: 1;
}
h2 {
font-style: italic;
font-weight: 400;
margin-top: 2.1rem;
margin-bottom: 1.4rem;
font-size: 2.2rem;
line-height: 1;
}
h3 {
font-style: italic;
font-weight: 400;
font-size: 1.7rem;
margin-top: 2rem;
margin-bottom: 1.4rem;
line-height: 1;
}
hr {
display: block;
height: 1px;
width: 55%;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
p.subtitle {
font-style: italic;
margin-top: 1rem;
margin-bottom: 1rem;
font-size: 1.8rem;
display: block;
line-height: 1;
}
.numeral {
font-family: et-book-roman-old-style;
}
.danger {
color: red;
}
article {
padding: 5rem 0rem;
}
section {
padding-top: 1rem;
padding-bottom: 1rem;
}
p,
ol,
ul {
font-size: 1.4rem;
line-height: 2rem;
}
p {
margin-top: 1.4rem;
margin-bottom: 1.4rem;
padding-right: 0;
vertical-align: baseline;
}
/* Chapter Epigraphs */
div.epigraph {
margin: 5em 0;
}
div.epigraph > blockquote {
margin-top: 3em;
margin-bottom: 3em;
}
div.epigraph > blockquote,
div.epigraph > blockquote > p {
font-style: italic;
}
div.epigraph > blockquote > footer {
font-style: normal;
}
div.epigraph > blockquote > footer > cite {
font-style: italic;
}
/* end chapter epigraphs styles */
blockquote {
font-size: 1.4rem;
}
blockquote p {
width: 55%;
margin-right: 40px;
}
blockquote footer {
width: 55%;
font-size: 1.1rem;
text-align: right;
}
section > p,
section > footer,
section > table {
width: 55%;
}
/* 50 + 5 == 55, to be the same width as paragraph */
section > ol,
section > ul {
width: 50%;
-webkit-padding-start: 5%;
}
li:not(:first-child) {
margin-top: 0.25rem;
}
figure {
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
max-width: 55%;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
margin: 0 0 3em 0;
}
figcaption {
float: right;
clear: right;
margin-top: 0;
margin-bottom: 0;
font-size: 1.1rem;
line-height: 1.6;
vertical-align: baseline;
position: relative;
max-width: 40%;
}
figure.fullwidth figcaption {
margin-right: 24%;
}
/* Links: replicate underline that clears descenders */
a:link,
a:visited {
color: inherit;
}
.no-tufte-underline:link {
background: unset;
text-shadow: unset;
}
a:link, .tufte-underline, .hover-tufte-underline:hover {
text-decoration: none;
background: -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(currentColor, currentColor);
background: linear-gradient(#fffff8, #fffff8), linear-gradient(#fffff8, #fffff8), linear-gradient(currentColor, currentColor);
-webkit-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
-moz-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
background-repeat: no-repeat, no-repeat, repeat-x;
text-shadow: 0.03em 0 #fffff8, -0.03em 0 #fffff8, 0 0.03em #fffff8, 0 -0.03em #fffff8, 0.06em 0 #fffff8, -0.06em 0 #fffff8, 0.09em 0 #fffff8, -0.09em 0 #fffff8, 0.12em 0 #fffff8, -0.12em 0 #fffff8, 0.15em 0 #fffff8, -0.15em 0 #fffff8;
background-position: 0% 93%, 100% 93%, 0% 93%;
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
a:link, .tufte-underline, .hover-tufte-underline:hover {
background-position-y: 87%, 87%, 87%;
}
}
a:link::selection,
a:link::-moz-selection {
text-shadow: 0.03em 0 #b4d5fe, -0.03em 0 #b4d5fe, 0 0.03em #b4d5fe, 0 -0.03em #b4d5fe, 0.06em 0 #b4d5fe, -0.06em 0 #b4d5fe, 0.09em 0 #b4d5fe, -0.09em 0 #b4d5fe, 0.12em 0 #b4d5fe, -0.12em 0 #b4d5fe, 0.15em 0 #b4d5fe, -0.15em 0 #b4d5fe;
background: #b4d5fe;
}
/* Sidenotes, margin notes, figures, captions */
img {
max-width: 100%;
}
.sidenote,
.marginnote {
float: right;
clear: right;
margin-right: -60%;
width: 50%;
margin-top: 0;
margin-bottom: 0;
font-size: 1.1rem;
line-height: 1.3;
vertical-align: baseline;
position: relative;
}
.sidenote-number {
counter-increment: sidenote-counter;
}
.sidenote-number:after,
.sidenote:before {
font-family: et-book-roman-old-style;
position: relative;
vertical-align: baseline;
}
.sidenote-number:after {
content: counter(sidenote-counter);
font-size: 1rem;
top: -0.5rem;
left: 0.1rem;
}
.sidenote:before {
content: counter(sidenote-counter) " ";
font-size: 1rem;
top: -0.5rem;
}
blockquote .sidenote,
blockquote .marginnote {
margin-right: -82%;
min-width: 59%;
text-align: left;
}
div.fullwidth,
table.fullwidth {
width: 100%;
}
div.table-wrapper {
overflow-x: auto;
font-family: "Trebuchet MS", "Gill Sans", "Gill Sans MT", sans-serif;
}
.sans {
font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
letter-spacing: .03em;
}
code, pre > code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 1.0rem;
line-height: 1.42;
-webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
}
.sans > code {
font-size: 1.2rem;
}
h1 > code,
h2 > code,
h3 > code {
font-size: 0.80em;
}
.marginnote > code,
.sidenote > code {
font-size: 1rem;
}
pre > code {
font-size: 0.9rem;
width: 52.5%;
margin-left: 2.5%;
overflow-x: auto;
display: block;
}
pre.fullwidth > code {
width: 90%;
}
.fullwidth {
max-width: 90%;
clear:both;
}
span.newthought {
font-variant: small-caps;
font-size: 1.2em;
}
input.margin-toggle {
display: none;
}
label.sidenote-number {
display: inline;
}
label.margin-toggle:not(.sidenote-number) {
display: none;
}
.iframe-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
padding-top: 25px;
height: 0;
}
.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
@media (max-width: 760px) {
body {
width: 84%;
padding-left: 8%;
padding-right: 8%;
}
hr,
section > p,
section > footer,
section > table {
width: 100%;
}
pre > code {
width: 97%;
}
section > ol {
width: 90%;
}
section > ul {
width: 90%;
}
figure {
max-width: 90%;
}
figcaption,
figure.fullwidth figcaption {
margin-right: 0%;
max-width: none;
}
blockquote {
margin-left: 1.5em;
margin-right: 0em;
}
blockquote p,
blockquote footer {
width: 100%;
}
label.margin-toggle:not(.sidenote-number) {
display: inline;
}
.sidenote,
.marginnote {
display: none;
}
.margin-toggle:checked + .sidenote,
.margin-toggle:checked + .marginnote {
display: block;
float: left;
left: 1rem;
clear: both;
width: 95%;
margin: 1rem 2.5%;
vertical-align: baseline;
position: relative;
}
label {
cursor: pointer;
}
div.table-wrapper,
table {
width: 85%;
}
img {
width: 100%;
}
}