前言

第一次系统地做逆向入门,不会就跟着大佬的writeup,当作点些技能树

checkin

点击就送

moectf{Enjoy_yourself_in_Reverse_Engineering!!!}

HEX

010Editor打开拉到最后就有

moectf{Hello_Hex}

begin

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rbx
  char Str[108]; // [rsp+20h] [rbp-60h] BYREF
  int i; // [rsp+8Ch] [rbp+Ch]

  sub_4016D0(argc, argv, envp);
  puts("<---Welcome to moectf2022!--->");
  puts("Xor is very interesting and useful! You can learn it by various search engines.\n");
  printf("Input your flag, and I will check for you:");
  scanf("%s", Str);
  for ( i = 0; ; ++i )
  {
    v3 = i;
    if ( v3 >= strlen(Str) )
      break;
    Str[i] ^= 0x19u;
  }
  if ( !strcmp(Str, Str2) )
    puts("\nGood job!!! You know how to decode my flag by xor!");
  else
    puts("\nQwQ. Something wrong. Please try again. >_<");
  return 0;
}

对输入异或加密之后与加密的flag比较,点开Str2写个脚本

EXP1

str2 = [0x74,0x76,0x7c,0x7a,0x6d,0x7f,0x62,0x41,0x29,0x6b,0x46,0x28,0x6a,0x46,0x6a,0x29,0x46,0x70,0x77,0x6d,0x2a,0x6a,0x6d,0x70,0x77,0x7e,0x38,0x38,0x38,0x38,0x38,0x64]
for i in str2:
    print(chr(i^0x19),end="")

moectf{X0r_1s_s0_int3sting!!!!!}

EXP2

使用IDA-IDC脚本(一种语法和C很像的IDA脚本),以后研究下

#include <idc.idc>

static main()
{
    auto addr = 0x403020;
    auto i ,j;
    for(i=0;i<32;i++)
    {
        PatchByte(addr+i,Byte(addr+i)^0x19);
    }
}

base

__int64 __fastcall main()
{
  char a[50]; // [rsp+20h] [rbp-A0h] BYREF
  char inp[20]; // [rsp+60h] [rbp-60h] BYREF
  char de64[20]; // [rsp+80h] [rbp-40h] BYREF
  char base64[29]; // [rsp+A0h] [rbp-20h] BYREF

  _main();
  strcpy(base64, "1wX/yRrA4RfR2wj72Qv52x3L5qa=");
  text_46("Welcome to moectf,plz input your flag!\n");
  gets(inp);
  base64_decode(base64, de64);
  if ( !strcmp(de64, inp) )
    text_46("great!");
  else
    text_46("wrong!");
  gets(a);
  return 0i64;
}

base64换表,可以CyberChef,可以动态调试看内存(PE文件的调试我还不太会),也可以自己写脚本

EXP

import base64
import string

m = '1wX/yRrA4RfR2wj72Qv52x3L5qa='
new_table = 'abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ'
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
print(base64.b64decode(m.translate(str.maketrans(new_table,table))))

moectf{qwqbase_qwq}

源码

#include<stdio.h>
#include<string.h>
#include<time.h>
//const char * base64char  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char * base64char  = "abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 int base64_decode( char * base64, char * originChar )//base64解码
 {
     int i, j;
     unsigned char k;
     unsigned char temp[4];
     for ( i = 0, j = 0; base64[i] != '
#include<stdio.h>
#include<string.h>
#include<time.h>
//const char * base64char  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char * base64char  = "abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int base64_decode( char * base64, char * originChar )//base64解码
{
int i, j;
unsigned char k;
unsigned char temp[4];
for ( i = 0, j = 0; base64[i] != '\0' ; i += 4 )
{
memset( temp, 0xFF, sizeof(temp) );
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i] )
temp[0]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i+1] )
temp[1]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i+2] )
temp[2]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i+3] )
temp[3]= k;
}
originChar[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2))&0xFC)) |
((unsigned char)((unsigned char)(temp[1]>>4)&0x03));
if ( base64[i+2] == '=' )
break;
originChar[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4))&0xF0)) |
((unsigned char)((unsigned char)(temp[2]>>2)&0x0F));
if ( base64[i+3] == '=' )
break;
originChar[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6))&0xF0)) |
((unsigned char)(temp[3]&0x3F));
}
return j;
}
int main(){
char base64[]="1wX/yRrA4RfR2wj72Qv52x3L5qa=";
char de64[20];
char inp[20];
printf("Welcome to moectf,plz input your flag!\n");
gets(inp);
base64_decode(base64,de64);
if(!strcmp(de64,inp)){
printf("great!");
}else{
printf("wrong!");
}
char a [50];
gets(a);
return 0;
}
' ; i += 4 ) { memset( temp, 0xFF, sizeof(temp) ); for ( k = 0 ; k < 64 ; k ++ ) { if ( base64char[k] == base64[i] ) temp[0]= k; } for ( k = 0 ; k < 64 ; k ++ ) { if ( base64char[k] == base64[i+1] ) temp[1]= k; } for ( k = 0 ; k < 64 ; k ++ ) { if ( base64char[k] == base64[i+2] ) temp[2]= k; } for ( k = 0 ; k < 64 ; k ++ ) { if ( base64char[k] == base64[i+3] ) temp[3]= k; } originChar[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2))&0xFC)) | ((unsigned char)((unsigned char)(temp[1]>>4)&0x03)); if ( base64[i+2] == '=' ) break; originChar[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4))&0xF0)) | ((unsigned char)((unsigned char)(temp[2]>>2)&0x0F)); if ( base64[i+3] == '=' ) break; originChar[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6))&0xF0)) | ((unsigned char)(temp[3]&0x3F)); } return j; } int main(){ char base64[]="1wX/yRrA4RfR2wj72Qv52x3L5qa="; char de64[20]; char inp[20]; printf("Welcome to moectf,plz input your flag!\n"); gets(inp); base64_decode(base64,de64); if(!strcmp(de64,inp)){ printf("great!"); }else{ printf("wrong!"); } char a [50]; gets(a); return 0; }

EquationPy

题目给出一个.pyc文件,也就是python编译后的python字节码文件
可是python不是解释型语言吗,为什么也能编译?实际上,python和Java相似,都是运行在自己的虚拟机上以获得更高的跨平台能力,而pyc就是python虚拟机(PVM)的字节码

Uncompyle6可以将.pyc文件反编译为.py文件,但是似乎暂时还不支持python3.10,所以我选择使用在线工具
整出来这么一堆玩意

似乎是个方程组,提示可以用z3求解器求解

pip3 install z3-solver

具体使用方法可以参见这篇文章

EXP

