lib/pwnlib/util/getdents.rb
# encoding: ASCII-8BIT
# frozen_string_literal: true
require 'bindata'
require 'pwnlib/context'
module Pwnlib
module Util
# Helper methods related to getdents syscall.
module Getdents
# For inverse mapping of +linux_dirent#d_type+. +man getdents+ to see more information.
DT_TYPE_INVERSE = {
0 => 'UNKNOWN',
1 => 'FIFO',
2 => 'CHR',
4 => 'DIR',
6 => 'BLK',
8 => 'REG',
10 => 'LNK',
12 => 'SOCK'
}.freeze
# The +linux_dirent+ structure.
class Dirent < ::BinData::Record
attr_accessor :bits
# struct linux_dirent {
# unsigned long d_ino; /* Inode number */
# unsigned long d_off; /* Offset to next linux_dirent */
# unsigned short d_reclen; /* Length of this linux_dirent */
# char d_name[]; /* Filename (null-terminated) */
# /* length is actually (d_reclen - 2 -
# offsetof(struct linux_dirent, d_name)) */
# /*
# char pad; // Zero padding byte
# char d_type; // File type (only since Linux
# // 2.6.4); offset is (d_reclen - 1)
# */
# }
endian :big_and_little
choice :d_ino, selection: :bits, choices: { 32 => :uint32, 64 => :uint64 }
choice :d_off, selection: :bits, choices: { 32 => :uint32, 64 => :uint64 }
uint16 :d_reclen
string :d_name, read_length: -> { d_reclen - d_ino.num_bytes - d_off.num_bytes - 4 }
int8 :pad
int8 :d_type
end
module_function
# Parse the output of getdents syscall.
# For users to handle the shit-like output by +shellcraft.ls+ (e.g. {Shellcraft::Generators::X86::Linux#ls}).
#
# @param [String] binstr
# The content returns by getdents syscall.
#
# @return [String]
# Formatted output of filenames with file types.
#
# @example
# context.arch = 'i386'
# Util::Getdents.parse("\x92\x22\x0e\x01\x8f\x4a\xb3\x41" \
# "\x18\x00\x52\x45\x41\x44\x4d\x45" \
# "\x2e\x6d\x64\x00\x00\x00\x00\x08" \
# "\xb5\x10\x34\x01\xff\xff\xff\x7f" \
# "\x10\x00\x6c\x69\x62\x00\x00\x04")
# #=> "REG README.md\nDIR lib\n"
def parse(binstr)
str = StringIO.new(binstr)
result = StringIO.new
until str.eof?
ent = Dirent.new(endian: context.endian.to_sym)
ent.bits = context.bits
ent.read(str)
# NOTE: d_name might contains garbage after first "\x00", so we use gsub(/\x00.*/) instead of delete("\x00").
result.puts("#{DT_TYPE_INVERSE[ent.d_type]} #{ent.d_name.gsub(/\x00.*/, '')}")
end
result.string
end
include ::Pwnlib::Context
end
end
end