NGfish_mini-lctf_writeup

WriteUp比赛完就写了= =结果我拖到现在才整理发上来
mini-lctf是校内入门赛啊……
真正的dalao们都出题去了,没有参加,于是给我单挑打了个第五出来
然而因为没有新生在队内所以不给我奖品咯[掀桌!]
好歹我还ak了re和mobile!(虽然都是入门级的)
好咯,不管你,post一下题目的打包
链接: https://pan.baidu.com/s/1pKXa2rp 密码: gnbq

Web 50 (苏打学姐撞上碳酸钠了)

能看到源码为

1
2
3
4
5
6
7
8
sha1 conllision, Can you do it<!-- 
$key = "aa3OFF9m";
$pass = isset($_GET['pass']) ? $_GET['pass'] : "";
if ($pass != $key && sha1($pass)==sha1($key) ) {
echo $flag;
} else {
echo "sha1 conllision</br>";
} -->

获得pass,与key=”aa30FF9m”的sha1值比较

第一个想到的是sha1碰撞,试了下显然没什么效果,50分不应该很难

后来谷狗搜了下key的sha1值,发现了这个神奇的github

https://github.com/spaze/hashes

这个是php的问题了额

挑了个输入,得到flag

LCTF{conllision_is_so_difficult}

然后web就基本都不会做了= =

Misc 20 (回转十三位)

后面接着4个等号,base32编码,加上回转13位,想到ROT13,得到flag

Misc20 (Easy)

