-
-
[原创]BUUCTF逆向题:[WUSTCTF2020]level3
-
2022-3-24 23:13 9026
-
1.基本信息探查:
1.EXEinfo:
64位,无壳,ELF文件
2.运行一下:
2.IDA分析:
1.主函数分析:
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 | int __cdecl main( int argc, const char * * argv, const char * * envp) { const char * v3; / / rax char v5; / / [rsp + Fh] [rbp - 41h ] char v6[ 56 ]; / / [rsp + 10h ] [rbp - 40h ] BYREF unsigned __int64 v7; / / [rsp + 48h ] [rbp - 8h ] v7 = __readfsqword( 0x28u ); printf( "Try my base64 program?.....\n>" ); __isoc99_scanf( "%20s" , v6); v5 = time( 0LL ); srand(v5); if ( (rand() & 1 ) ! = 0 ) { v3 = (const char * )base64_encode(v6); puts(v3); puts( "Is there something wrong?" ); } else { puts( "Sorry I think it's not prepared yet...." ); puts( "And I get a strange string from my program which is different from the standard base64:" ); puts( "d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD==" ); puts( "What's wrong??" ); } return 0 ; } |
函数的逻辑很清晰,v6接受输入,但却是随缘进入if判断因为它的进入条件是随机生成的而随机的种子是系统时间,假设进入了if里面,会将输入作为参数调用一个函数(看名字是base64编码),但是编码完后就直接输出,也不进行判断,奇奇怪怪的,这里看一下下方提示:
对不起,我想它还没有准备好....
我从程序中得到一个奇怪的字符串,它不同于标准的base64:
- “d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD==”
- 发生肾么事了??
先去在线网站将这个base64编码的字串解密看一下:
发现解码失败,根据上面的提示它说这个字符串不同于标准的base64,这里猜测base64_encode()
这个函数可能不是正常的base64,应该魔改了。
2.base64_encode()函数分析:
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 43 44 45 46 47 | char * __fastcall base64_encode(char * a1) { int v1; / / eax int v2; / / eax int v4; / / [rsp + 1Ch ] [rbp - 54h ] int v5; / / [rsp + 20h ] [rbp - 50h ] int v6; / / [rsp + 24h ] [rbp - 4Ch ] int v7; / / [rsp + 28h ] [rbp - 48h ] int v8; / / [rsp + 2Ch ] [rbp - 44h ] char src[ 56 ]; / / [rsp + 30h ] [rbp - 40h ] BYREF unsigned __int64 v10; / / [rsp + 68h ] [rbp - 8h ] v10 = __readfsqword( 0x28u ); v1 = strlen(a1); v8 = v1 % 3 ; v7 = v1 / 3 ; memset(src, 0 , 0x30uLL ); v6 = 0 ; v4 = 0 ; v5 = 0 ; while ( v4 < v7 ) { src[v6] = base64_table[a1[v5] >> 2 ]; src[v6 + 1 ] = base64_table[( 16 * (a1[v5] & 3 )) | (a1[v5 + 1 ] >> 4 )]; src[v6 + 2 ] = base64_table[( 4 * (a1[v5 + 1 ] & 0xF )) | (a1[v5 + 2 ] >> 6 )]; v2 = v6 + 3 ; v6 + = 4 ; src[v2] = base64_table[a1[v5 + 2 ] & 0x3F ]; v5 + = 3 ; + + v4; } if ( v8 = = 1 ) { src[v6] = base64_table[a1[v5] >> 2 ]; src[v6 + 1 ] = base64_table[ 16 * (a1[v5] & 3 )]; strcat(src, "==" ); } else if ( v8 = = 2 ) { src[v6] = base64_table[a1[v5] >> 2 ]; src[v6 + 1 ] = base64_table[( 16 * (a1[v5] & 3 )) | (a1[v5 + 1 ] >> 4 )]; src[v6 + 2 ] = base64_table[ 4 * (a1[v5 + 1 ] & 0xF )]; src[v6 + 3 ] = 61 ; } strcpy(a1, src); return a1; } |
这个函数就是这个程序的base64编码函数,先去看一下它的码表有没有问题:
可以看到码表没什么问题,那就看一下,base64的编码过程有没有问题了。
这里贴一下base64加密的原理:(以“ABC”为例):
- 首先将需要编码的字符串以3个字节为一组,放入一个24位缓冲区缺少的用0补齐,先来的字节占高位
- 然后将缓冲区的数据以每6bit为一组,共分成四组
- 将这四组数据高位补零,补齐一个字节,4组共32bit
- 之后将四个字节的数据与base64编码对照表一一对应
- 若原长度不是3的倍数且剩下1个输入数据,则在编码结果后加2个“=”;若剩下2个输入数据,则在编码结果后加1个“=”
修饰后的伪代码:
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 43 44 45 46 47 | char * __fastcall base64_encode(char * flag) { int v1; / / eax int v2; / / eax int i; / / [rsp + 1Ch ] [rbp - 54h ] int v5; / / [rsp + 20h ] [rbp - 50h ] int v6; / / [rsp + 24h ] [rbp - 4Ch ] int flag_len_3; / / [rsp + 28h ] [rbp - 48h ] int v8; / / [rsp + 2Ch ] [rbp - 44h ] char base_cache[ 56 ]; / / [rsp + 30h ] [rbp - 40h ] BYREF unsigned __int64 v10; / / [rsp + 68h ] [rbp - 8h ] v10 = __readfsqword( 0x28u ); v1 = strlen(flag); v8 = v1 % 3 ; flag_len_3 = v1 / 3 ; memset(base_cache, 0 , 0x30uLL ); v6 = 0 ; / / v6作为base_cache的下标 i = 0 ; v5 = 0 ; / / v5作为flag变量的下标 while ( i < flag_len_3 ) { base_cache[v6] = base64_table[flag[v5] >> 2 ]; base_cache[v6 + 1 ] = base64_table[( 16 * (flag[v5] & 3 )) | (flag[v5 + 1 ] >> 4 )]; base_cache[v6 + 2 ] = base64_table[( 4 * (flag[v5 + 1 ] & 0xF )) | (flag[v5 + 2 ] >> 6 )]; v2 = v6 + 3 ; v6 + = 4 ; base_cache[v2] = base64_table[flag[v5 + 2 ] & 0x3F ]; v5 + = 3 ; + + i; } if ( v8 = = 1 ) / / 当需要加密的字串长度余数为一则在后面填充两个空格 { base_cache[v6] = base64_table[flag[v5] >> 2 ]; base_cache[v6 + 1 ] = base64_table[ 16 * (flag[v5] & 3 )]; strcat(base_cache, "==" ); } else if ( v8 = = 2 ) / / 当需要加密的字串长度余数为二则在后面填充一个空格 { base_cache[v6] = base64_table[flag[v5] >> 2 ]; base_cache[v6 + 1 ] = base64_table[( 16 * (flag[v5] & 3 )) | (flag[v5 + 1 ] >> 4 )]; base_cache[v6 + 2 ] = base64_table[ 4 * (flag[v5 + 1 ] & 0xF )]; base_cache[v6 + 3 ] = 61 ; } strcpy(flag, base_cache); return flag; } |
怎么说呢,这个代码不能说十分正常,只能说是一点毛病没有,到这里我就卡壳了,去百度后发现,这个程序还是换了码表但是它是在运行时动态更换的所以我们在看码表的数据时看不出什么毛病。我们点击码表看他的交叉引用:
可以看到除了base64_encode()
,O_OLookAtYou()
也调用了码表,这时候确实有点无语这个函数名这么奇怪做题时一点没发现,先到O_OLookAtYou()
函数那去看看,因为main函数是没有调用这个函数的,那这个函数是如何做到修改码表的呢?,还是看它的交叉引用:
可以看到函数其实是在init,也就是初始化函数里被调用,我们知道函数的最开始并不是从main开始,start才是函数的真正入口点,这个知识点可以去看程序员的自我修养那本书,对这些东西分析得很透彻。init和start一样都是编译器加进去的,init函数主要负责设置包、初始化变量或其他要在程序运行前优先完成的引导工作。
这里选择直接动调看码表改完后是个啥样:
这里随便下个断点,没啥影响:
直接双击查看码表:
TSRQPONMLKJIHGFEDCBAUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
上面这一串就是修改后的码表,然后利用新表解base64就行:
1 2 3 4 5 6 7 8 | import base64 str1 = "d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD==" string1 = "TSRQPONMLKJIHGFEDCBAUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" print ((base64.b64decode((str1.translate( str .maketrans(string1,string2))).encode())).decode()) |
wctf2020{Base64_is_the_start_of_reverse}
提交时记得是flag包裹
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课