firmware
一个关于路由器固件的逆向
思路
UE打开看到文件开头的TP-LINK technologies等字样,应该是路由器相关的。binwalk查看bin文件,经过相关查询,发现最后的1
1180160 0x120200 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 2774624 bytes, 519 inodes, blocksize: 131072 bytes, created: 2015-04-13 09:35:04
为一个文件系统,将其分离出来1
dd if=firmware.bin skip=1180160 of=router-fs.squashfs bs=1
file得到文件大小后,再加上大小分离1
dd if=firmware.bin skip=1180160 of=fs.squashfs bs=1 count=2774624
尝试使用unsquashfs解包,报错。查询相关问题,确认是版本不对,查询到相关解压缩工具1
2
3su apt-get install build-essential zlib1g-dev liblzma-dev python-magic
git clone https://github.com/mirror/firmware-mod-kit.git
cd firmware-mod-kit/src && ./configure && make
之后使用unsquashfs_all.sh尝试解包得到squashfs_root,
原以为按题目提示是在/home/ctf/flag.txt中,但是又说了是后门网站及端口,
寻找到backor文件解upx在字符串中即可找到网站再通过交叉引用即可发现端口。
crackME
有反调试手段的程序
思路
需要输入用户名及密码来进行校验,用户名已知。
开始时根据用户名建了个表,在函数fn_401090处。
之后进行check。
先将输入的密码每两个字节转化为一个字节存储1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25while ( i < strlen(pass) )
{
if ( isdigit(pass[i]) )
{
v9 = pass[i] - '0';
}
else if ( isxdigit(pass[i]) ) // a-f
{
if ( *(_DWORD *)(*(_DWORD *)(__readfsdword(0x30u) + 24) + 12) != 2 ) //debug check
pass[i] = 0x22;
v9 = (pass[i] | 0x20) - 0x57;
}
else
{
v9 = ((pass[i] | 0x20) - 'a') % 6 + 10;
}
v10 = v9 + 0x10 * v10; // 每两个字节的输入转化为一个字节并将其存入V15中,eg:12abyz=>[0x12,0xab,0xab]
if ( !((signed int)(i + 1) % 2) )
{
v15[idx++] = v10;
len = idx;
v10 = 0;
}
++i;
}
之后将其与用户名建的表异或1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21while ( i_ < 8 )
{
v11 += user_rela[++j];
v13 = user_rela[j];
v8 = user_rela[v11];
user_rela[v11] = v13;
user_rela[j] = v8;
if ( *(_DWORD *)(__readfsdword(0x30u) + 0x68) & 0x70 )// debug check
v13 = v11 + j;
*(&v16 + i_) = user_rela[(unsigned __int8)(v8 + v13)] ^ v15[v5];
if ( *(_DWORD *)(__readfsdword(0x30u) + 2) & 0xFF )// debug check
{
v11 = 0xADu;
j = 0x2B;
}
sub_401710((int)&v16, (const char *)user, i_++);
v5 = i_;
if ( i_ >= (unsigned int)(&v15[strlen(v15) + 1] - &v15[1]) )
v5 = 0;
}
最后检测得到的结果是否为’dbappsec’
调试写脚本1
2
3
4
5
6
7
8
9
10re = 'dbappsec'
li = [0x2a,0xd7,0x92,0xe9,0x53,0xe2,0xc4,0xcd]
flag = [0 for _ in range(8)]
for i in range(8):
flag[i] = hex(ord(re[i])^li[i]).replace('0x','')
pwd = ''.join(flag)
import md5
print md5.new(pwd).hexdigest()
反调试手段
该程序使用的均为PEB结构的检查,通过FS:[30h]处得到PEB结构的基址1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
检测BeingDebugged
1
2
3.text:00401B4D mov eax, large fs:30h
.text:00401B55 mov eax, [eax+2]
.text:00401B5C test eax, eax检测ProcessHeap属性
PEB结构的0X18处,被设置为加载器为进程分配的第一个堆的位置,第一个堆头有一个属性字段用来告诉这个堆是否在调试器中创建(ForceFlags和Flags)
在XP中ForceFlags位于堆头部偏移量0x10处,在win7他在0x44处
同理在XP中偏移量0x0C或者win7系统中偏离量0x40中查看Flags属性
1 | .text:00401985 mov eax, large fs:30h |
- 检测NTGlobalFlag
PEB结构0x68处标志位,看是否为0x701
2
3
4.text:00401ADA mov eax, large fs:30h
.text:00401AE0 mov eax, [eax+68h]
.text:00401AE3 and eax, 70h
.text:00401AE6 test eax, eax
equation
jsfuck相关
思路
拿到脚本可以看到全是jsfuck来判断flag,看到里面有不少==判断,拿到在线解jsfuck的网站上对几个进行测试,可以确定是对flag中的值进行加减之后来校验,问题是太多了不可能一个个手动复制过去得到原等式。
查询看下别人的脚本,是将判断的那些先提取出到一个文件中,再对每一个’l[…]’(原先的…为正常的jsfuck的下标),修改为”l[‘+(…)+’]”,相当于对所有jsfuck表示的数字用”‘+(…)+””给包括住
1 | #include<cstdio> |
之后将等式在浏览器控制台运行即可得到等式
将其复制出来加入z3的Solver,得到flag
1 | from z3 import * |
signin
RSA相关逆向?
思路
可以直观的看到是个离散对数问题,反正是不可能硬解的。
然后是按rsa的思路来,模数是两个大素数的积,直接在factordb上分解得到pq,然后计算(p-1)*(q-1)
使用python计算的时候用下gmpy2库1
2
3
4apt-get install libgmp-dev
apt-get install libmpfr-dev
apt-get install libmpc-dev
pip install gmpy2
写下解密脚本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
c= 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
e = 65537
phin = (p-1)*(q-1)
import gmpy2
d= gmpy2.invert(e,phin)
m = pow(c,d,n)
flag = ''
m = hex(m)[2:]
print m
for i in range(len(m)/2):
flag += chr(int('0x'+m[2*i:2*(i+1)],16))
print flag
[GWCTF 2019] re3
SMC程序的逆向
思路
输入之后判断长度,然后对402219为起始地址处开始每个字节异或0x99,之后运行该函数。
这里需要写个IDC脚本来帮忙1
2
3
4
5
6
7
8
9
10
11#include <idc.idc>
static main()
{
auto addr = 0x402219;
auto i = 0;
for(i=0;i<=223;i++)
{
PatchByte(addr+i,Byte(addr+i)^0x99);
}
}
之后f5将其转化为伪代码,发现内部函数不少,怀疑其为加密算法,后面发现有AES的S盒,推测为AES,找到生成的密钥
写个脚本1
2
3
4
5
6
7
8from Crypto.Cipher import AES
k='cb8d493521b47a4cc1ae7e62229266ce'.decode('hex')
c='BC0AADC0147C5ECCE0B140BC9C51D52B46B2B9434DE5324BAD7FB4B39CDB4B5B'.decode('hex')
aes=AES.new(k)
mess = aes.decrypt(c)
print mess
maze_behind_junk
又一道迷宫
思路
查壳发现upx壳直接脱壳,之后可能因为中间掺杂了数据不能看伪代码,不过汇编也不长可以直接看
可以分析出来就是正常的迷宫switch,上下和左右分别控制两个变量,给了两张表,如果拿到里面是4的话就直接去最后的检查
分析出来是wasd控制上下左右,因为最后对两个变量进行检查,所以控制下次数就ok了,不过flag肯定不唯一,就不用交了
BJD hamburger competition
unity 逆向
思路
第一次碰到unity逆向,还是老八的小汉堡…,像是B站某个up做的…。看着程序挺正常。
使用dnspy逆向BJD hamburger competition_Data\Managed\Assembly-CSharp.dll,能够找到后门的关键函数(不太清楚是不是unity的关键函数都在这个dll里)1
2
3
4
5
6
7
8
9
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
43string name = this.toSpawn.name;
if (name == "汉堡底" && Init.spawnCount == 0)
{
Init.secret += 997;
}
else if (name == "鸭屁股")
{
Init.secret -= 127;
}
else if (name == "胡罗贝")
{
Init.secret *= 3;
}
else if (name == "臭豆腐")
{
Init.secret ^= 18;
}
else if (name == "俘虏")
{
Init.secret += 29;
}
else if (name == "白拆")
{
Init.secret -= 47;
}
else if (name == "美汁汁")
{
Init.secret *= 5;
}
else if (name == "柠檬")
{
Init.secret ^= 87;
}
else if (name == "汉堡顶" && Init.spawnCount == 5)
{
Init.secret ^= 127;
string str = Init.secret.ToString();
if (ButtonSpawnFruit.Sha1(str) == "DD01903921EA24941C26A48F2CEC24E0BB0E8CC7")
{
this.result = "BJDCTF{" + ButtonSpawnFruit.Md5(str) + "}";
Debug.Log(this.result);
}
}
选择不同的配料会对secret进行处理,最后对其sha1值进行比对,若相同,则计算其md5值即可得到flag,这里完全可以在线破解得到secret为1001。查了下出题人意思是除了写脚本得到选择的配料以外,还可以按老八原视频的顺序加…,魔鬼出题人无疑。
[RoarCTF2019]polyre
控制流平坦化的一道逆向
思路
几百行的while(1),人都看傻了。肯定不能就这么看,找到个基于angr的去控制流平坦化的脚本(https://github.com/cq674350529/deflat),效果还行,处理完分析程序流程。
程序里面还残留着不少相同的while(…)和if(…),看的时候基本上可以直接忽略掉,重点函数1
2
3
4
5
6
7
8
9
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
43for ( i = 0; ; ++i ) // i=0;i<6;i++
{
v18 = i;
v19 = v18 < 6;
if ( !v19 )
break;
v20 = s;
v4 = *(_QWORD *)&v20[8 * i];
v7 = 0;
while ( 1 )
{
v21 = v7;
v22 = v21 < 64; // 64次
if ( !v22 )
break;
v23 = v4;
v24 = v4 < 0;
if ( v4 >= 0 ) // 乘2
{
v27 = v4;
v28 = 2 * v27;
v4 = v28;
}
else
{
v25 = 2 * v4;
v26 = v25;
v4 = v26 ^ 0xB0004B7679FA26B3LL;
}
v29 = v7;
v7 = v29 + 1;
}
v30 = 8 * i;
v31 = &s1[8 * i];
*(_QWORD *)v31 = v4;
*(_QWORD *)v31 = v4;
i_ = i + 1;
}
把那些多余的去掉,剩下这些基本上可以看出来在干啥,基本上就是取出来八个字节一组然后进行64次循环,里面会通过对大小进行判断然后做处理。
这些处理完之后最后应该是与data段的一块数据做对比,所以逆向流程写脚本就可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19re = [0x96, 0x62, 0x53, 0x43, 0x6D, 0xF2, 0x8F, 0xBC, 0x16, 0xEE,
0x30, 0x05, 0x78, 0x00, 0x01, 0x52, 0xEC, 0x08, 0x5F, 0x93,
0xEA, 0xB5, 0xC0, 0x4D, 0x50, 0xF4, 0x53, 0xD8, 0xAF, 0x90,
0x2B, 0x34, 0x81, 0x36, 0x2C, 0xAA, 0xBC, 0x0E, 0x25, 0x8B,
0xE4, 0x8A, 0xC6, 0xA2, 0x81, 0x9F, 0x75, 0x55]
flag = ''
for i in range(0,48,8):
num = (re[i+7]<<56) | (re[i+6] <<48) | re[i+5]<<40 | re[i+4]<<32 | re[i+3]<<24 | re[i+2]<<16 | re[i+1]<<8 | re[i]
for _ in range(64):
if num&1:
num = (num ^ 0xB0004B7679FA26B3)//2
num += 0x8000000000000000
else:
num = num//2
meta = hex(num)[2:]
for j in range(len(meta)//2-1,-1,-1): #小端序表示,需要倒序输出
flag += chr(int('0x'+meta[j*2:(j+1)*2],16))
print(flag)
xxor
思路
本来挺简单的,但是因为python数据一般不会自动控制大小,所以写的时候麻烦了点。前面用z3解下后面的结果
1 | from z3 import * |
拿到之后对处理函数反过来写一下,主要是在中间每一步运算之后就要将其控制成四个字节,然后最后尝试使用了下libnum的n2s直接转化十六进制为字符串。1
2
3
4
5
6
7
8
9
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
36re = [0 for _ in range(6)]
re[2] = 3774025685
re[3] = 1548802262
re[4] = 2652626477
re[1] = 550153460
re[5] = 2230518816
re[0] = 3746099070
v5 = 0
for i in range(64):
v5 += 0x458BCD42
v5 %= 0x100000000
print(hex(v5)) #0x62F35080
import libnum
flag = ''
a = [0 for _ in range(2)]
for i in range(3):
v3 = re[i*2]
v4 = re[i*2+1]
v5 = 0x62F35080
for j in range(64):
v4 -= ((v3 + v5 + 20) ^ ((v3 << 6) + 3) ^ ((v3 >> 9) + 4) ^ 0x10)%0x100000000
v4 %= 0x100000000
v3 -= ((v4 + v5 + 11) ^ ((v4 << 6) + 2) ^ ((v4 >> 9) + 2) ^ 0x20)%0x100000000
v3 %= 0x100000000
v5 -= 0x458BCD42
v5 %= 0x100000000
flag += libnum.n2s(v3)
flag += libnum.n2s(v4)
print(flag)
[FlareOn5]Ultimate Minesweeper
挺有意思的.NET 逆向
思路
打开是个扫雷程序,随便点了几次全都是雷…。查壳没有,dnspy打开,基本上没做混淆,看了下几个函数,发现主要都在MainForm里面,注意到了有个GetKey函数,看着有随机数,寻找下调用,发现上面的函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private void SquareRevealedCallback(uint column, uint row)
{
if (this.MineField.BombRevealed)
{
this.stopwatch.Stop();
Application.DoEvents();
Thread.Sleep(1000);
new FailurePopup().ShowDialog();
Application.Exit();
}
this.RevealedCells.Add(row * MainForm.VALLOC_NODE_LIMIT + column);
if (this.MineField.TotalUnrevealedEmptySquares == 0)
{
this.stopwatch.Stop();
Application.DoEvents();
Thread.Sleep(1000);
new SuccessPopup(this.GetKey(this.RevealedCells)).ShowDialog();
Application.Exit();
}
}
这里就是检测有没有碰到雷的函数,可以看到就是成功的时候会调用GetKey应该就可以拿到flag。也可以看到计算位置的时候就是row*30+column。寻找下初始化扫雷方块的函数,找到了分配内存的函数,可以发现大多数都是直接为true,应该就是为地雷,而特殊的有专门得到flag为false的地方1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private void AllocateMemory(MineField mf)
{
for (uint num = 0u; num < MainForm.VALLOC_NODE_LIMIT; num += 1u)
{
for (uint num2 = 0u; num2 < MainForm.VALLOC_NODE_LIMIT; num2 += 1u)
{
bool flag = true;
uint r = num + 1u;
uint c = num2 + 1u;
if (this.VALLOC_TYPES.Contains(this.DeriveVallocType(r, c))) //here
{
flag = false;
}
mf.GarbageCollect[(int)num2, (int)num] = flag;
}
}
}
查看下跟这句话有关的地方1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private uint DeriveVallocType(uint r, uint c)
{
return ~(r * MainForm.VALLOC_NODE_LIMIT + c);
}
private static uint VALLOC_TYPE_HEADER_PAGE = 4294966400u;
private static uint VALLOC_TYPE_HEADER_POOL = 4294966657u;
private static uint VALLOC_TYPE_HEADER_RESERVED = 4294967026u;
private uint[] VALLOC_TYPES = new uint[]
{
MainForm.VALLOC_TYPE_HEADER_PAGE,
MainForm.VALLOC_TYPE_HEADER_POOL,
MainForm.VALLOC_TYPE_HEADER_RESERVED
};
也就是当计算的值为这几个时就为false,反过来计算下就行1
2
3
4
5
6
7
8def getrc(num):
num = ~num & 0xffffffff
c = num % 30
r = num // 30
return r,c
print(map(getrc,[4294966400,4294966657,4294967026]))
#[(29, 25), (21, 8), (8, 29)]
点击这三个方块即可拿到flag