From 9b2796b2a1fea179428a78eea59d43b1bf553290 Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Thu, 22 Aug 2024 17:41:56 +0700 Subject: [PATCH] basic POC for string removal --- macho-go/pkg/ios/macho/edit.go | 241 +++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/macho-go/pkg/ios/macho/edit.go b/macho-go/pkg/ios/macho/edit.go index b49b1f5..748c15e 100644 --- a/macho-go/pkg/ios/macho/edit.go +++ b/macho-go/pkg/ios/macho/edit.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "io" + "os" "math/rand" "strings" "time" @@ -557,6 +558,246 @@ func (mc *MachoContext) RemoveExportTrie() { } } +// this function breaks the file by adding another segment at +// the end of the file +func (mc *MachoContext) RemoveStrings() { + // add a new writable segment with a section + // loop over the instructions for adrp,add instructions + // if the access points to old cstring section, update + // with new values from the new segment and section. + + // data references, e.g., pointer to string, are compiled + // into Rebase symbol, so actively check for rebase and + // rewrite the rebase value to new segment, section offset. + + // save the strings into a file for recovery. should keep linear + // format as before, or customized order, if want complex. + + // __LINKEDIT must be at the end of the section for the binary + // to be able to resign, so we have to move __LINKEDIT down + // by how much? by the page aligned size of the added segment + + // but __LINKEDIT also contains data for link time data + // (fixed ups and bytecode chain) so have to modify + // their reference down too, + // symtab and dysymtab can be ignored, by removing them lmao + + fmt.Println("TODO: Removing strings") + + // identify the cstring region + + var cstring *Section64; + for _, command := range mc.commands { + switch command.(type) { + case *Segment64: + var segment = command.(*Segment64) + if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 { + for _, section := range segment.Sections() { + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__cstring")) == 0 { + cstring = section.(*Section64) + } + } + } + continue + + default: + continue + } + } + + // no section __cstring in __TEXT + if cstring == nil { + return + } + + // get last segment, as the start point we extend from + // this assumes that the segment are ordered correctly, + // first segment offset is lower then second segment offset, + // and so on, yielding last segment is the last part of the + // binary. Our new segment add another part to the binary + // at the end. + + // last segment is always the linkedits + last_segment := mc.Segments()[len(mc.Segments()) - 1] + fmt.Printf("last segment %v %s\n", last_segment, string(last_segment.SegName())) + + // all data must be inside the segment (or in header) + + // occupy the segment of linkedit and move linkedit down + + segstart := last_segment.Vmaddr() + + // segment must be page aligned, and the size is based on + // the section size + secstart := segstart + secsize := cstring.Size() + + filestart := last_segment.Fileoff() + // align to page address, not sure if neccessary, because the + // loader can pick up from anywhere and load in memory (mmap) + if filestart % 0x4000 != 0 { + filestart += 0x4000 - (filestart % 0x4000) + } + + fmt.Printf("section size %x\n", secsize) + secsize_aligned := uint64(0) + if secsize % 0x4000 == 0 { + // very rare, but possible, it occupies whole pages + secsize_aligned = secsize + } else { + secsize_aligned = secsize + (0x4000 - (secsize % 0x4000)) + } + filesize := secsize_aligned + + segname := make([]byte, 16) + copy(segname, []byte("__BSHIELD")) + secname := make([]byte, 16) + copy(secname, []byte("__secrets")) + + + fmt.Printf("segstart %x\n", segstart) + fmt.Printf("file_start %x\n", filestart) + + // size of the section and segment is defined by the total + // space for strings required + + section := Section64{ + sectname: secname, + segname: segname, + addr: secstart, + size: secsize, + offset: uint32(filestart), + align: 3, // idk, see AddSection + reloff: 0, + nreloc: 0, + flags: 0, + reserved1: 0, + reserved2: 0, + reserved3: 0, + } + string_segment := Segment64{ + c: LoadCmd{cmd: LC_SEGMENT_64, cmdsize: 0}, + segname: segname, + vmaddr: segstart, + vmsize: secsize_aligned, + fileoff: filestart, + filesize: filesize, + maxprot: 3, // read/write + initprot: 3, // read/write + nsects: 1, + flags: 0, + sections: []*Section64{§ion}, + } + + // rewrite the header to be correct + // mc.AddLoadCmd(&string_segment) + + edit_segname := make([]byte, 16) + copy(edit_segname, []byte("__LINKEDIT")) + edit_segment := Segment64{ + c: LoadCmd{cmd: LC_SEGMENT_64, cmdsize: 0}, + segname: edit_segname, + vmaddr: segstart + secsize_aligned, // move down + vmsize: last_segment.Vmsize(), + fileoff: filestart + filesize, + filesize: last_segment.Filesize(), + maxprot: 1, // read/write + initprot: 1, // read/write + nsects: 0, + flags: 0, + sections: []*Section64{}, + } + + // modify the segment list + mc.segments[len(mc.segments) - 1] = &string_segment + mc.segments = append(mc.segments, &edit_segment) + + // modify the command list + for i, cmd := range mc.commands { + if cmd.(*Segment64) == last_segment.(*Segment64) { + mc.commands = append(mc.commands[:i + 1], mc.commands[i:]...) + mc.commands[i] = &string_segment + mc.commands[i + 1] = &edit_segment + break + } + } + + // modify offset in other commands to use new link edit offset + + edit_offset_migrate := func (file_offset uint64) uint64 { + // they should keep the old offset, + // but the base related to linkedit is modified + relative_offset := file_offset - last_segment.Fileoff() + return relative_offset + edit_segment.Fileoff() + } + + for _, cmd := range mc.commands { + if lcmd, ok := cmd.(*LinkEdit); ok { + lcmd.dataoff = uint32(edit_offset_migrate(uint64(lcmd.dataoff))) + } + if lcmd, ok := cmd.(*Symtab); ok { + lcmd.stroff = uint32(edit_offset_migrate(uint64(lcmd.stroff))) + lcmd.symoff = uint32(edit_offset_migrate(uint64(lcmd.symoff))) + } + if lcmd, ok := cmd.(*DySymtab); ok { + lcmd.indirectsymoff = uint32(edit_offset_migrate(uint64(lcmd.indirectsymoff))) + } + } + + mc.RewriteHeader() + + + tmp_file := mc.file.Name() + + // has to reopen file as append + mc.file.Close() + mc.file, _ = os.OpenFile(tmp_file, os.O_RDWR | os.O_APPEND, 0644) + + // make extra space + expected_end := edit_segment.Fileoff() + edit_segment.Filesize() + end, _ := mc.file.Seek(0, io.SeekEnd) + if end < int64(expected_end) { + mc.file.WriteAt(make([]byte, expected_end - uint64(end)), end) + } + + // close and reopen as read/write, the buffer at the end is now empty + mc.file.Close() + mc.file, _ = os.OpenFile(tmp_file, os.O_RDWR, 0644) + + // peek at old link edit and move down + old_linkedit := make([]byte, last_segment.Filesize()) + mc.file.ReadAt(old_linkedit, int64(last_segment.Fileoff())) + mc.file.WriteAt(old_linkedit, int64(edit_segment.Fileoff())) + + // prepare dummy bytes into new string segment, 0 for now + dummy := make([]byte, edit_segment.Fileoff() - string_segment.Fileoff()) + copy(dummy, []byte("We R BShield\n")) + mc.file.WriteAt(dummy, int64(string_segment.Fileoff())) + + // loop over __TEXT,__text and find all occurances of (adrp, add) + // edit the offset to points to new region + // because adrp sets the register to the address page at its address + // (for page align 0x4000), e.g., + // `adrp x0` instruction at 0x100003f70, yields x0 = 0x100003000 + // technically, adrp can offset as far as 33-bit, roughly 4GB memory + // so we should be very free, because very few program goes this far + // but if this happens, god bless you + + // for the demo, we can try to hard code, (addresses are virtual) + // text should be in 0x100003f70 + // string to reference is 0x100008000 + + // encoding ADRP is actually hard hmmge? + + // revert to offset + mc.file.WriteAt([]byte{0x20, 0x00, 0x00, 0xb0}, int64(0x000003f70)) + mc.file.WriteAt([]byte{0x00, 0x00, 0x00, 0x91}, int64(0x000003f74)) + + // modify the rebase table (for both opcode and fixups chain versions) + // this is for pointer references + +} + func (mc *MachoContext) AddSection(segname string, name string, size int) Section { // mc.file.WriteAt(mc.header.Serialize(mc), 0) var ret Section