Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion profiler/lib/augure-profiler/profiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,23 @@ def emit_symbols(facts)
# CTF heuristic: a local win/flag function = a callable target
# (the ret2func family's target_function fact). The winning
# symbol keeps its name AND its address - the payload builder
# needs the address, not just the existence.
# needs the address, not just the existence. The address is only
# usable when the module is ET_EXEC (non-PIE: the address is
# absolute): an ET_DYN module's symbol address is module-
# relative, and a companion library's win is not in the binary's
# address space - the merged fact base must not carry an address
# the runtime never maps (the emporium libret2csu defines its
# own ret2win at 0x2515, module-relative).
exec = @elf.header.type == "EXEC"
@elf.symbols.each do |sym|
next unless sym.name.to_s.match?(/\Awin\z|win\z|flag\z/)
# an IMPORT is not a win HERE: the ret2csu shape imports its
# ret2win from the companion library; the symbol's value is 0
# and a built [qword win] payload would jump to NULL
next if sym.shndx.to_s == "UNDEF"

next unless exec

facts.add("target_function", "true")
facts.add("win_symbol", sym.name, sym.value.to_i)
end
Expand Down
14 changes: 14 additions & 0 deletions profiler/spec/auto_vuln_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,18 @@
facts = AugureProfiler::Profiler.new(bin).facts.to_s
expect(facts).not_to match(/vuln\("sof"\)/)
end

it "does not mistake a PLT stub for a win symbol in a companion library" do
lib = ProfilerBinaryBuilder.win_importer_lib
facts = AugureProfiler::Profiler.new(lib).facts.to_s
expect(facts).to match(/plt_addr\("ret2win"/)
expect(facts).not_to match(/win_symbol\("ret2win"/)
end

it "does not emit a module-relative win address from a shared object" do
lib = ProfilerBinaryBuilder.win_definer_lib
facts = AugureProfiler::Profiler.new(lib).facts.to_s
expect(facts).not_to match(/win_symbol\("ret2win"/)
expect(facts).not_to match(/target_function\("true"\)/)
end
end
48 changes: 48 additions & 0 deletions profiler/spec/support/binary_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def importing_win(dir = Dir.mktmpdir("augure_win"))
_o, e, s = Open3.capture3("gcc", lib_src, "-shared", "-fPIC", "-o", lib)
raise "lib build failed: #{e}" unless s.success?

@last_built_lib = lib
src = File.join(dir, "importer.c")
# the volatile global keeps the relocation alive (the compiler
# cannot fold it away); the function is never called
Expand All @@ -78,6 +79,53 @@ def importing_win(dir = Dir.mktmpdir("augure_win"))
bin
end

# The companion-library shape: a .so that IMPORTS the win function
# (its PLT stub carries the win name at a stub address). The win
# symbol heuristic must not mistake the stub for a callable target:
# a [qword win] payload pointing at a PLT stub jumps into the
# resolver, not at the win.
def win_importer_lib(dir = Dir.mktmpdir("augure_winlib"))
src = File.join(dir, "libpwn.c")
File.write(src, <<~C)
#include <unistd.h>
extern void ret2win(void);
__attribute__((noinline)) void pwnme(void) {
char small[8];
read(0, small, 64);
ret2win();
}
C
lib = File.join(dir, "libpwn.so")
# -fcf-protection: the .plt.sec layout (CFI) is where metasm
# materializes the stub as a defined FUNC symbol in .dynsym, the
# emporium companion libraries' shape
_o, e, s = Open3.capture3("gcc", src, "-shared", "-fPIC", "-o", lib,
"-fno-stack-protector", "-O0", "-fcf-protection=full")
raise "lib build failed: #{e}" unless s.success?

lib
end

# A shared object that DEFINES the win function: its symbol address
# is module-relative (ET_DYN), the merged fact base must not carry
# an address the runtime never maps at that value.
def win_definer_lib(dir = Dir.mktmpdir("augure_wdeflib"))
src = File.join(dir, "libwin.c")
File.write(src, <<~C)
#include <unistd.h>
__attribute__((noinline)) void ret2win(void) {
char small[8];
read(0, small, 16);
}
C
lib = File.join(dir, "libwin2.so")
_o, e, s = Open3.capture3("gcc", src, "-shared", "-fPIC", "-o", lib,
"-fno-stack-protector", "-O0")
raise "lib build failed: #{e}" unless s.success?

lib
end

# A sized copy whose length is an immediate too small to reach the
# return slot: bounded, not the overflow. -fno-builtin keeps memcpy a
# real call (the compiler would fold it away otherwise).
Expand Down
Loading