密文是LbbeCaarT3r}Fer{_i

典型栅栏密码

Misc60 (Document)

解压出来后用binwalk跑了下,发现了一堆隐藏的文件

直接后缀名改成.zip,是个压缩包

直接就在子文件夹中找到flag了

Misc100 (Noisy)

下载文件名是wav,音频文件嘛……后缀名改成.wmv,听一下是一段噪音

下了个Audacity分析,瞎搞一下

发现转成频谱图的时候

捕获

噢草,厉害了!

Misc150 (Sword Art Online)

一个txt文件,打开发现全是255, 255, 255

然后中间还夹杂着一些别的数据

第一反应就是RGB数据咯,统计了下共1080*1080组数据

写了个脚本复原一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#-*-coding:utf-8-*-

from PIL import Image, ImageDraw

import math

width = 1080
height = 1080

image = Image.new('RGB', (width, height))

colorfd = open('wtf.txt','r')

draw = ImageDraw.Draw(image)

for x in range(width):
for y in range(height):
s = colorfd.readline()
if x%4==1:
y1 = (y+270)%1080
elif x%4==2:
y1 = (y+540)%1080
elif x%4==3:
y1 = (y+810)%1080
else:
y1 = y
num = s.split(', ')
draw.point((x,y1), fill=(int(num[0]), int(num[1]), int(num[2])))

image.save('fix.jpg','jpeg')

当中一些点的排序有问题,导致图分成了四个部分= = (事实上是我错估了分辨率)

随意魔改了下脚本,虽然还是有问题,但是已经勉强能看出flag了2333333

fix

LCTF{C0ngratulation_y0u_g0t_1t}

Misc玩完了……时间不够不想继续尝试了T_T

Pwn20

捕获

这个,gdb调试能发现,写v2时直接溢出就能覆盖到v3,v3只要为非0就是true了

直接输入长点瞎搞一下就出来了……

Pwn80

捕获

同样,输入能覆盖到v2、v3,0x63 0x65这些还都是正常的字符, 不需要用python构造输入

gdb调试时确定下相关位置就稳了

pwn基本没怎么接触过,shellcode还不会写T_T

狗带吧!

Mobile20 (Log)

题目都说着Log了……

然后我直接反编译看了下代码

1

log的数据写到了jni里,这么正常思维就是连上真机/虚拟机调试DDMS看log出来的flag

然后咸鱼如我直接ida了一下lib包……(才20分怎么可能写得很复杂嘛!)

2

然后就出来了呗

Mobile50 (Base)

说实话这题我不知道考点在哪……代码混淆吗……

毕竟也就50分

就是这段代码

1

检测是否调试器链接,然后toast输出flag

懵逼了很久……后来我甚至发现了那算法就是base32阿噗

流程就是把写死在程序里的key base32变换一下输出,key转换一下顺序再base32 一下输出

结果就是直接装上真机,按一下按钮flag就出来了……

Mobile100 (Smali)

下载下来就是一个Smali代码

而且是经过一定的处理,不能用Smali2Java之类的工具转换成java代码

那么就直接阅读吧(真棒!!!刚好给我练一下阅读smali代码!)

smali代码指令可以到这查询

http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

很关键的几个字符串AES/ECB/PKCS5Padding MD5

最终结果是

1
2
00000080  const-string        v6, "falg"
00000084 invoke-static Log->d(String, String)I, v6, v4

Log输出flag(唔,为什么是falg)

代码不长,大致流程就是

字符串this_is_your_key ,传进Encrypt.handle()函数

用这返回结果作为AES加密的秘钥,

再用这返回结果调用AESEncrypt.Encryption()函数

最后加密结果把byte转回为16进制String输出

分析一下handle函数,其实就是把奇数位和偶数位的字符交换一下

由此复原函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.UnsupportedEncodingException;

public class Learn {
public static String handle(String key) throws UnsupportedEncodingException{
StringBuilder stringBuilder = new StringBuilder();
for(int i=0;i<key.length();i+=2){
stringBuilder.append(key.charAt(i+1));
stringBuilder.append(key.charAt(i));
}
return stringBuilder.toString();
}

public static void main(String[] args) throws Exception {
String key = "this_is_your_key";
String keyChange = handle(key);
System.out.println(keyChange);
CryptoTools cipher = new CryptoTools(keyChange.getBytes());
byte[] result = cipher.aesEncryptData(keyChange.getBytes());
System.out.println(CryptoTools.byte2HexString(result));
}

}

其中CryptoTools是自己写好的相应的AES加密的类

跑一下出结果

184b4e1bc353510f7841bb126d94259449055ef296e3b665dac507032e4c01e1

值得一提的是,在初始化秘钥的函数有点混淆的地方

就是当传入秘钥参数为空时会把空字符串求MD5作为秘钥,这是库中的一点处理,正常是空字符串求不了的吧……

刚开始还在这卡了点时间,怎么能如此的不熟练

然后就是眼瞎把最后加密用的字符串或秘钥各种看错,把key换成keyChange、keyChange换成key啥的阿噗,ctf真是个考验眼睛的比赛

估计是考虑到科普向,mobile的题就这三题(这才是我主场啊喂)

不过,我也蜜汁ak了re红红火火恍恍惚惚

Re60

跑一下程序运行时让输入flag,正确就会输出对应的flag

直接拖到ida用f5神器看一下

1

calLenEqual32,函数名说得挺清楚的

然后进到里面也是一个很简单的判断,输入长度为32即返回5

其中sub_3c4d函数就是很简单的一个strcmp函数,相等即返回5,输出flag

所以关键就是在sub_3c4c函数中

分析可知第一个参数是输入的字符串,第二个参数是内存中一段数据,第三个是结果

进到该函数看

2

在OD中调试能看到strupr是把字符串都转换为大写的函数

iHenStrlenLow是数据字符串的长度,Gap那个很复杂,但input和bc2e(自己改的名)长度应该是相同的,所以都不用管。

关键就是中间一段循环,调用的两个函数

两个函数也很简单,判断输入大小跟64和9的关系,把输入的字符-55、-48或+55、+48

我一开始写解密函数时还想着改写下这两个函数,仔细一看55+9=64,这两个函数时互补的

在OD调试中看到硬编码的字符串,长度32,直接写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<bits/stdc++.h>
using namespace std;
char str[] = "bc2e0d7f253d7fff9f9d56053d051f0b";

char fun(char in){
return in<=64? in-48 : in-55;
}

char fun2(char in){
return in<=9? in+48 : in+55;
}

void toUpper(){
for(int i=0;i<32;i++){
str[i] = toupper(str[i]);
}
}

//char rel[] = "17500E5A4DCC77B3475ADD736DAB2FB4";

int main(){


char result2[32];
//char result[32];
//char input[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
toUpper();
for(int i=0;i<32;i++){
char tmp;
rel[i] = fun(rel[i]);
tmp = rel[i] ^ fun(str[i]);
result2[i] = fun2(tmp);
}

// for(int i=0;i<32;i++){
// char tmp = fun(input[i]);
// result[i] = tmp ^ fun(str[i]);
// result[i] = fun2(result[i]);
// }

for(int i=0;i<32;i++){
cout<<result2[i];
}
cout<<endl;
return 0;
}

//input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
//result:17500E5A4DCC77B3475ADD736DAB2FB4

跑出来flag是AB7E032568F1084CD8C78B7650AE30BF

Re100 (Easy GUI)

windows下的gui逆向,输入正确的password

一般来说有两种定位的思路,一是查找相应的字符串,二是在GetDlgItem这类获取控制句柄的winapi中下断点

1

这里字符串一下子看不到有用的信息,于是在GetDlgItem下断点,OD跑出来

2

然后就可以定位到关键函数了,这里发挥自己的想象瞎搞一波

可以直接到ida相关地方f5

也可以直接OD动态调试看寄存器

像这样,稍微阅读汇编发现password长度为13

3

cmp地方下个断点,反复跑13次程序,就能骗出flag了,这样解密程序都不用写233333

flag: w1napi_1s_old

Re150 (Easy Linux)

拖到linux中跑来看看

结果发现data.dat文件不一起放过去的话是跑不动的

所以可以猜想一部分代码在data.dat中

然后拖进ida就发现了checkpassword函数

2

非常简单的逻辑,跳转到secret的地方,获得13位数据

3

写个程序跑一下就出来了

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h>
using namespace std;
char str[] = {0xFF,0xA0,0xAF,0xFC,0xA1,0xA9,0xFE,0x80,0xA5,0xA2,0xB9,0xB4};

int main(){
for(int i=0;i<13;i++){
printf("%c",str[i]^0xcc);
}
return 0;
}

//W3lc0me2Linux

flag W3lc0me2Linux

Re200 (壶中的大银河~Easy~)

然后后面这几题就真的学到很多东西了

这题提示着linux signal机制

1

ida看到main函数很简单,主要有alarm、pause函数

注意在gdb调试中,默认alarm信号是不传进程序的,会导致程序阻塞在pause的地方

学习了一轮signal机制,知道了这么一回事

signal函数 void (*signal(int signo, void (*handler)(int)))(int);

在接受到signo的信号后,会跳转到第二个参数(一个函数指针)去处理

这里主程序是发出了alarm的信号

ida中搜了一下signal,就能发现这个函数

2

跳转到handler里面

3

然后查看各个地址,0x8049B40中获得奇怪的字符串一个

写个程序就跑出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<bits/stdc++.h>
using namespace std;

char cmp[] = "8BVXznh]z^VXdAfC}PgE";

int main(){

char rel[20];
for(int i=0;i<20;i++){
if(i & 1){
rel[i] = cmp[i] ^ rel[0];
} else{
rel[i] = cmp[i] ^ 9;
}
}

for(int i=0;i<20;i++){
printf("%c",rel[i]);
}
printf("\n");
return 0;
}

flag: 1s_is_also_important

Re250 (蓬莱的玉枝)

也是一个winGUI

跟之前同样道理,定位到关键函数,用ida查看

1

这个函数非常简单!而且Seed是写死的,rand()每次运行程序都一样

那么思路就是获得0x0041c010的数据,写个程序跑出来

嗯……然后出来一堆奇怪的东西,所以这样不对吧

然后用OD调试,发现长度27,到这里会有判断……

2

又是累一点下个断点一次次跑然后出结果2333333

后来再想其实那个程序为什么会不对呢,有可能在某个函数对那块空间处理了一下

在OD中看那块地址空间果然跟原来的不一样了

后来果然找到了那个函数

3

咦……IsDebuggerPresent()函数

如果检测到在调试模式下变换出来的字符串是错误的

结果我就发现……用OD调试时IsDebuggerPresent()函数返回的是非!厉害了OD

同时用ida调试了一下,出来的是非0值,检测到在调试,再修改下程序,就跑出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;
//char ch[] = {0x17,0x14,0xE7,0x83,0xA4,0x63,0xAC,0x91,0xCB,0xC5,0x42,0x4A,0x8B,0x5B,0x9D,0x93,5,0x98,0x20,0xB1,0x29,0xC1,0x8C,0x53,0x92,0x8D,0x36}; //source

char ch[] = {0x12,0x11,0xE2,0x86,0xA1,0x66,0xA9,0x94,0xCE,0xC0,0x47,0x4F,0x8E,0x5E,0x98,0x96,0x9D,0x00,0x25,0xB4,0x2C,0xC4,0x89,0x56,0x97,0x88,0x33};

//char ch[] = {0x1E,0x1D,0xEE,0x8A,0xAD,0x6A,0xA5,0x98,0xC2,0xCC,0x4B,0x43,0x82,0x52,0x94,0x9A,0x91,0x0C,0x29,0xB8,0x20,0xC8,0x85,0x5A,0x9B,0x84,0x3F}; //wrong
int main(){
srand(17);
for(int i=0;i<27;i++){
printf("%c",(ch[i] ^ (rand() % 0xFFu)));
}
printf("\n");
return 0;
}

//LCTF{learn_more_think_more}

LCTF{learn_more_think_more}

Re350 (永遠の春夢)

嗯……做的时候提示还没出来

分析出来后成就感满满的啊噢!

这个比较麻烦我就直接用ida调试了,先看主操作函数

1

关键就是在sub_401621函数中了

2

刚开始就懵逼的发现前面有个异或的操作,后面有个a1(a4)这特么是个啥!

然后再前面传入的第一个参数一直分不清到底是数据还是程序

a2的长度刚好就是a1地址中数据的长度

这时看看汇编代码

看到一个call eax

3

所以那就是个函数咯

那前面异或?这是就想到动态自修改了

调试中看到这段汇编代码,这个就用不了f5了,直接阅读吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.text:00401584 push    ebp
.text:00401584 sub_401584 endp ; sp-analysis failed
.text:00401584
.text:00401585 mov ebp, esp
.text:00401587 mov eax, [ebp+8]
.text:0040158A mov eax, ds:dword_40EA40[eax*4]
.text:00401591 shl eax, 4
.text:00401594 movzx edx, al
.text:00401597 mov eax, [ebp+8]
.text:0040159A mov eax, ds:dword_40EA40[eax*4]
.text:004015A1 shr eax, 4
.text:004015A4 and eax, 0Fh
.text:004015A7 or eax, edx
.text:004015A9 movzx edx, al
.text:004015AC mov eax, [ebp+8]
.text:004015AF mov ds:dword_40EA40[eax*4], edx
.text:004015B6 mov eax, [ebp+8]
.text:004015B9 mov eax, dword_40A020[eax*4]
.text:004015C0 lea ecx, [eax+30h]
.text:004015C3 mov edx, 80808081h
.text:004015C8 mov eax, ecx
.text:004015CA mul edx
.text:004015CC mov eax, edx
.text:004015CE shr eax, 7
.text:004015D1 mov edx, eax
.text:004015D3 shl edx, 8
.text:004015D6 sub edx, eax
.text:004015D8 mov eax, ecx
.text:004015DA sub eax, edx
.text:004015DC mov edx, [ebp+8]
.text:004015DF mov dword_40A020[edx*4], eax
.text:004015E6 mov eax, 0
.text:004015EB pop ebp
.text:004015EC retn

大致就是每次取ds:dword_40EA40的值中的8位字符数据低四位与高四位交换一下

再把dword_40A020中的数据进行一点处理,这里似乎是取模?

然后结合外部f5出来的代码可以猜想对输入字符串都进行这个操作了

然后是第二个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:004015ED push    ebp                             ; DATA XREF: sub_4014C8+8Do
.text:004015EE mov ebp, esp
.text:004015F0 sub esp, 10h
.text:004015F3 mov eax, [ebp+8]
.text:004015F6 mov eax, ds:dword_40EA40[eax*4]
.text:004015FD mov [ebp-4], eax
.text:00401600 xor dword ptr [ebp-4], 55h
.text:00401604 mov eax, [ebp+8]
.text:00401607 mov eax, dword_40A020[eax*4]
.text:0040160E cmp eax, [ebp-4]
.text:00401611 jz short loc_40161A
.text:00401613 mov eax, 0
.text:00401618 jmp short locret_40161F

很简单的异或一下,然后比较

所以就可以写程序跑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using namespace std;

int chr[] = {0x61,0x31,0xdf,1,0xb2,0x30,0x51,0x31,0x70,0x93,0x32,0x70,0xd2,0xa2,0x33,0x93,0xe1,0xd2,0xe2,0x17,0x52};
int chr2[21];
int main(){
for(int i=0;i<21;i++){
long long d1 = chr[i] + 0x30;
long long e1 = d1*0x80808081;
long long e2 = e1>>32;
e2 = e2>>7;
long long e3 = e2<<8;

chr2[i] = d1-(e3-e2);
chr2[i] = chr2[i]^0x55;

int tmp = chr2[i];
int tmp2 = (tmp<<4) & 0xff;//获取低四位
int tmp3 = (tmp>>4) & 0xf; //获取高四位
chr2[i] = tmp2^tmp3;

}

for(int i=0;i<21;i++){
printf("%c",chr2[i]);
}
printf("\n");
return 0;
}

因为前面没分析出来mul 0x80808081的作用,所以就直接强行模拟了

后来看这结合上下代码似乎是一个取模的操作

跑出来结果

LCTF{SMC_is_excited!}

Excited!

×

赞助gif换电脑、吃双皮奶(逃

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. Web 50 (苏打学姐撞上碳酸钠了)
  2. 2. Misc 20 (回转十三位)
  3. 3. Misc20 (Easy)
  4. 4. Misc60 (Document)
  5. 5. Misc100 (Noisy)
  6. 6. Misc150 (Sword Art Online)
  7. 7. Pwn20
  8. 8. Pwn80
  9. 9. Mobile20 (Log)
  10. 10. Mobile50 (Base)
  11. 11. Mobile100 (Smali)
  12. 12. Re60
  13. 13. Re100 (Easy GUI)
  14. 14. Re150 (Easy Linux)
  15. 15. Re200 (壶中的大银河~Easy~)
  16. 16. Re250 (蓬莱的玉枝)
  17. 17. Re350 (永遠の春夢)
,