from z3 import *
solver = Solver( )
flag = [Int(i) for i in range(0,22) ]
solver.add(flag[0]==ord('m'))
solver.add(flag[1]==ord('o'))
solver.add(flag[2]==ord('e'))
solver.add(flag[3]==ord('c'))
solver.add(flag[4]==ord('t'))
solver.add(flag[5]==ord('f'))
solver.add(flag[6]==ord('{'))
solver.add(flag[21]==ord('}'))
solver.add(flag[0] * 7072 + flag[1] * 2523 + flag[2] * 6714 + flag[3] * 8810 + flag[4] * 6796 + flag[5] * 2647 + flag[6] * 1347 + flag[7] * 1289 + flag[8] * 8917 + flag[9] * 2304 + flag[10] * 5001 + flag[11] * 2882 + flag[12] * 7232 + flag[13] * 3192 + flag[14] * 9676 + flag[15] * 5436 + flag[16] * 4407 + flag[17] * 6269 + flag[18] * 9623 + flag[19] * 6230 + flag[20] * 6292 + flag[21] * 57 == 10743134)
solver.add(flag[0] * 3492 + flag[1] * 1613 + flag[2] * 3234 + flag[3] * 5656 + flag[4] * 9182 + flag[5] * 4240 + flag[6] * 8808 + flag[7] * 9484 + flag[8] * 4000 + flag[9] * 1475 + flag[10] * 2616 + flag[11] * 2766 + flag[12] * 6822 + flag[13] * 1068 + flag[14] * 9768 + flag[15] * 1420 + flag[16] * 4528 + flag[17] * 1031 + flag[18] * 8388 + flag[19] * 2029 + flag[20] * 2463 + flag[21] * 32 == 9663091) 
solver.add(flag[0] * 9661 + flag[1] * 1108 + flag[2] * 2229 + flag[3] * 1256 + flag[4] * 7747 + flag[5] * 5775 + flag[6] * 5211 + flag[7] * 2387 + flag[8] * 1997 + flag[9] * 4045 + flag[10] * 7102 + flag[11] * 7853 + flag[12] * 5596 + flag[13] * 6952 + flag[14] * 8883 + flag[15] * 5125 + flag[16] * 9572 + flag[17] * 1149 + flag[18] * 7583 + flag[19] * 1075 + flag[20] * 9804 + flag[21] * 72 == 10521461) 
solver.add(flag[0] * 4314 + flag[1] * 3509 + flag[2] * 6200 + flag[3] * 5546 + flag[4] * 1705 + flag[5] * 9518 + flag[6] * 2975 + flag[7] * 2689 + flag[8] * 2412 + flag[9] * 8659 + flag[10] * 5459 + flag[11] * 7572 + flag[12] * 3042 + flag[13] * 9701 + flag[14] * 4697 + flag[15] * 9863 + flag[16] * 1296 + flag[17] * 1278 + flag[18] * 5721 + flag[19] * 5116 + flag[20] * 4147 + flag[21] * 52 == 9714028) 
solver.add(flag[0] * 2310 + flag[1] * 1379 + flag[2] * 5900 + flag[3] * 4876 + flag[4] * 5329 + flag[5] * 6485 + flag[6] * 6610 + flag[7] * 7179 + flag[8] * 7897 + flag[9] * 1094 + flag[10] * 4825 + flag[11] * 8101 + flag[12] * 9519 + flag[13] * 3048 + flag[14] * 3168 + flag[15] * 2775 + flag[16] * 4366 + flag[17] * 4066 + flag[18] * 7490 + flag[19] * 5533 + flag[20] * 2139 + flag[21] * 87 == 10030960) 
solver.add(flag[0] * 1549 + flag[1] * 8554 + flag[2] * 6510 + flag[3] * 6559 + flag[4] * 5570 + flag[5] * 1003 + flag[6] * 8562 + flag[7] * 6793 + flag[8] * 3509 + flag[9] * 4965 + flag[10] * 6111 + flag[11] * 1229 + flag[12] * 5654 + flag[13] * 2204 + flag[14] * 2217 + flag[15] * 5039 + flag[16] * 5657 + flag[17] * 9426 + flag[18] * 7604 + flag[19] * 5883 + flag[20] * 5285 + flag[21] * 17 == 10946682)
solver.add(flag[0] * 2678 + flag[1] * 4369 + flag[2] * 7509 + flag[3] * 1564 + flag[4] * 7777 + flag[5] * 2271 + flag[6] * 9696 + flag[7] * 3874 + flag[8] * 2212 + flag[9] * 6764 + flag[10] * 5727 + flag[11] * 5971 + flag[12] * 5876 + flag[13] * 9959 + flag[14] * 4604 + flag[15] * 8461 + flag[16] * 2350 + flag[17] * 3564 + flag[18] * 1831 + flag[19] * 6088 + flag[20] * 4575 + flag[21] * 9 == 10286414)
solver.add(flag[0] * 8916 + flag[1] * 8647 + flag[2] * 4522 + flag[3] * 3579 + flag[4] * 5319 + flag[5] * 9124 + flag[6] * 9535 + flag[7] * 5125 + flag[8] * 3235 + flag[9] * 3246 + flag[10] * 3378 + flag[11] * 9221 + flag[12] * 1875 + flag[13] * 1008 + flag[14] * 6262 + flag[15] * 1524 + flag[16] * 8851 + flag[17] * 4367 + flag[18] * 7628 + flag[19] * 9404 + flag[20] * 2065 + flag[21] * 9 == 11809388)
solver.add(flag[0] * 9781 + flag[1] * 9174 + flag[2] * 3771 + flag[3] * 6972 + flag[4] * 6425 + flag[5] * 7631 + flag[6] * 8864 + flag[7] * 9117 + flag[8] * 4328 + flag[9] * 3919 + flag[10] * 6517 + flag[11] * 7165 + flag[12] * 6895 + flag[13] * 3609 + flag[14] * 3878 + flag[15] * 1593 + flag[16] * 9098 + flag[17] * 6432 + flag[18] * 2584 + flag[19] * 8403 + flag[20] * 4029 + flag[21] * 30 == 13060508)
solver.add(flag[0] * 2511 + flag[1] * 8583 + flag[2] * 2428 + flag[3] * 9439 + flag[4] * 3662 + flag[5] * 3278 + flag[6] * 8305 + flag[7] * 1100 + flag[8] * 7972 + flag[9] * 8510 + flag[10] * 8552 + flag[11] * 9993 + flag[12] * 6855 + flag[13] * 1702 + flag[14] * 1640 + flag[15] * 3787 + flag[16] * 8161 + flag[17] * 2110 + flag[18] * 5320 + flag[19] * 3313 + flag[20] * 9286 + flag[21] * 74 == 10568195)
solver.add(flag[0] * 4974 + flag[1] * 4445 + flag[2] * 7368 + flag[3] * 9132 + flag[4] * 5894 + flag[5] * 7822 + flag[6] * 7923 + flag[7] * 6822 + flag[8] * 2698 + flag[9] * 3643 + flag[10] * 8392 + flag[11] * 4126 + flag[12] * 1941 + flag[13] * 6641 + flag[14] * 2949 + flag[15] * 7405 + flag[16] * 9980 + flag[17] * 6349 + flag[18] * 3328 + flag[19] * 8766 + flag[20] * 9508 + flag[21] * 65 == 12514783)
solver.add(flag[0] * 4127 + flag[1] * 4703 + flag[2] * 6409 + flag[3] * 4907 + flag[4] * 5230 + flag[5] * 3371 + flag[6] * 5666 + flag[7] * 3194 + flag[8] * 5448 + flag[9] * 8415 + flag[10] * 4525 + flag[11] * 4152 + flag[12] * 1467 + flag[13] * 5254 + flag[14] * 2256 + flag[15] * 1643 + flag[16] * 9113 + flag[17] * 8805 + flag[18] * 4315 + flag[19] * 8371 + flag[20] * 1919 + flag[21] * 2 == 10299950)
solver.add(flag[0] * 6245 + flag[1] * 8783 + flag[2] * 6059 + flag[3] * 9375 + flag[4] * 9253 + flag[5] * 1974 + flag[6] * 8867 + flag[7] * 6423 + flag[8] * 2577 + flag[9] * 6613 + flag[10] * 2040 + flag[11] * 2209 + flag[12] * 4147 + flag[13] * 7151 + flag[14] * 1011 + flag[15] * 9446 + flag[16] * 4362 + flag[17] * 3073 + flag[18] * 3006 + flag[19] * 5499 + flag[20] * 8850 + flag[21] * 23 == 11180727)
solver.add(flag[0] * 1907 + flag[1] * 9038 + flag[2] * 3932 + flag[3] * 7054 + flag[4] * 1135 + flag[5] * 5095 + flag[6] * 6962 + flag[7] * 6481 + flag[8] * 7049 + flag[9] * 5995 + flag[10] * 6233 + flag[11] * 1321 + flag[12] * 4455 + flag[13] * 8181 + flag[14] * 5757 + flag[15] * 6953 + flag[16] * 3167 + flag[17] * 5508 + flag[18] * 4602 + flag[19] * 1420 + flag[20] * 3075 + flag[21] * 25 == 10167536)
solver.add(flag[0] * 1489 + flag[1] * 9236 + flag[2] * 7398 + flag[3] * 4088 + flag[4] * 4131 + flag[5] * 1657 + flag[6] * 9068 + flag[7] * 6420 + flag[8] * 3970 + flag[9] * 3265 + flag[10] * 5343 + flag[11] * 5386 + flag[12] * 2583 + flag[13] * 2813 + flag[14] * 7181 + flag[15] * 9116 + flag[16] * 4836 + flag[17] * 6917 + flag[18] * 1123 + flag[19] * 7276 + flag[20] * 2257 + flag[21] * 65 == 10202212)
solver.add(flag[0] * 2097 + flag[1] * 1253 + flag[2] * 1469 + flag[3] * 2731 + flag[4] * 9565 + flag[5] * 9185 + flag[6] * 1095 + flag[7] * 8666 + flag[8] * 2919 + flag[9] * 7962 + flag[10] * 1497 + flag[11] * 6642 + flag[12] * 4108 + flag[13] * 6892 + flag[14] * 7161 + flag[15] * 7552 + flag[16] * 5666 + flag[17] * 4060 + flag[18] * 7799 + flag[19] * 5080 + flag[20] * 8516 + flag[21] * 43 == 10435786)
solver.add(flag[0] * 1461 + flag[1] * 1676 + flag[2] * 4755 + flag[3] * 7982 + flag[4] * 3860 + flag[5] * 1067 + flag[6] * 6715 + flag[7] * 4019 + flag[8] * 4983 + flag[9] * 2031 + flag[10] * 1173 + flag[11] * 2241 + flag[12] * 2594 + flag[13] * 8672 + flag[14] * 4810 + flag[15] * 7963 + flag[16] * 7749 + flag[17] * 5730 + flag[18] * 9855 + flag[19] * 5858 + flag[20] * 2349 + flag[21] * 71 == 9526385)
solver.add(flag[0] * 9025 + flag[1] * 9536 + flag[2] * 1515 + flag[3] * 8177 + flag[4] * 6109 + flag[5] * 4856 + flag[6] * 6692 + flag[7] * 4929 + flag[8] * 1010 + flag[9] * 3995 + flag[10] * 3511 + flag[11] * 5910 + flag[12] * 3501 + flag[13] * 3731 + flag[14] * 6601 + flag[15] * 6200 + flag[16] * 8177 + flag[17] * 5488 + flag[18] * 5957 + flag[19] * 9661 + flag[20] * 4956 + flag[21] * 48 == 11822714)
solver.add(flag[0] * 4462 + flag[1] * 1940 + flag[2] * 5956 + flag[3] * 4965 + flag[4] * 9268 + flag[5] * 9627 + flag[6] * 3564 + flag[7] * 5417 + flag[8] * 2039 + flag[9] * 7269 + flag[10] * 9667 + flag[11] * 4158 + flag[12] * 2856 + flag[13] * 2851 + flag[14] * 9696 + flag[15] * 5986 + flag[16] * 6237 + flag[17] * 5845 + flag[18] * 5467 + flag[19] * 5227 + flag[20] * 4771 + flag[21] * 72 == 11486796)
solver.add(flag[0] * 4618 + flag[1] * 8621 + flag[2] * 8144 + flag[3] * 7115 + flag[4] * 1577 + flag[5] * 8602 + flag[6] * 3886 + flag[7] * 3712 + flag[8] * 1258 + flag[9] * 7063 + flag[10] * 1872 + flag[11] * 9855 + flag[12] * 4167 + flag[13] * 7615 + flag[14] * 6298 + flag[15] * 7682 + flag[16] * 8795 + flag[17] * 3856 + flag[18] * 6217 + flag[19] * 5764 + flag[20] * 5076 + flag[21] * 93 == 11540145)
solver.add(flag[0] * 7466 + flag[1] * 8442 + flag[2] * 4822 + flag[3] * 7639 + flag[4] * 2049 + flag[5] * 7311 + flag[6] * 5816 + flag[7] * 8433 + flag[8] * 5905 + flag[9] * 4838 + flag[10] * 1251 + flag[11] * 8184 + flag[12] * 6465 + flag[13] * 4634 + flag[14] * 5513 + flag[15] * 3160 + flag[16] * 6720 + flag[17] * 9205 + flag[18] * 6671 + flag[19] * 7716 + flag[20] * 1905 + flag[21] * 29 == 12227250)
solver.add(flag[0] * 5926 + flag[1] * 9095 + flag[2] * 2048 + flag[3] * 4639 + flag[4] * 3035 + flag[5] * 9560 + flag[6] * 1591 + flag[7] * 2392 + flag[8] * 1812 + flag[9] * 6732 + flag[10] * 9454 + flag[11] * 8175 + flag[12] * 7346 + flag[13] * 6333 + flag[14] * 9812 + flag[15] * 2034 + flag[16] * 6634 + flag[17] * 1762 + flag[18] * 7058 + flag[19] * 3524 + flag[20] * 7462 + flag[21] * 11 == 11118093 )
solver.check()
result = solver.model()
for i in flag:
print(chr(result[i].as_long()),end="")  #as_long()方法可以将Int类对象转化为整数

