basic POC for string removal

This commit is contained in:
nganhkhoa 2024-08-22 17:41:56 +07:00
parent 925429c4a9
commit 9b2796b2a1

View File

@ -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{&section},
}
// 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