前言
CTF快速入门手册推荐,于是从这里开始刷题,也算是正式入门的开始
[HNCTF 2022 Week1]超级签到
ida简单使用
下载题目附件得到exe程序,拖入ida并f5查看main_0函数
观察伪代码发现最底下判断正误条件是str1
和Str2
是否相等
flag应该是Str2
对应的字符串
同时注意到Str2
被上面的for循环中的if语句处理过
按R把ASCll码转换为char字符
发现是把其中的o
转换成0
然后双击Str2
查看内存发现对应的字符串
按上面的逻辑替换可以得到flag为
NSSCTF{hell0_w0rld}
[HNCTF 2022 Week1]贝斯是什么乐器啊?
base64算法识别
下载题目附件得到exe程序,拖入ida并f5查看main()函数
观察伪代码发现判断正误条件为enc
与Str2
是否相等
Str2
是由我们输入的Str
每一位ASCll值加上数组的下标,然后base64编码得到
双击enc
查看内存发现对应的字符串
逆向编写python脚本解码得到flag
from base64 import *
enc = 'TlJRQFBBdTs4alsrKFI6MjgwNi5p'
flag = b64decode(enc).decode()
for i in range(len(flag)):
print(chr(i + ord(flag[i])), end="")
>>> NSSCTF{B@se64_HAHAHA}
[HNCTF 2022 Week1]X0r
异或
下载题目附件得到exe程序,拖入ida并f5查看main()函数
一个简单的异或加密程序
双击arr
查看数组获得每个字符的16位值(这里也可以shift+e查看)
逆向编写python脚本获取flag
arr = [
0x3fe, 0x3eb, 0x3eb, 0x3fb, 0x3e4, 0x3f6, 0x3d3, 0x3d0, 0x388, 0x3ca,
0x3ef, 0x389, 0x3cb, 0x3ef, 0x3cb, 0x388, 0x3ef, 0x3d5, 0x3d9, 0x3cb,
0x3d1, 0x3cd
]
flag = ''
for i in range(len(arr)):
flag += chr((arr[i] - 900) ^ 0x34)
print(flag)
>>>NSSCTF{x0r_1s_s0_easy}
[HNCTF 2022 Week1]给阿姨倒一杯Jvav
java逆向
下载题目附件得到.class文件,拖入JD-GUI进行反编译
一眼找到加密代码
逆向编写python脚本进行解密得到flag
from base64 import *
key = [
180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129,
135, 191, 65
]
flag = ''
for i in range(len(key)):
flag += chr(key[i] - 64 ^ 0x20)
print(flag)
>>>This_is_the_flag_!
[HNCTF 2022 Week1]你知道什么是Py嘛?
python逆向加密
下载题目附件得到pyhton源码
s = str(input("please input your flag:"))
arr = [
29, 0, 16, 23, 18, 61, 43, 41, 13, 28, 88, 94, 49, 110, 66, 44, 43, 28, 91,
108, 61, 7, 22, 7, 43, 51, 44, 46, 9, 18, 20, 6, 2, 24
]
if (len(s) != 35 or s[0] != 'N'):
print("error")
exit(0)
for i in range(1, len(s)):
if (ord(s[i - 1]) ^ ord(s[i]) != arr[i - 1]):
print("error!")
exit(0)
print("right!")
可以得知flag也就是s
的值
从第一个if语句可以得知s
的长度为35且第一个字符为’N’
然后for循环中的语句是让s
的第i位值与前一位进行异或由此得到arr
据此逆向编写python脚本获得flag
arr = [
29, 0, 16, 23, 18, 61, 43, 41, 13, 28, 88, 94, 49, 110, 66, 44, 43, 28, 91,
108, 61, 7, 22, 7, 43, 51, 44, 46, 9, 18, 20, 6, 2, 24
]
flag = 'N'
for i in range(1,35):
temp = chr((arr[i - 1] ^ ord(flag[i - 1])))
flag += temp
print(flag)
>>>NSSCTF{Pyth0n_1s_th3_best_l@nguage}
[HNCTF 2022 Week1]Little Endian
小段存储
小端存储(Little Endian)是一种计算机数据存储的方式,在这种存储方式中,数值的低位(即数值的最右边)存储在内存的低地址处,而数值的高位(即数值的最左边)则存储在内存的高地址处。by ChatGPT
下载题目附件得到exe程序,拖入ida并f5对main函数进行反编译
双击查看enc
的x86汇编语言指令
这是一条x86汇编语言的指令,用于在内存中存储一系列32位的双字(DWORD)数据
该指令的语法为:
dd value1, value2, value3, ..., valuen
其中,value1、value2、value3等为32位无符号整数的十六进制表示
在这个例子中,dd指令将存储7个双字,分别为51670536h、5E4F102Ch、7E402211h、7C71094Bh、7C553F1Ch、6F5A3816h和两个0。
逆向编写python脚本获得flag
(因为小端存储数据是逆向存的 所以最后要旋转一下)
from Crypto.Util.number import *
enc = [0x51670536, 0x5e4f102c, 0x7e402211, 0x7c71094b, 0x7c553f1c, 0x6f5a3816]
for i in enc:
print(long_to_bytes(i ^ 0x12345678).decode()[::-1], end='')
>>>NSSCTF{Littl3_Endiannnn}
[HNCTF 2022 Week1]CrackMe
Ollydbg动态调试
下载题目附件,
压缩包名字叫动调下断点
要求得到CreakMe的注册码
打开是一个注册框
因为告诉我们要动态调试了,所以要用到ollydbg这个工具
ollydbg学习链接,工具可以在吾爱论坛上面获取
把exe程序扔进olldbg中
使用字符串查找插件找到判断正误语句的字符串
然后对这个if语句f2设置断点,然后f9运行
name为CreakMe
,然后先随便输个序列号进去check一下
然后一直f8继续运行到程序出现报错框
这个时候再回去点击上面的字符串String1和String2可以发现出现我们需要的正确的序列号了
这是因为是明文比较的serial
则正确的序列号为4e1837FC4c4036D054786DA04c40
flag:
NSSCTF{4e1837FC4c4036D054786DA04c40}
[HNCTF 2022 WEEK2]Easy_Android
安卓逆向
先了解一下安卓开发的基础CTF Wiki
需要用到Jadx进行反编译:https://github.com/skylot/jadx
下载题目附件,扔进Jadx进行反编译
在MainActivity的checkSN
方法中找到与flag相关的语句
public boolean checkSN(String userName, String sn) {
if (userName != null) {
try {
if (userName.length() == 0 || sn == null || sn.length() != 22) {
return false;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(userName.getBytes());
byte[] bytes = digest.digest();
String hexstr = toHexString(bytes, "");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
String userSN = sb.toString();
return new StringBuilder().append("flag{").append(userSN).append("}").toString().equalsIgnoreCase(sn);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false;
}
}
这个方法要传入userName和sn两个参数,其中userName可以通过跟踪checkSN
方法在上面的onCreate
方法找到
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle(R.string.unregister);
this.edit_userName = "Tenshine";
this.edit_sn = (EditText) findViewById(R.id.edit_sn);
this.btn_register = (Button) findViewById(R.id.button_register);
this.btn_register.setOnClickListener(new View.OnClickListener() { // from class: com.example.crackme.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
if (!MainActivity.this.checkSN(MainActivity.this.edit_userName.trim(), MainActivity.this.edit_sn.getText().toString().trim())) {
Toast.makeText(MainActivity.this, (int) R.string.unsuccessed, 0).show();
return;
}
Toast.makeText(MainActivity.this, (int) R.string.successed, 0).show();
MainActivity.this.btn_register.setEnabled(false);
MainActivity.this.setTitle(R.string.registered);
}
});
即MainActivity.this.edit_userName
,也就是”Tenshine”
然后对userSN
的加密算法进行分析
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(userName.getBytes());
byte[] bytes = digest.digest();
String hexstr = toHexString(bytes, "");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
String userSN = sb.toString();
使用Java内置的MessageDigest类,使用MD5算法计算userName的哈希值,并将结果转换为十六进制字符串。
然后将十六进制字符串中的每个偶数位字符提取出来,组成一个新的字符串
于是编写对应的python脚本
from hashlib import *
c = "Tenshine"
hash = md5(c.encode()).hexdigest()
flag ="NSSCTF{"+hash[::2]+"}"
print(flag)
flag:NSSCTF{bc72f242a6af3857}
[HNCTF 2022 WEEK2]Try2Bebug_Plus
IDA动态调试ELF
IDA远程调试的环境配置参考这篇文章
下载附件丢进ida64进行反编译
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 11; i += 2 )
{
argv = (const char **)&k;
decrypt((char *)&v + 4 * i, &k);
}
puts("waiting and i will give you flag!");
sleep(10000u);
function(&v, argv);
printf("sorry! something wrong!");
printf("please find the flag by Debug!");
return 0;
}
可以发现存在一个 sleep(10000u);
使程序运行需要等待大量时间
跟进function()
函数
unsigned __int64 __fastcall function(__int64 a1)
{
int i; // [rsp+1Ch] [rbp-24h]
char v3[12]; // [rsp+2Ch] [rbp-14h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0; i <= 11; ++i )
v3[i] = (16 * i) ^ *(_BYTE *)(4LL * i + a1);
printf("flag is %%s\n");
puts("sorry!!! I forget where is flag ");
return v4 - __readfsqword(0x28u);
}
那这段应该就是获取flag的代码
所以接下来动态调试的重点就是绕过sleep函数,然后进入function函数
所以我们先在sleep函数前面打个断点
然后f9进入动调
切换成流程图模式,可以发现2710h是sleep函数的参数,观察到它是传给RDI寄存器的
f8单步步过到sleep函数前
可以发现此时RDI寄存器中的值为0x2710
也就是说下一次f8之后RDI寄存器中的数值就会传给sleep函数
此时我们可以修改RDI寄存器的数值为较小的数字来避免sleep时间过长,双击RDI直接改为0即可
然后我们可以继续f8步过,这样绕过了长时间的sleep
继续f8,蓝色条子到达funtion时,f7单步步进,进入该函数(f8会跳过函数,f7会进入函数)
接下来的几次f8都是进行for循环
f8到for循环结束进入printf语句
此时我们看其操作的数组v3的内存地址
到这里我们就可以看到flag了
NSSCTF{th1s_1s_flag}
[HNCTF 2022 WEEK2]e@sy_flower
花指令
下载附件,是32位,丢进ida反编译
在函数名那栏没找到main函数,应该是有花指令
找到汇编的main函数和错误处
看到错误处有jmp指令,右键直接nop掉
然后回到汇编的main函数处,右键选择creat_function,就能编译为函数了
然后f5编译成c语言代码
void __noreturn sub_401091()
{
int v0; // edx
int v1; // esi
char v2; // cl
unsigned int i; // edx
int v4; // eax
char v5; // [esp+0h] [ebp-44h]
char v6; // [esp+0h] [ebp-44h]
char Arglist; // [esp+10h] [ebp-34h] BYREF
_BYTE v8[47]; // [esp+11h] [ebp-33h] BYREF
sub_401020("please input flag\n", v5);
sub_401050("%s", (char)&Arglist);
v0 = 0;
v1 = (int)(strlen(&Arglist) - (_DWORD)v8) >> 1;
if ( v1 > 0 )
{
do
{
v2 = v8[2 * v0 - 1];
v8[2 * v0 - 1] = v8[2 * v0];
v8[2 * v0++] = v2;
}
while ( v0 < v1 );
}
for ( i = 0; i < strlen(&Arglist); ++i )
v8[i - 1] ^= 0x30u;
v4 = strcmp(&Arglist, "c~scvdzKCEoDEZ[^roDICUMC");
if ( v4 )
v4 = v4 < 0 ? -1 : 1;
if ( !v4 )
{
sub_401020("yes", v6);
exit(0);
}
sub_401020("error", v6);
exit(0);
}
分析逻辑:
将用户输入的字符串存储在 Arglist 变量中
计算字符串长度并将其减去变量 v8 的长度的一半,结果存储在 v1 变量中
如果 v1 大于 0,则执行一个循环,交换数组 v8 中相邻的元素
对字符串中的每个字符与 0x30 进行异或,结果存储回 v8 数组中
结果为
c~scvdzKCEoDEZ[^roDICUMC
编写python脚本获得flag
enc = list('c~scvdzKCEoDEZ[^roDICUMC')
flag = []
for i in range(len(enc)):
flag.append(chr(ord(enc[i]) ^ 0x30))
for i in range(int(len(flag) / 2)):
tmp = flag[2 * i]
flag[2 * i] = flag[2 * i + 1]
flag[2 * i + 1] = tmp
for i in flag:
print(i, end='')
flag:NSSCTF{Just_junk_Bytess}
[HNCTF 2022 WEEK2]TTTTTTTTTea
Tea加密
下载题目附件,丢进ida64反编译
main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[16]; // [rsp+20h] [rbp-80h] BYREF
char v5[44]; // [rsp+60h] [rbp-40h] BYREF
int k; // [rsp+8Ch] [rbp-14h]
int j; // [rsp+90h] [rbp-10h]
int i; // [rsp+94h] [rbp-Ch]
int *v9; // [rsp+98h] [rbp-8h]
_main();
puts("please input your flag");
scanf("%s", v5);
v9 = (int *)v5;
for ( i = 0; i <= 5; ++i )
v4[i + 8] = *v9++;
for ( j = 0; j <= 2; ++j )
tea_encrypt(&v4[2 * j + 8], &key);
v4[0] = -1054939302;
v4[1] = -1532163725;
v4[2] = -165900264;
v4[3] = 853769165;
v4[4] = 768352038;
v4[5] = 876839116;
for ( k = 0; k <= 5; ++k )
{
if ( v4[k] != v4[k + 8] )
{
printf("ERROR!");
exit(9);
}
}
printf("you are right!");
return 0;
}
分析逻辑:
- 输入字符串,存储在v5数组
- 将v5强制转换为整型指针v9,然后将v5中的值按顺序存储到v4数组中
- 然后,使用TEA算法对v4数组中的2个元素进行加密,加密后的结果存储回原位置。该过程重复3次,共加密6个元素
- 将v4数组的前6个元素与预定义的值进行比较
可以发现这里存在加密函数tea_encrypt
找到key的值
分别为0x00010203,0x04050607,0x08090a0b,0x0c0d0e0f
同时我们可以在函数栏找到对应的解密函数
网上找个Tea解密的脚本对着改一改吧,本人能力不足感觉没啥好的思路。。。
#include<stdio.h>
int main()
{
unsigned int enc[6] = {0xC11EE75A, 0xA4AD0973, 0xF61C9018, 0x32E37BCD, 0x2DCC1F26, 0x344380CC};
unsigned int key[4] = {0x10203, 0x4050607, 0x8090A0B, 0x0C0D0E0F};
int i, j;
long sum = 0, delta = 0x61C88647;
// 解码
for(i=0;i < 6;i+=2){
sum = 0 - (32 * delta);
for(j = 0; j < 32; j++) {
enc[i+1] -= (((enc[i] >> 5) ^ (16 * enc[i])) + enc[i]) ^ (key[((sum >> 11) & 3)] + sum);
sum += delta;
enc[i] -= ((((enc[i+1] >> 5) ^ (16 * enc[i+1])) + enc[i+1]) ^ key[sum & 3] + sum);
}
}
// 打印
for (i = 0; i < 6; i++)
{
for (j = 0; j<=3; j++)
{
printf("%c", (enc[i] >> (j * 8)) & 0xFF);
}
}
return 0;
}