moectf{z3_i5_he1pful!}

D_flat

C#题,主程序打开没东西,主要逻辑在DLL里

方法1

用工具dnSpy打开DLL文件就有了

// D_flate
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
private static void Main()
{
int f = 0;
int[] flag = new int[]
{
109,
111,
101,
99,
116,
102,
123,
68,
95,
102,
108,
97,
116,
101,
95,
105,
115,
95,
67,
95,
115,
104,
97,
114,
112,
33,
125
};
Console.WriteLine("In music theory, there is a note that has the same pitch as D flat.");
Console.WriteLine("Do you know it?\nNow plz input your flag:");
string input = Console.ReadLine();
byte[] byteArray = Encoding.ASCII.GetBytes(input);
for (int i = 0; i < input.Length; i++)
{
if (flag[i] == (int)byteArray[i])
{
f++;
}
}
if (f == flag.Length)
{
Console.WriteLine("TTTTTQQQQQQLLLLLLL!!! This is your flag!");
return;
}
Console.WriteLine("QwQ, plz try again.");
}

EXP

#include<iostream>
using namespace std;
int main(){
char flag[] ={109,111,101,99,116,102,123,68,95,102,108,97,116,101,95,105,115,95,67,95,115,104,97,114,112,33,125};
for(int i;flag[i-1]!=125;i++) cout<<flag[i];
return 0;
}

moectf{D_flate_is_C_sharp!}

方法2

用x64dbg动态调试
断点随便下在入口点,让我们输入的内容可以长一些,因为必定会有一段循环来校验我们的输入和flag的内容
观察到有循环在校验的时候右键Follow In Dump就能看到flag了

Android Cracker

安卓逆向入门,用工具JEB打开就有了

要考虑JDK兼容性,不想下了,用别人的图算了(才不是偷懒)

moectf{Andr01d_1s_so00oo_e@sy_t0_cr4ck!!!}

ezTEA

TEA加密算法是一种非常轻量的分组加密算法,在逆向工程中非常常见

题目源码

#include <stdio.h>
#include <stdint.h>
void encrypt (uint32_t* v, uint32_t* k) {    // 主要加密函数,试着搞定它
uint32_t v0 = v[0], v1 = v[1], sum = 0;
uint32_t delta = 0xd33b470;
for (int i = 0; i < 32; i++) {
sum += delta;
v0 += ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>5) + k[1]);
v1 += ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>5) + k[3]);
}
v[0] = v0;
v[1] = v1;
}
int main() {
uint32_t k[4] = {1, 2, 3, 4};
int8_t input[33] = {0};
scanf("%32s", input);
for (int i = 0; i < 32; i+=8) {
uint32_t v[2] = {*(uint32_t *)&input[i], *(uint32_t *)&input[i+4]};
encrypt(v, k);
for (int j = 0; j < 2; j++) {    // 这一段主要是把 v 按单字节输出,另外可以了解一下 “大小端序” 在这题是如何体现的
for (int k = 0; k < 4; k++) {
printf("%#x, ", v[j] & 0xff);
v[j] >>= 8;
}
}
}
return 0;
}

flag密文

0x17,0x65,0x54,0x89,0xed,0x65,0x46,0x32,0x3d,0x58,0xa9,0xfd,0xe2,0x5e,0x61,0x97,
0xe4,0x60,0xf1,0x91,0x73,0xe9,0xe9,0xa2,0x59,0xcb,0x9a,0x99,0xec,0xb1,0xe1,0x7d

由于TEA算法依赖于异或,所以我们只用简单实现一个逆变换,其他复制粘贴源码小改一下就行了

EXP

