目录

  1. 1. 前言
  2. 2. [HNCTF 2022 Week1]超级签到
  3. 3. [HNCTF 2022 Week1]贝斯是什么乐器啊?
  4. 4. [HNCTF 2022 Week1]X0r
  5. 5. [HNCTF 2022 Week1]给阿姨倒一杯Jvav
  6. 6. [HNCTF 2022 Week1]你知道什么是Py嘛?
  7. 7. [HNCTF 2022 Week1]Little Endian
  8. 8. [HNCTF 2022 Week1]CrackMe
  9. 9. [HNCTF 2022 WEEK2]Easy_Android
  10. 10. [HNCTF 2022 WEEK2]Try2Bebug_Plus
  11. 11. [HNCTF 2022 WEEK2]e@sy_flower
  12. 12. [HNCTF 2022 WEEK2]TTTTTTTTTea

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

HNCTF RE刷题记录

2023/4/17 Reverse 刷题
  |     |   总文章阅读量:

前言

官方WP

CTF快速入门手册推荐,于是从这里开始刷题,也算是正式入门的开始

[HNCTF 2022 Week1]超级签到

ida简单使用

下载题目附件得到exe程序,拖入ida并f5查看main_0函数

image-20230417220106196

观察伪代码发现最底下判断正误条件是str1Str2是否相等

flag应该是Str2对应的字符串

同时注意到Str2被上面的for循环中的if语句处理过

按R把ASCll码转换为char字符

image-20230417220658185

发现是把其中的o转换成0

然后双击Str2查看内存发现对应的字符串

image-20230417221009988

按上面的逻辑替换可以得到flag为

NSSCTF{hell0_w0rld}


[HNCTF 2022 Week1]贝斯是什么乐器啊?

base64算法识别

下载题目附件得到exe程序,拖入ida并f5查看main()函数

image-20230419010602868

观察伪代码发现判断正误条件为encStr2是否相等

Str2是由我们输入的Str每一位ASCll值加上数组的下标,然后base64编码得到

双击enc查看内存发现对应的字符串

image-20230419010643821

逆向编写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()函数

image-20230419195315550

一个简单的异或加密程序

双击arr查看数组获得每个字符的16位值(这里也可以shift+e查看)

image-20230419195555272

image-20230419195829444

逆向编写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逆向

JD-GUI逆向工具

下载题目附件得到.class文件,拖入JD-GUI进行反编译

image-20230420234930904

一眼找到加密代码

逆向编写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函数进行反编译

image-20230505182433565

双击查看enc的x86汇编语言指令

image-20230505182740412

这是一条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}

long_to_bytes


[HNCTF 2022 Week1]CrackMe

Ollydbg动态调试

下载题目附件,

压缩包名字叫动调下断点

要求得到CreakMe的注册码

打开是一个注册框

image-20230716164557926

因为告诉我们要动态调试了,所以要用到ollydbg这个工具

ollydbg学习链接,工具可以在吾爱论坛上面获取

把exe程序扔进olldbg中

使用字符串查找插件找到判断正误语句的字符串

image-20230716164644132

然后对这个if语句f2设置断点,然后f9运行

image-20230716172518679

name为CreakMe,然后先随便输个序列号进去check一下

image-20230716173052504

然后一直f8继续运行到程序出现报错框

image-20230716173129324

这个时候再回去点击上面的字符串String1和String2可以发现出现我们需要的正确的序列号了

这是因为是明文比较的serial

image-20230716173229796

则正确的序列号为4e1837FC4c4036D054786DA04c40

image-20230716173559543

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函数前面打个断点

image-20230717180607765

然后f9进入动调

image-20230717181134195

切换成流程图模式,可以发现2710h是sleep函数的参数,观察到它是传给RDI寄存器的

image-20230717181939106

f8单步步过到sleep函数前

image-20230717183138740

可以发现此时RDI寄存器中的值为0x2710

也就是说下一次f8之后RDI寄存器中的数值就会传给sleep函数

此时我们可以修改RDI寄存器的数值为较小的数字来避免sleep时间过长,双击RDI直接改为0即可

image-20230717183533427

然后我们可以继续f8步过,这样绕过了长时间的sleep

继续f8,蓝色条子到达funtion时,f7单步步进,进入该函数(f8会跳过函数,f7会进入函数)

接下来的几次f8都是进行for循环

image-20230717184009665

f8到for循环结束进入printf语句

此时我们看其操作的数组v3的内存地址

image-20230717184308687

到这里我们就可以看到flag了

NSSCTF{th1s_1s_flag}


[HNCTF 2022 WEEK2]e@sy_flower

花指令

下载附件,是32位,丢进ida反编译

在函数名那栏没找到main函数,应该是有花指令

找到汇编的main函数和错误处

image-20230717225009395

看到错误处有jmp指令,右键直接nop掉

然后回到汇编的main函数处,右键选择creat_function,就能编译为函数了

image-20230717230055345

然后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);
}

分析逻辑:

  1. 将用户输入的字符串存储在 Arglist 变量中

  2. 计算字符串长度并将其减去变量 v8 的长度的一半,结果存储在 v1 变量中

  3. 如果 v1 大于 0,则执行一个循环,交换数组 v8 中相邻的元素

  4. 对字符串中的每个字符与 0x30 进行异或,结果存储回 v8 数组中

  5. 结果为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;
}

分析逻辑:

  1. 输入字符串,存储在v5数组
  2. 将v5强制转换为整型指针v9,然后将v5中的值按顺序存储到v4数组中
  3. 然后,使用TEA算法对v4数组中的2个元素进行加密,加密后的结果存储回原位置。该过程重复3次,共加密6个元素
  4. 将v4数组的前6个元素与预定义的值进行比较

可以发现这里存在加密函数tea_encrypt

找到key的值

image-20230718160826897

分别为0x00010203,0x04050607,0x08090a0b,0x0c0d0e0f

同时我们可以在函数栏找到对应的解密函数

image-20230718113745266

网上找个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;
}