package winpwn import ( "bytes" "debug/pe" "errors" "io" "math" ) // Section characteristic flags relevant to memory protection // (IMAGE_SCN_MEM_*, see winnt.h). const ( imageSCNMemExecute = 0x20000000 imageSCNMemRead = 0x40000000 imageSCNMemWrite = 0x80000000 ) // Section wraps debug/pe.Section with the permission/entropy helpers a pwn // workflow actually needs (find the RWX section, spot the packed one). type Section struct { *pe.Section // live is set when this Section belongs to a live-process-backed // PEFile. debug/pe.Section.Data() always reads via PointerToRawData // (the section's *file* offset), which is wrong once the image is // loaded into memory -- SectionAlignment shifts things around relative // to FileAlignment. When live is set, Data() reads VirtualSize bytes // at VirtualAddress instead, through the same ReaderAt the rest of a // live PEFile uses. live io.ReaderAt } func (s *Section) IsReadable() bool { return s.Characteristics&imageSCNMemRead != 0 } func (s *Section) IsWritable() bool { return s.Characteristics&imageSCNMemWrite != 0 } func (s *Section) IsExecutable() bool { return s.Characteristics&imageSCNMemExecute != 0 } func (s *Section) IsRWX() bool { return s.IsReadable() && s.IsWritable() && s.IsExecutable() } // Data returns the section's raw bytes. Overrides (shadows) // debug/pe.Section.Data: see the live field's doc comment for why a // live-process-backed section needs a different read path. func (s *Section) Data() ([]byte, error) { if s.live == nil { return s.Section.Data() } buf := make([]byte, s.VirtualSize) if _, err := s.live.ReadAt(buf, int64(s.VirtualAddress)); err != nil { return nil, err } return buf, nil } // Entropy returns the Shannon entropy (0..8 bits/byte) of the section's raw // data, the standard quick signal for "this is packed/encrypted" (UPX-style // sections commonly read >7.2). func (s *Section) Entropy() (float64, error) { data, err := s.Data() if err != nil { return 0, err } return ShannonEntropy(data), nil } // ShannonEntropy computes the byte-level Shannon entropy of data, in bits // per byte (0 = uniform/empty, 8 = maximally random). func ShannonEntropy(data []byte) float64 { if len(data) == 0 { return 0 } var counts [256]int for _, b := range data { counts[b]++ } entropy := 0.0 total := float64(len(data)) for _, c := range counts { if c == 0 { continue } freq := float64(c) / total entropy -= freq * math.Log2(freq) } return entropy } // Sections returns every section wrapped with the permission/entropy helpers. func (p *PEFile) Sections() []*Section { out := make([]*Section, len(p.File.Sections)) for i, sec := range p.File.Sections { out[i] = p.wrapSection(sec) } return out } // Section looks up a single section by name (e.g. ".text"). func (p *PEFile) Section(name string) (*Section, error) { for _, sec := range p.File.Sections { if sec.Name == name { return p.wrapSection(sec), nil } } return nil, errors.New("section not found: " + name) } func (p *PEFile) wrapSection(sec *pe.Section) *Section { s := &Section{Section: sec} if p.live { s.live = p.r } return s } // LikelyPackedSections returns sections whose entropy exceeds threshold // (0 selects the common UPX-style default of 7.2 bits/byte), the quick // "is this binary packed" check pwntools has no direct analogue for since // ELF packers are rarer in CTF practice than UPX-on-Windows. func (p *PEFile) LikelyPackedSections(threshold float64) ([]*Section, error) { if threshold <= 0 { threshold = 7.2 } var hits []*Section for _, sec := range p.Sections() { if sec.Size == 0 { continue } entropy, err := sec.Entropy() if err != nil { continue } if entropy >= threshold { hits = append(hits, sec) } } return hits, nil } // SearchBytes ищет последовательность байт во всех секциях PE-файла // Возвращает массив RVA (Relative Virtual Address) всех совпадений func (p *PEFile) SearchBytes(pattern []byte) ([]uint64, error) { var results []uint64 for _, sec := range p.Sections() { // Читаем сырые данные секции (live-aware: see Section.Data) data, err := sec.Data() if err != nil { continue // Если секция пустая (например .bss), пропускаем } offset := 0 for { // Ищем паттерн в оставшейся части данных idx := bytes.Index(data[offset:], pattern) if idx == -1 { break } // Вычисляем RVA: виртуальный адрес секции + смещение внутри секции rva := sec.VirtualAddress + uint32(offset+idx) results = append(results, uint64(rva)) // Сдвигаем offset, чтобы продолжить поиск после текущего совпадения offset += idx + 1 } } if len(results) == 0 { return nil, errors.New("pattern not found in PE file") } return results, nil }