#include <stdio.h>
#include <stdint.h>
void decrypt(uint32_t *v, uint32_t *k)
{
uint32_t v0 = v[0], v1 = v[1], sum = 0;
uint32_t delta = 0xd33b470;
sum += delta * 32;
for (int i = 0; i < 32; i++)
{
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}
int main()
{
uint32_t k[4] = {1, 2, 3, 4};
int8_t input[33] = {0x17, 0x65, 0x54, 0x89, 0xed, 0x65, 0x46, 0x32, 0x3d, 0x58, 0xa9, 0xfd, 0xe2, 0x5e, 0x61, 0x97, 0xe4, 0x60, 0xf1, 0x91, 0x73, 0xe9, 0xe9, 0xa2, 0x59, 0xcb, 0x9a, 0x99, 0xec, 0xb1, 0xe1, 0x7d};
for (int i = 0; i < 32; i += 8)
{
uint32_t v[2] = {*(uint32_t *)&input[i], *(uint32_t *)&input[i + 4]};
decrypt(v, k);
for (int j = 0; j < 2; j++)
{
for (int k = 0; k < 4; k++)
{
printf("%c", v[j]);
v[j] >>= 8;
}
}
}
return 0;
}

moectf{Th3_TEA_!S_s0_t4s7y~~!!!}

fake_key

int __cdecl main(int argc, const char **argv, const char **envp)
{
char Str[112]; // [rsp+20h] [rbp-80h] BYREF
int v5; // [rsp+90h] [rbp-10h]
int v6; // [rsp+94h] [rbp-Ch]
int j; // [rsp+98h] [rbp-8h]
int i; // [rsp+9Ch] [rbp-4h]
sub_401800(argc, argv, envp);
v6 = strlen(byte_403040);
puts("I changed the key secretly, you can't find the right key!");
puts("And I use random numbers to rot my input, you can never guess them!");
puts("Unless you debug to get the key and random numbers...");
puts("Now give me your flag:");
scanf("%s", Str);
v5 = strlen(Str);
for ( i = 0; i < v5; ++i )
Str[i] ^= byte_403040[i % v6];
for ( j = 0; j < v5; ++j )
Str[j] += rand() % 10;
if ( (unsigned int)sub_4015A2(Str, &unk_403020) )
puts("\nRight! TTTTTQQQQQLLLLL!!!");
else
puts("QwQ, plz try again.");
return 0;
}

我们输入的内容会经过两次简单的加密

第一次会跟key(byte_403040)进行异或加密(key不够长就从头循环),但是题目提醒我们这个key是假的,需要动态调试才现出真容

第二次会加上一个伪随机数,但是rand()没有设种子(默认种子为1),所以相当于伪随机数也被确定了
看大佬们还有动态调试看密文的偏移来计算每个随机数的,但是菜逼如我实在看不懂调试信息,就此作罢
还有一点是Linux下和Windows下rand()给定相同种子生成的随机数并不一样

EXP

#include <stdio.h>
#include <string.h>
int main()
{
char m[] = {0x15, 0x21, 0xf, 0x19, 0x25, 0x5b, 0x19, 0x39, 0x5f, 0x3a, 0x3b, 0x30, 0x74, 0x7, 0x43, 0x3f, 0x9, 0x5a, 0x34, 0xc, 0x74, 0x3f, 0x1e, 0x2d, 0x27, 0x21, 0x12, 0x16, 0x1f, 0x00};
char key[] = "yunzh1junTCL,trackYYDS";
int l0 = strlen(m);
int l1 = strlen(key);
for (int i = 0; i < l0; i++)
{
m[i] -= rand() % 10;
}
for (int i = 0; i < l0; i++)
{
m[i] ^= key[i % l1];
}
puts(m);
return 0;
}

moectf{D3bug_t0_g3t_7he_Key!}

源码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
unsigned char check[] = {21,33,15,25,37,91,25,57,95,58,59,48,116,7,67,63,9,90,52,12,116,63,30,45,39,33,18,22,31,0};
unsigned char key[100] = {"yunzh1jun"};
//moectf{D3bug_t0_g3t_7he_Key!}
__attribute((constructor)) static void fun()
{
strcat(key,"TCL,trackYYDS");
}
int main()
{
int len = strlen(key);
unsigned char input[100];
puts("I changed the key secretly, you can't find the right key!");
puts("And I use random numbers to rot my input, you can never guess them!");
puts("Unless you debug to get the key and random numbers...");
puts("Now give me your flag:");
scanf("%s", input);
int len_ipt = strlen(input);
for(int i = 0; i < len_ipt; i++)
input[i] ^= key[i % len];
for(int i = 0; i < len_ipt; i++)
input[i] += rand() % 10;
if(!strcmp(input, check))
puts("\nRight! TTTTTQQQQQLLLLL!!!");
else
puts("QwQ, plz try again.");
return 0;
}

EzRisc-V

checksec一下发现架构是64位Risc-V小端序

Arch:     em_riscv-64-little

IDA的Risc-V插件听说不太好用,就下了个Ghidra(NSA的开源逆向工具)

undefined8 main(void)
{
undefined8 local_70;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined4 local_50;
undefined2 local_4c;
undefined local_4a;
byte abStack_48 [52];
int local_14;
gp = _dl_static_dtv + 0x1c8;
local_70 = 0x4b425f4d5a5c5654;
local_68 = 0x4a08664f145a4a08;
local_60 = 0x6656560909564a66;
local_58 = 0x4d4a5c4b5c4d5708;
local_50 = 0x18005708;
local_4c = 0x1818;
local_4a = 0x44;
puts("Welcome to moeCTF 2022");
puts("Plz input your flag:");
__isoc99_scanf(&DAT_000584b0,abStack_48);
local_14 = 0;
while( true ) {
if (0x26 < local_14) {
printf("congratulations!!! you are right");
return 0;
}
if ((abStack_48[local_14] ^ 0x39) != *(byte *)((long)&local_70 + (long)local_14)) break;
local_14 = local_14 + 1;
}
printf("ops, wrong input!\nPlease try again");
gp = (undefined1 *)((long)_dl_static_dtv + 0x1c8);
return 0;
}

有点乱,但还是勉强能看懂,简单来说就是个基础的异或加密

EXP

注意是小端序

m = [0x54,0x56,0x5c,0x5a,0x4d,0x5f,0x42,0x4b,0x08,0x4a,0x5a,0x14,0x4f,0x66,0x08,0x4a,0x66,0x4a,0x56,0x09,0x09,0x56,0x56,0x66,0x08,0x57,0x4d,0x5c,0x4b,0x5c,0x4a,0x4d,0x08,0x57,0x00,0x18,0x18,0x18,0x44]
for i in m:
print(chr(i^0x39),end="")

moectf{r1sc-v_1s_so00oo_1nterest1n9!!!}

源码

#include<stdio.h>
int main(){
char input[50];
char enc_flag[] = {84, 86, 92, 90, 77, 95, 66, 75, 8, 74, 90, 20, 79, 102, 8, 74, 102, 74, 86, 9, 9, 86, 86, 102, 8, 87, 77, 92, 75, 92, 74, 77, 8, 87, 0, 24, 24, 24, 68};
printf("Welcome to moeCTF 2022\n");
printf("Plz input your flag:\n");
scanf("%s", input);
for(int i=0; i<39; i++){
if((input[i] ^ 0x39) != enc_flag[i]){
printf("ops, wrong input!\nPlease try again");
return 0;
}
}
printf("congratulations!!! you are right");
return 0;
}

Art

题目描述“给程序穿衣服”其实就是给程序加壳,PE文件最经典的加壳方式就是upx
壳的相关补充知识可以参看这篇文章

下载好upx之后可以加个环境变量方便使用
然后就是脱壳

upx -d filename

脱壳后反编译如下

int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[112]; // [rsp+20h] [rbp-60h] BYREF
char Str1[108]; // [rsp+90h] [rbp+10h] BYREF
int i; // [rsp+FCh] [rbp+7Ch]
sub_402030(argc, argv, envp);
puts("Do you know UPX???");
puts("Oh no...Something seems to be wrong...My equations has multiple solutions...");
puts("May be I can check it by a hash algorithm. You can never reverse it!!!");
printf("Input your flag:");
scanf("%s", Str1);
for ( i = 0; i <= 27; ++i )
v4[i] = Str1[i];
for ( i = 1; i <= 27; ++i )
Str1[i - 1] ^= (Str1[i - 1] % 17 + Str1[i]) ^ 0x19;
if ( !strcmp(Str1, &Str2) && (unsigned int)sub_401550(v4) )
puts("\nGood job!!! You know UPX and hash!!!");
else
puts("\nQwQ. Something wrong. Please try again. >_<");
return 0;
}

Str1[i - 1] ^= (Str1[i - 1] % 17 + Str1[i]) ^ 0x19;中求余这步需要爆破
我们已知flag第一个字母是“m”,那么就可以按ASCII码从头开始爆破(当然也可以用“}”从后往前爆破)

EXP

c = [0x02, 0x18, 0x0F, 0xF8, 0x19, 0x04, 0x27, 0xD8, 0xEB, 0x00, 0x35, 0x48, 0x4D, 0x2A,
0x45, 0x6B, 0x59, 0x2E, 0x43, 0x01, 0x18, 0x5C, 0x09, 0x09, 0x09, 0x09, 0xB5, 0x7D]
b = ord('m')
print('m',end = "")
for i in c:
for j in range(33,127):
t = j
m = b ^ ((b % 17) + j) ^ 0x19
if(m == i):
print(chr(j),end = "")
b = j
break

