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!

作者

giglf

发布于

2017-02-21

更新于

2026-05-12

许可协议

评论