moectf{Art_i5_b14s7ing!!!!!}

Fake code

题目的hint给了很多前置知识和提示

总的来说就是Windows下程序有一种异常处理机制,但是由于IDA检测不到这里的执行流,所以反编译时会缺少一段程序

那么我们来分析一下这段异常处理的逻辑

try块:

tmp = (0x7F * tmp + 0x66) % 0xFF
t = tmp >> 7
t = 1 / t

except块:

key = (0x61 * key + 0x65) % 0xE9
key ^= 0x29

loc_140001212即为v7[i] ^= byte_140005010[dword_140005000];

总体的逻辑就是v5在循环中不断经历运算,然后v7根据当前的keydword_140005000来索引密钥数组byte_140005010进行异或加密,直到v5的第一位为0时就会跳到except块修改key的值以变换v7异或加密的密钥
所以我们只需用异或加密后的密文代替输入复现一次完整的执行流即可解密

EXP

c = [0x1E, 0x70, 0x7A, 0x6E, 0xEA, 0x83, 0x9E, 0xEF, 0x96, 0xE2, 0xB2, 0xD5, 0x99, 0xBB, 0xBB, 0x78, 0xB9, 0x3D, 0x6E, 0x38, 0x42, 0xC2, 0x86, 0xFF, 0x63, 0xBD, 0xFA, 0x79, 0xA3, 0x6D, 0x60, 0x94, 0xB3, 0x42, 0x11, 0xC3, 0x90, 0x89, 0xBD, 0xEF, 0xD4, 0x97, 0xF8, 0x7B, 0x8B, 0xB, 0x2D, 0x75, 0x7E, 0xDD, 0xCB]
table = [0xAC, 0x04, 0x58, 0xB0, 0x45, 0x96, 0x9F, 0x2E, 0x41, 0x15, 0x18, 0x29, 0xB1, 0x33, 0xAA, 0x12, 0x0D, 0x89, 0xE6, 0xFA, 0xF3, 0xC4, 0xBD, 0xE7, 0x70, 0x8A, 0x94, 0xC1, 0x85, 0x9D, 0xA3, 0xF2, 0x3F, 0x82, 0x8E, 0xD7, 0x03, 0x93, 0x3D, 0x13, 0x05, 0x6B, 0x41, 0x03, 0x96, 0x76, 0xE3, 0xB1, 0x8A, 0x4A, 0x22, 0x55, 0xC4, 0x19, 0xF5, 0x55, 0xA6, 0x1F, 0x0E, 0x61, 0x27, 0xCB, 0x1F, 0x9E, 0x5A, 0x7A, 0xE3, 0x15, 0x40, 0x94, 0x47, 0xDE, 0x00, 0x01, 0x91, 0x66, 0xB7, 0xCD, 0x22, 0x64, 0xF5, 0xA5, 0x9C, 0x68, 0xA5, 0x52, 0x86, 0xBD, 0xB0, 0xDD, 0x76, 0x28, 0xAB, 0x16, 0x95, 0xC5, 0x26, 0x2C, 0xF6, 0x39, 0xBE, 0x00, 0xA5, 0xAD, 0xE3, 0x93, 0x9E, 0xE3, 0x05, 0xA0, 0xB0, 0x1D, 0xB0, 0x16, 0x0B, 0x5B, 0x33, 0x95, 0xA4, 0x09, 0x16, 0x87, 0x56, 0x1F, 0x83, 0x4E, 0x4A, 0x3C, 0x55, 0x36, 0x6F, 0xBB, 0x4C, 0x4B, 0x9D, 0xB1, 0xAE, 0xE5, 0x8E, 0xC8, 0xFB, 0x0E, 0x29, 0x8A, 0xBB, 0xFC, 0x20, 0x62, 0x04, 0x2D, 0x80, 0x61, 0xD6, 0xC1, 0xCC, 0x3B, 0x89, 0xC5, 0x8B, 0xD5, 0x26, 0x58, 0xD6, 0xB6, 0xA0, 0x50, 0x75, 0xAB, 0x17, 0x83, 0x7F, 0x37, 0x2B, 0xA0, 0x1D, 0x2C, 0xCF, 0xC7, 0xE0, 0xE5, 0x49, 0xC9, 0xFA, 0x6B, 0xC0, 0x98, 0x66, 0x99, 0x92, 0x00, 0x02, 0xD4, 0x75, 0x46, 0x22, 0x05, 0x35, 0xD1, 0x4B, 0xC5, 0xAD, 0xE0, 0x8E, 0x45, 0x3B, 0x50, 0x15, 0xB5, 0x2E, 0x85, 0x30, 0x89, 0x54, 0x12, 0xDE, 0xF1, 0x5A, 0xF0, 0x2B, 0xA7, 0x1B, 0x4A, 0x26, 0x5D, 0x98, 0xD4, 0xA1, 0xBE, 0xD1, 0x4D, 0x7E, 0x38, 0xDE, 0x0B, 0x0A, 0x54, 0xB8, 0x73, 0x6D, 0xAD, 0x8C, 0x1E, 0xD9, 0x31, 0x5F, 0x56, 0x7E, 0xBD, 0x48, 0x32, 0x98, 0x2E, 0x3E, 0xEB, 0xA2, 0x1D]
key = 0x19
tmp = 0
for i in range(len(c)):
tmp = (0x7f * tmp + 0x66) % 0xff
if tmp >> 7 ==0:
key = (0x61 * key + 0x65) % 0xe9
key ^= 0x29
print(chr(c[i] ^ table[key]),end = "")

moectf{Re4d_4ssemb1y_t0_g3t_the_m4gic_key_0f_Tr4ck}

源码

#include <stdio.h>
#include <string.h>
#include <Windows.h>
#include <exception>
int key = 0x19;
unsigned char box[] = { 172, 4, 88, 176, 69, 150, 159, 46, 65, 21, 24, 41, 177, 51, 170, 18, 13, 137, 230, 250, 243, 196, 189, 231, 112, 138, 148, 193, 133, 157, 163, 242, 63, 130, 142, 215, 3, 147, 61, 19, 5, 107, 65, 3, 150, 118, 227, 177, 138, 74, 34, 85, 196, 25, 245, 85, 166, 31, 14, 97, 39, 203, 31, 158, 90, 122, 227, 21, 64, 148, 71, 222, 0, 1, 145, 102, 183, 205, 34, 100, 245, 165, 156, 104, 165, 82, 134, 189, 176, 221, 118, 40, 171, 22, 149, 197, 38, 44, 246, 57, 190, 0, 165, 173, 227, 147, 158, 227, 5, 160, 176, 29, 176, 22, 11, 91, 51, 149, 164, 9, 22, 135, 86, 31, 131, 78, 74, 60, 85, 54, 111, 187, 76, 75, 157, 177, 174, 229, 142, 200, 251, 14, 41, 138, 187, 252, 32, 98, 4, 45, 128, 97, 214, 193, 204, 59, 137, 197, 139, 213, 38, 88, 214, 182, 160, 80, 117, 171, 23, 131, 127, 55, 43, 160, 29, 44, 207, 199, 224, 229, 73, 201, 250, 107, 192, 152, 102, 153, 146, 0, 2, 212, 117, 70, 34, 5, 53, 209, 75, 197, 173, 224, 142, 69, 59, 80, 21, 181, 46, 133, 48, 137, 84, 18, 222, 241, 90, 240, 43, 167, 27, 74, 38, 93, 152, 212, 161, 190, 209, 77, 126, 56, 222, 11, 10, 84, 184, 115, 109, 173, 140, 30, 217, 49, 95, 86, 126, 189, 72, 50, 152, 46, 62, 235, 162, 29};
unsigned char check[] = { 30,112,122,110,234,131,158,239,150,226,178,213,153,187,187,120,185,61,110,56,66,194,134,255,99,189,250,121,163,109,96,148,179,66,17,195,144,137,189,239,212,151,248,123,139,11,45,117,126,221,203,0 };
int FilterFunc(int dwExceptionCode)
{
if (dwExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
return EXCEPTION_EXECUTE_HANDLER;
return EXCEPTION_CONTINUE_SEARCH;
}
int my_strcmp(unsigned char* s1, unsigned char* s2)
{
int i = 0, f = 0;
while (i < 51)
if (s1[i] == s2[i++])
f++;
if (f == 51)
return 1;
else
return 0;
}
int main()
{
unsigned char input[100];
//unsigned char input[100] = {"moectf{Re4d_4ssemb1y_t0_g3t_the_m4gic_key_0f_Tr4ck}"};
int index = 0, tmp, i;
puts("Can you read my assembly in exception?");
puts("Give me your flag:");
scanf_s("%s", input, 100);
if (strlen((const char*)input) != 51)
{
puts("\nQwQ, please try again.");
return 0;
}
for (i = 0; i < 51; i++)
{
__try {
index = (0x7f * index + 0x66) % 0xff;
tmp = index >> 7;
tmp = 1 / tmp;
}
__except (FilterFunc(GetExceptionCode())) {
key = (97 * key + 101) % 233;
key ^= 0x29;
//printf("catch i = %d\n", i);
}
input[i] ^= box[key];
}
if (strcmp(check, input))
puts("\nTTTTTTTTTTQQQQQQQQQQQQQLLLLLLLLL!!!!");
else
puts("\nQwQ, please try again.");
return 0;
}

Broken_hash

int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
int i; // [rsp+20h] [rbp-C8h]
BOOL v5; // [rsp+24h] [rbp-C4h]
__int64 v6; // [rsp+28h] [rbp-C0h]
void (*v7)(void); // [rsp+38h] [rbp-B0h]
char v8[112]; // [rsp+60h] [rbp-88h] BYREF
v7 = (void (*)(void))sub_140001BE0;
if ( dword_1400053F0 )
v7 = (void (*)(void))sub_1400010A0;
puts("This is a surprise!");
sub_140001E80("Give me your flag: ");
sub_140001F00("%s", v8);
v6 = -1i64;
do
++v6;
while ( v8[v6] );
if ( v6 == 88 )
{
sub_1400010C0(v8);
for ( i = 0; i < 88; ++i )
{
v5 = dword_140005184 && dword_140005260[i] == dword_140005000[i];
dword_140005184 = v5;
if ( !v5 )
break;
}
v7();
if ( dword_140005184 )
sub_140001E80("%s", aTtttqqqqqqqlll);
else
sub_140001E80("%s", aWhatAPityPlzTr);
result = 0;
}
else
{
puts("Wrong length!");
result = 0;
}
return result;
}

具体没太看懂,运行起来功能大概是逐个加密比对你的输入和加密的flag(88位),一旦有错就退出比对输出错误信息

方法1

看提示是要patch程序然后爆破
既然是逐个比对,那么我们就让报错信息变成输出对的个数也就是循环变量i[rsp+0E8h+var_C8]),当然,还得改下格式化字符串aS_0才能正常输出

.text:0000000140001E47 loc_140001E47:                          ; CODE XREF: main+179↑j
.text:0000000140001E47                 mov     rdx, [rsp+0E8h+var_A8]
.text:0000000140001E4C                 lea     rcx, aS_0       ; "%s"
.text:0000000140001E53                 call    sub_140001E80
.text:0000000140001E58                 xor     eax, eax

改成

.text:00007FF715DF1E47 loc_7FF715DF1E47:                       ; CODE XREF: main+179↑j
.text:00007FF715DF1E47                 mov     rdx, [rsp+0E8h+var_C8]
.text:00007FF715DF1E4C                 lea     rcx, aS_0       ; "%d"
.text:00007FF715DF1E53                 call    sub_7FF715DF1E80
.text:00007FF715DF1E58                 xor     eax, eax

修改后调试变成这样

但是实际运行结果和调试结果并不相同
看了出题人的writeup后才得知出题人通过SEH来隐藏真正的输出,并加了反调试来让程序在调试时不进入异常

再将(SEH异常处理段,IDA有时不显示需要删掉IDA文件重新创建才能看到,这里逻辑不太看得懂)

.text:0000000140001DEF loc_140001DEF:                          ; DATA XREF: .rdata:00000001400038F0↓o
.text:0000000140001DEF ;   __except(loc_140002D40) // owned by 140001DE9
.text:0000000140001DEF                 lea     rax, dword_140005000
.text:0000000140001DF6                 add     rax, 160h
.text:0000000140001DFC                 mov     rcx, rax
.text:0000000140001DFF                 call    sub_140001050
.text:0000000140001E04                 lea     rax, dword_140005000
.text:0000000140001E0B                 add     rax, 160h
.text:0000000140001E11                 mov     rdx, rax
.text:0000000140001E14                 lea     rcx, aS_1       ; "%s"
.text:0000000140001E1B                 call    sub_140001E80
.text:0000000140001E20                 xor     eax, eax
.text:0000000140001E22                 jmp     short loc_140001E5A

改成(记得用空指令nop(0x90)来填充空出的内存,aS_1同样改成%d

text:00007FF6A0681DEF loc_7FF6A0681DEF:                       ; DATA XREF: .rdata:00007FF6A06838F0↓o
.text:00007FF6A0681DEF ;   __except(loc_7FF6A0682D40) // owned by 7FF6A0681DE9
.text:00007FF6A0681DEF                 lea     rax, dword_7FF6A0685000
.text:00007FF6A0681DF6                 add     rax, 160h
.text:00007FF6A0681DFC                 mov     rcx, rax
.text:00007FF6A0681DFF                 call    sub_7FF6A0681050
.text:00007FF6A0681E04                 mov     rdx, [rsp+0E8h+var_C8]
.text:00007FF6A0681E09                 nop
.text:00007FF6A0681E0A                 nop
.text:00007FF6A0681E0B                 nop
.text:00007FF6A0681E0C                 nop
.text:00007FF6A0681E0D                 nop
.text:00007FF6A0681E0E                 nop
.text:00007FF6A0681E0F                 nop
.text:00007FF6A0681E10                 nop
.text:00007FF6A0681E11                 nop
.text:00007FF6A0681E12                 nop
.text:00007FF6A0681E13                 nop
.text:00007FF6A0681E14                 lea     rcx, aS_1       ; "%d"
.text:00007FF6A0681E1B                 call    sub_7FF6A0681E80
.text:00007FF6A0681E20                 xor     eax, eax
.text:00007FF6A0681E22                 jmp     short loc_7FF6A0681E5A

后来才意识到实际上aS_1不改更好,EXP可以省去一段字符处理

现在实际运行也能有预期的回显了
然后我们通过遍历ASCII码来爆破flag

EXP1

使用subprocess模块

import subprocess
flag = 'moectf{'
for i in range(7,88):
for j in range(33,127):
t = flag + chr(j) * (88 - i)
p = subprocess.Popen(['C:/Users/asuka/Desktop/CTF/MoeCTF2022/Reverse/Broken_hash/Broken_hash.exe'],stdin = subprocess.PIPE,stdout = subprocess.PIPE,stderr = subprocess.PIPE)
p.stdin.write(t.encode())
p.stdin.close()
out = p.stdout.read()
p.stdout.close()
# print(chr(out[-1]))
num = out[-2]-48
if num >= 0 and num <= 9:
num = num*10+(out[-1]-48)
else:
num = out[-1]-48
if (num) > i:
flag += chr(j)
# print(flag)
break
print(flag)

moectf{F1nd_th3_SEH_7hen_B1a5t_My_Fla9_and_Y0u_Can_Get_A_Cup_Of_Milk_Tea_From_YunZh1Jun}
吐槽:也许是不断创建新进程的缘故,爆破过程是真的慢出天际

方法2

看大佬是通过遍历所有字符看密文,与flag密文构建一个映射关系来爆破的
流下了不会调试的泪水

EXP2

a = [0x64744C9A, 0x047C2FF1, 0xA2D74292, 0x85BEF77E, 0x711FCBF7, 0x669E1609, 0x6BBD9DB6, 0x6941C8A4, 0xB16E48B3, 0xDE321186, 0x5251E8C2, 0xFB8F95A7, 0x711FCBF7, 0xCB5C3FAD, 0x36568AF5, 0xFB8F95A7, 0x82ACF96A, 0x75DCD570, 0x7EF00E40, 0xFB8F95A7, 0x4BE9314A, 0xCB5C3FAD, 0xA2D74292, 0xDE321186, 0xFB8F95A7, 0x46927FA8, 0xB16E48B3, 0xD7C1A410, 0x567375C3, 0x711FCBF7, 0xFB8F95A7, 0x9C19F0F3, 0xD035E914, 0xFB8F95A7, 0x6941C8A4, 0x0B7D1395, 0xD7C1A410, 0xC87A7C7E, 0xFB8F95A7, 0xD7C1A410, 0xDE321186, 0x5251E8C2, 0xFB8F95A7, 0xD5380C52, 0xBEA99D3B, 0xCEDB7952, 0xFB8F95A7, 0x73456320,
0xD7C1A410, 0xDE321186, 0xFB8F95A7, 0x581D99E5, 0xA2D74292, 0x711FCBF7, 0xFB8F95A7, 0x06372812, 0xFB8F95A7, 0x73456320, 0xCEDB7952, 0xEF53E254, 0xFB8F95A7, 0x9F12424D, 0x669E1609, 0xFB8F95A7, 0x9C19F0F3, 0xFECF7685, 0x0B7D1395, 0x1833E8B1, 0xFB8F95A7, 0x9F66DD04, 0xA2D74292, 0xD7C1A410, 0xFB8F95A7, 0x6941C8A4, 0x866CAF4F, 0x047C2FF1, 0x64744C9A, 0xFB8F95A7, 0xD5380C52, 0xCEDB7952, 0xDE321186, 0x81453D43, 0xCB5C3FAD, 0xB16E48B3, 0xC578F843, 0xCEDB7952, 0xDE321186, 0xE38C6F07, 0x8B9E97A8, 0x8FDF9EDF, 0xD1868B96, 0x93AFD1D1, 0x8D8BDF85, 0x989EDF86, 0xDF91969E, 0xF5C3A0C1, 0x00000000][:88]
b = "abcdefghijklmnopqrstuvwxyz1234567890_!ABCDEFGHIJKLMNOPQRSTUVWXYZ{}aaaaaaaaaaaaaaaaaaaaaa"
c = [0x0D7C1A410, 0x9E3919E7, 0x85BEF77E, 0x5251E8C2, 0x0A2D74292, 0x669E1609, 0x5C1DFF11, 0x0CB5C3FAD, 0x0FECF7685, 0x0B0F33A9A, 0x1833E8B1, 0x0B7D1395, 0x64744C9A, 0x0DE321186, 0x47C2FF1, 0x0EF53E254, 0x1902B329, 0x866CAF4F, 0x4A528AE0, 0x711FCBF7, 0x0CEDB7952, 0x352B172C, 0x0AFEA7FF6, 0x3175EDAB, 0x0D035E914, 0x20D324AE, 0x0B16E48B3, 0x5C6054F, 0x36568AF5, 0x0BEA2375F, 0x567375C3, 0x0BF0FD0CB, 0x4BE9314A, 0x7F2A2EBE, 0x0C87A7C7E, 0x0BEA99D3B, 0x0FB8F95A7, 0x94D2FB03, 0x6372812, 0x46927FA8, 0x73456320, 0x4E3F843, 0x75DCD570, 0x6941C8A4,
0x581D99E5, 0x7EF00E40, 0x7A260E4D, 0x0C578F843, 0x17947C53, 0x786C70, 0x9C19F0F3, 0x1D795AC9, 0x9F12424D, 0x0AB021E08, 0x77ACB10, 0x0D1D0F68E, 0x82ACF96A, 0x9F66DD04, 0x8AD9BAF7, 0x0AAE6D8C9, 0x0E73CAEF, 0x0BFC92893, 0x0D5380C52, 0x81453D43, 0x6BBD9DB6, 0x0E38C6F07, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410, 0x0D7C1A410]
for i in a:
print(b[c.index(i)], end="")

源码

#include <stdio.h>
#include <string.h>
#include <Windows.h>
#include <exception>
#include "sha1.h"
int sha1_test(BYTE* input);
int Filter_div0(int dwExceptionCode);
void sha1(BYTE* input);
void decode(unsigned int* string);
void trigger_exception();
void hook();
#ifdef _WIN64
#pragma comment(linker,"/INCLUDE:_tls_used")
#else
#pragma comment(linker,"/INCLUDE:__tls_used")
#endif // _WIN64
void NTAPI MY_TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved);
//TLS
void NTAPI MY_TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
dbg = IsDebuggerPresent();
}
extern "C"
#ifdef _WIN64
#pragma const_seg(".CRT$XLX")
const
#else
#pragma data_seg(".CRT$XLX")
#endif
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { MY_TLS_CALLBACK,0 };
#pragma data_seg()
#pragma const_seg()
unsigned int check[100];
unsigned int enc[] = { 0x64744c9a,0x47c2ff1,0xa2d74292,0x85bef77e,0x711fcbf7,0x669e1609,0x6bbd9db6,0x6941c8a4,0xb16e48b3,0xde321186,0x5251e8c2,0xfb8f95a7,0x711fcbf7,0xcb5c3fad,0x36568af5,0xfb8f95a7,0x82acf96a,0x75dcd570,0x7ef00e40,0xfb8f95a7,0x4be9314a,0xcb5c3fad,0xa2d74292,0xde321186,0xfb8f95a7,0x46927fa8,0xb16e48b3,0xd7c1a410,0x567375c3,0x711fcbf7,0xfb8f95a7,0x9c19f0f3,0xd035e914,0xfb8f95a7,0x6941c8a4,0xb7d1395,0xd7c1a410,0xc87a7c7e,0xfb8f95a7,0xd7c1a410,0xde321186,0x5251e8c2,0xfb8f95a7,0xd5380c52,0xbea99d3b,0xcedb7952,0xfb8f95a7,0x73456320,0xd7c1a410,0xde321186,0xfb8f95a7,0x581d99e5,0xa2d74292,0x711fcbf7,0xfb8f95a7,0x6372812,0xfb8f95a7,0x73456320,0xcedb7952,0xef53e254,0xfb8f95a7,0x9f12424d,0x669e1609,0xfb8f95a7,0x9c19f0f3,0xfecf7685,0xb7d1395,0x1833e8b1,0xfb8f95a7,0x9f66dd04,0xa2d74292,0xd7c1a410,0xfb8f95a7,0x6941c8a4,0x866caf4f,0x47c2ff1,0x64744c9a,0xfb8f95a7,0xd5380c52,0xcedb7952,0xde321186,0x81453d43,0xcb5c3fad,0xb16e48b3,0xc578f843,0xcedb7952,0xde321186,0xe38c6f07,0x8b9e97a8,0x8fdf9edf,0xd1868b96,0x93afd1d1,0x8d8bdf85,0x989edf86,0xdf91969e,0xf5c3a0c1,0 };
//moectf{F1nd_th3_SEH_7hen_B1a5t_My_Fla9_and_Y0u_Can_Get_A_Cup_Of_Milk_Tea_From_YunZh1Jun}
char right[] = { "TTTTQQQQQQQLLLLL!!!!!\nThis is your flag!!!\nHave fun in moectf2022 and insist on learning reverse engineering!\n" };
char wrong[] = { "What a pity...Plz try again >_<\n" };
int pass = 1, dbg = 0;
int main()
{
void (*phook)();
phook = trigger_exception;
if (dbg)
phook = hook;
BYTE input[100];
char* result;
puts("This is a surprise!");
printf("Give me your flag: ");
scanf_s("%s", input, 100);
if (strlen((const char*)input) != 88)
{
puts("Wrong length!");
return 0;
}
sha1(input);
for (int i = 0; i < 88; i++)
{
pass = pass && (check[i] == enc[i]);
if (!pass)
break;
}
__try {
phook();
}
__except (Filter_div0(GetExceptionCode())) {
decode(enc + 88);
printf("%s", (char*)(enc + 88));
return 0;
}
if (pass)
result = right;
else
result = wrong;
printf("%s", result);
return 0;
}
void sha1(BYTE* input)
{
pass = sha1_test(input);
}
int sha1_test(BYTE* input)
{
BYTE tmp[2];
BYTE buf[SHA1_BLOCK_SIZE];
SHA1_CTX ctx;
for (int j = 0; j < 88; j++)
{
tmp[0] = input[j];
sha1_init(&ctx);
sha1_update(&ctx, tmp, 1);
sha1_final(&ctx, buf);
check[j] = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
//printf("%#x,", check);
}
//printf("\n");
return pass;
}
void decode(unsigned int* string)
{
for (int i = 0; i < 8; i++)
string[i] ^= 0xffffffff;
}
void trigger_exception()
{
int i = 1 / pass;
}
void hook()
{
int i = 1 + pass;
}
int Filter_div0(int dwExceptionCode)
{
if (dwExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
return EXCEPTION_EXECUTE_HANDLER;
return EXCEPTION_CONTINUE_SEARCH;
}

chicken soup

题目描述告诉我们源码加了花指令

int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
char v4[100]; // [esp+10h] [ebp-68h] BYREF
puts("I poisoned the program... Can you reverse it?!");
puts("Come on! Give me your flag:");
sub_4012A0("%s", (char)v4);
if ( strlen(v4) == 38 )
{
((void (__cdecl *)(char *))loc_401000)(v4);
((void (__cdecl *)(char *))loc_401080)(v4);
if ( sub_401110(v4, &unk_403000) )
puts("\nTTTTTTTTTTQQQQQQQQQQQQQLLLLLLLLL!!!!");
else
puts("\nQwQ, please try again.");
result = 0;
}
else
{
puts("\nQwQ, please try again.");
result = 0;
}
return result;
}

F5后发现loc_401000和loc_401080两个函数无法反编译

可以在这两个函数的反汇编里发现多字节指令的特征(跳转到中间指令、插入指令E9
把两个E9改为nop指令即可重新反编译

loc_401000():每一位加上后一位

unsigned int __cdecl sub_401000(const char *a1)
{
unsigned int result; // eax
unsigned int i; // [esp+18h] [ebp-8h]
for ( i = 0; ; ++i )
{
result = strlen(a1) - 1;
if ( i >= result )
break;
a1[i] += a1[i + 1];
}
return result;
}

loc_401080():每一位左移四位或上右移四位

unsigned int __cdecl sub_401080(const char *a1)
{
unsigned int result; // eax
unsigned int i; // [esp+18h] [ebp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= strlen(a1) )
break;
a1[i] = (16 * a1[i]) | ((int)(unsigned __int8)a1[i] >> 4);
}
return result;
}

EXP

#include <stdio.h>
int main()
{
int flag[]={0xCD,0x4D,0x8C,0x7D,0xAD,0x1E,0xBE,0x4A,0x8A,0x7D,0xBC,0x7C,0xFC, 0x2E, 0x2A, 0x79,0x9D,0x6A,0x1A,0xCC,0x3D,0x4A,0xF8,0x3C,0x79,0x69,0x39,0xD9,0xDD,0x9D,0xA9,0x69,0x4C,0x8C,0xDD,0x59,0xE9,0xD7};
int i;
for(i=0;i<38;i++){
flag[i]=(flag[i]<<4)|(flag[i]>>4);
}
for(i=37;i>0;i--){
flag[i-1]-=flag[i];
}
for(i=0;i<38;i++){
printf("%c",flag[i]);
}
return 0;
}

moectf{p4tch_pr0gr4m_t0_d3c0mpi1e_it!}

python还是不太熟,所以换处理数组更顺手的C了
python版
有个大坑是需要加上&0xff来保证补码的一致性避免产生负数,具体参看

  • 详解 & 0xff 的作用(CSDN)
    • 还是不太明白,因为C可以不用这样,也许是因为python的位运算机制不太一样,记录一下
data = [0xCD, 0x4D, 0x8C, 0x7D, 0xAD, 0x1E, 0xBE, 0x4A, 0x8A, 0x7D, 0xBC, 0x7C, 0xFC, 0x2E, 0x2A, 0x79, 0x9D, 0x6A, 0x1A,0xCC, 0x3D, 0x4A, 0xF8, 0x3C, 0x79, 0x69, 0x39, 0xD9, 0xDD, 0x9D, 0xA9, 0x69, 0x4C, 0x8C, 0xDD, 0x59, 0xE9, 0xD7]
a = []
for i in data:
a.append((i << 4 | i >> 4) & 0xff)
b = []
tmp = a[len(a)-1]
for i in range(len(a)-1,-1,-1):
b.append(tmp)
tmp = a[i-1] - tmp
b = b[::-1]
for i in b:
print(chr(i), end="")

源码

#include<stdio.h>
#include<string.h>
unsigned char check[] = { 0xcd,0x4d,0x8c,0x7d,0xad,0x1e,0xbe,0x4a,0x8a,0x7d,0xbc,0x7c,0xfc,0x2e,0x2a,0x79,0x9d,0x6a,0x1a,0xcc,0x3d,0x4a,0xf8,0x3c,0x79,0x69,0x39,0xd9,0xdd,0x9d,0xa9,0x69,0x4c,0x8c,0xdd,0x59,0xe9,0xd7,0 };
//moectf{p4tch_pr0gr4m_t0_d3c0mpi1e_it!}
void enc1(unsigned char* input)
{
__asm {
jz label
jnz label
_emit 0xe9
label:
}
for (int i = 0; i < strlen((const char*)input) - 1; i++)
input[i] += input[i + 1];
}
void enc2(unsigned char* input)
{
__asm {
jz label
jnz label
_emit 0xe9
label:
}
for (int i = 0; i < strlen((const char*)input); i++)
input[i] = ((input[i] >> 4) | (input[i] << 4)) & 0xff;
}
int my_strcmp(unsigned char* s1, unsigned char* s2)
{
int i = 0, f = 0;
while (i < 38)
if (s1[i] == s2[i++])
f++;
if (f == 38)
return 1;
else
return 0;
}
int main()
{
unsigned char input[100];
puts("I poisoned the program... Can you reverse it?!");
puts("Come on! Give me your flag:");
scanf_s("%s", input, 100);
if (strlen((const char*)input) != 38)
{
puts("\nQwQ, please try again.");
return 0;
}
enc1(input);
enc2(input);
if(my_strcmp(input,check))
puts("\nTTTTTTTTTTQQQQQQQQQQQQQLLLLLLLLL!!!!");
else
puts("\nQwQ, please try again.");
return 0;
}

gogogo

因为WSL跑不动web服务加上不懂golang所以完全没有按照出题者的思路来做

看起来像是个golang写的web服务
反汇编是一大堆看不懂的东西,直接拉到底下那些main_xxxxx函数
发现有个main_check()main_AESEncrypt(),猜测题目是用AES加密输入并检测flag密文
AES加密需要16字节的Key和IV来进行加密,生成的密文长度也是16的倍数
通过观察反汇编代码发现两段一模一样的16字节HEX码(转换成ASCII码是---moeCTF2022---)、一段很长的未被反编译的数据段a200c2c3ef00f31

sub     rsp, 0A0h
mov     [rsp+0A0h+var_8], rbp
lea     rbp, [rsp+0A0h+var_8]
mov     [rsp+0A0h+arg_8], rbx
mov     [rsp+0A0h+arg_0], rax
mov     rcx, 5443656F6D2D2D2Dh
mov     [rsp+0A0h+str.len], rcx
mov     rdx, 2D2D2D3232303246h
mov     [rsp+60h], rdx
lea     rax, unk_772780
nop     dword ptr [rax+rax+00h]
call    runtime_newobject
iv_0 = rax              ; __uint8
mov     [rsp+0A0h+var_10], iv_0
mov     rcx, 5443656F6D2D2D2Dh
mov     [iv_0], rcx
mov     rcx, 2D2D2D3232303246h
mov     [iv_0+8], rcx
mov     rbx, [rsp+0A0h+arg_0]
mov     rcx, [rsp+0A0h+arg_8]
xor     eax, eax
call    runtime_stringtoslicebyte
lea     rdi, [rsp+0A0h+str.len] ; _r1
mov     esi, 10h        ; _r1
mov     r8, rsi
mov     r9, [rsp+0A0h+var_10]
mov     r10, rsi
mov     r11, rsi
call    main_AesEncrypt
nop
err = rdi               ; error_0
test    err, err
jnz     short loc_7424E5
lea     rbx, a200c2c3ef00f31 ; "200c2c3ef00f31999df93d6919aa33e42dde307"...
mov     ecx, 60h ; '`'
call    runtime_memequal
test    al, al
jnz     short loc_74257F

于是合理猜测Key和IV都是2D2D2D6D6F65435446323032322D2D2D---moeCTF2022---
密文通过尝试发现长度为96位(其实从mov ecx, 60h;也能猜出一二)
200c2c3ef00f31999df93d6919aa33e42dde307be02017ebf47067099ed0bddc525d5dba0f83c122159b89ae715907cc

最后丢进CyberChef解密成功
moeCTF{g0l@ng_1s_4n_1nte^est1n9_lan9ua9e}

我这wp太没营养了,这里丢出出题人和大佬的wp供参考

后记

第一次正经做逆向,虽然实际接触的不多,但是感觉还是非常有趣的
同样是和二进制打交道,Reverse和PWN带给我的感受和乐趣有些微妙的不同
其实一开始接触CTF时我原本打算定的方向就是Reverse(原因是想自己动手给喜欢的galgame做逆向),后来由于各种原因我最终选择了PWN作为首要方向
今后很想更多地和二进制打交道(其实是确实很想亲手给喜欢的galgame做逆向),所以偶尔也会去学习一些Reverse的知识,算是在这里立个flag吧

最后推荐个教galgame逆向的up,唤起我对逆向的奇怪热情的启蒙人