非常坑爹的一道题目,看似非常简单,实则有套路
链接: http://pan.baidu.com/s/1i4XLCd3 密码:9zho
为了练手 我会写出三种解法,包括 结合ascii码值范围的爆破,动态调试解法,静态调试解法
(感谢我xx学长的支持和某位网友的writeup)
1.首先拿到该程序,丢进ida会发现这是个elf程序,程序主体看起来也很简单,在ubuntu下也能正常跑起来
int __cdecl main() { int v0; // eax@1 char *v1; // edx@2 int v2; // ecx@3 int v4; // [sp+1Ch] [bp-10h]@4 printf("%s\nPlease input your flag:", off_8049900); __isoc99_scanf("%s", &byte_8049908); v0 = off_8049900 - (char *)main; if ( off_8049900 > (char *)main ) { v1 = (char *)main; do { v2 = (unsigned __int8)*v1++; v0 = v2 ^ (v0 >> 27) ^ 32 * v0; } while ( v1 != off_8049900 ); } v4 = v0; if ( __PAIR__((unsigned __int8)(byte_8049909 ^ BYTE1(v4)), (unsigned __int8)(byte_8049908 ^ v0)) != word_80498F0 || ((unsigned __int8)byte_804990A ^ BYTE2(v0)) != byte_80498F2 || (BYTE3(v0) ^ (unsigned __int8)byte_804990B) != byte_80498F3 || ((unsigned __int8)v0 ^ (unsigned __int8)byte_804990C) != byte_80498F4 || (BYTE1(v4) ^ (unsigned __int8)byte_804990D) != byte_80498F5 || (BYTE2(v0) ^ (unsigned __int8)byte_804990E) != byte_80498F6 || (BYTE3(v0) ^ (unsigned __int8)byte_804990F) != byte_80498F7 || ((unsigned __int8)v0 ^ (unsigned __int8)byte_8049910) != byte_80498F8 || (BYTE1(v4) ^ (unsigned __int8)byte_8049911) != byte_80498F9 || (BYTE2(v0) ^ (unsigned __int8)byte_8049912) != byte_80498FA || (BYTE3(v0) ^ (unsigned __int8)byte_8049913) != byte_80498FB || ((unsigned __int8)dword_8049914 ^ (unsigned __int8)v0) != byte_80498FC || (*(_WORD *)((char *)&dword_8049914 + 1) ^ *(_WORD *)((char *)&v4 + 1)) != word_80498FD || (BYTE3(dword_8049914) ^ BYTE3(v0)) != byte_80498FF ) { puts("You are wrong"); } else { puts("You are right"); } return 0; }
2.结合汇编语言,对程序可以做一个大致的分析,第二个if的判断是分别将输入的字符串的每一位和v4/v0(32位)的第一,二,三,四个字节做异或并将结果和0x80498f0起始处的字符串做比较,只有16位全相等时才会输出正确
3.瞄了一眼,v4和v0的值发现和输入字符串无关,应该是个固定值,而v4=v0对应的汇编语言是mov edx,eax,所以正常的想法是用gdb在0x08048416处下断点
.text:080483EB mov eax, ebx .text:080483ED sub eax, offset main .text:080483F2 cmp ebx, offset main .text:080483F8 jbe short loc_8048416 .text:080483FA mov edx, offset main .text:080483FF nop .text:08048400 .text:08048400 loc_8048400: ; CODE XREF: main+64j .text:08048400 mov ecx, eax .text:08048402 sar ecx, 1Bh .text:08048405 shl eax, 5 .text:08048408 xor eax, ecx .text:0804840A movzx ecx, byte ptr [edx] .text:0804840D add edx, 1 .text:08048410 xor eax, ecx .text:08048412 cmp edx, ebx .text:08048414 jnz short loc_8048400 .text:08048416 .text:08048416 loc_8048416: ; CODE XREF: main+48j .text:08048416 mov edx, eax .text:08048418 xor dl, ds:byte_8049908 .text:0804841E cmp dl, byte ptr word_80498F0 .text:08048424 mov [esp+1Ch], eax .text:08048428 jnz loc_8048566 .text:0804842E movzx edx, byte ptr [esp+1Dh] .text:08048433 mov ecx, edx .text:08048435 xor cl, ds:byte_8049909 .text:0804843B cmp cl, byte ptr word_80498F0+1 .text:08048441 jnz loc_8048566 .text:08048447 movzx ecx, byte ptr [esp+1Eh] .text:0804844C mov ebx, ecx .text:0804844E xor bl, ds:byte_804990A .text:08048454 cmp bl, byte_80498F2 .text:0804845A jnz loc_8048566
4.如果你的思路和我一样,那么恭喜你入套了,不过还是先这样走完
5.用gdb下断点后可以得到eax的值,如图为:0xd90c5525
6.于是我尝试写了这样一个python脚本:
a=[0x18,0x5b,0xbf,0x38,0x34,0x5a,0x99,0x4d,0x2e,0x73,0xbb,0x4e,0x23,0x76,0x9f,0x3] b=[0x25,0x55,0x0c,0xd9] c='' for i in range(16): d=i%4 c+=(chr(a[i]^b[d])) print c
7.当然你得到的只会是乱码。。。
8.好吧,现在是早上01:43:32我就不废话了,我还要睡觉呢!错误的原因在于第一个if语句是对text段的读操作,并对读到的值进行一系列的运算操作,从main函数一直读到0x0804872A,当然也会读到你下断点的地方,而据网上资料所讲b *0x 操作是将相应处内存改为0xcc,这样一来你得到的eax和edx的值就会不对,那该怎么办呢
解法一:动态调试的方法
这期间我犯了很多很多的错误
错误一:我分别用了 b *0x08048418 if $eip==0x08048146//r 和b *0x080483fa//r//b *08048418 if $edx==$dbx这两种方法
错误的原因在于理解错了breakpoint指令的意思 我以为b if 是时时监控 条件成立就下断点 实际上是运行到断点处才判断。。
如果是犯同样错误的人自己理解吧,这样是不行的
错误二:watch $eip==0x08048418 也是错的 具体原因我还没有查
直接写出最终解法吧:内存访问监控 涉及到的gdb指令是awatch(awatch是读写断点,内存被读或着写时都会断。而rwatch是读时断,watch是写时断。)
我这里用到的是awatch
指令序列为awatch *0x08048148//r//info b//d 1//s//s//...//b *0x08048148//c
还有其他很多方法:b *0x80483fa//r//d 1//awatch *0x8049908//c
最简单的方法:awatch *0x80498f0
这样一来就能得到正确的eax值,再结合最开始写的脚本就可以得到flag
方法二:爆破
这样需要结合flag的格式进行一定的手动猜测,一个简单python脚本最没技术含量的吧:
a=[0x18,0x5b,0xbf,0x38,0x34,0x5a,0x99,0x4d,0x2e,0x73,0xbb,0x4e,0x23,0x76,0x9f,0x3] b='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTTUVWXYZ0123456789{}_()=' for i in range(4): for j in range(0x00,0xff): for k in range(4): d=chr(j^a[i+4*k]) if d not in b: break if k==3: print '\n' print i,hex(j) for l in range(4): print chr(j^a[i+4*l]),
方法三:静态调试
写脚本直接访问文件对应地址的数据进行计算,这里计算得到的都是eax寄存器的值
分别用c和python写了两个脚本,两种方法
先看c的,直接访问源文件,这里有一个问题,文本要以rb方式打开,r不行,因为
以“r”方式读取,系统只读出ascii中可显示字符;如果不可显示(控制字符),系统会把它滤掉。但是“rb”的方式则不会。:
#include<stdlib.h> #include<stdio.h> void main() { FILE *fp; fp=fopen("C:\\Users\\tLOMO\\Desktop\\Elfcrackme2","rb"); int i=0x37a; //(main)0x80483b0~0x804872a地址之间的二进制文件内容的长度0x37A if(fp!=NULL) { int m,n; int a; for(n=0;n<0x3b0;n++)//main从0x3b0开始 { fgetc(fp); } for(m=0;m<0x37a;m++) { a=fgetc(fp); i=a^(i>>27)^(32*i); } printf("value of eax is:%x",i); } else { printf("the file is blank"); } fclose(fp); system("pause"); }
再来看python的,这里也遇到了一个比较严重的问题,不注意的话可能不会发现,
因为源程序是c语言,int默认为有符号32位,那么c>>27,如果是负数的话,高位会补f而不是0,而python是个弱类型语言,64位python,int为64位,所以会出问题,所以这里要做一个转换:
a='''55 89 E5 57 56 53 83 E4 F0 83 EC 20 A1 00 99 04 08 C7 04 24 F0 86 04 08 89 44 24 04 E8 8F FF FF FF C7 44 24 04 08 99 04 08 C7 04 24 0B 87 04 08 E8 BB FF FF FF 8B 1D 00 99 04 08 89 D8 2D B0 83 04 08 81 FB B0 83 04 08 76 1C BA B0 83 04 08 90 89 C1 C1 F9 1B C1 E0 05 31 C8 0F B6 0A 83 C2 01 31 C8 39 DA 75 EA 89 C2 32 15 08 99 04 08 3A 15 F0 98 04 08 89 44 24 1C 0F 85 38 01 00 00 0F B6 54 24 1D 89 D1 32 0D 09 99 04 08 3A 0D F1 98 04 08 0F 85 1F 01 00 00 0F B6 4C 24 1E 89 CB 32 1D 0A 99 04 08 3A 1D F2 98 04 08 0F 85 06 01 00 00 0F B6 7C 24 1F 0F B6 1D 0B 99 04 08 31 FB 3A 1D F3 98 04 08 0F 85 EC 00 00 00 0F B6 1D 0C 99 04 08 31 C3 3A 1D F4 98 04 08 0F 85 D7 00 00 00 0F B6 1D 0D 99 04 08 31 D3 3A 1D F5 98 04 08 0F 85 C2 00 00 00 0F B6 1D 0E 99 04 08 31 CB 3A 1D F6 98 04 08 0F 85 AD 00 00 00 0F B6 1D 0F 99 04 08 31 FB 3A 1D F7 98 04 08 0F 85 98 00 00 00 0F B6 1D 10 99 04 08 31 C3 3A 1D F8 98 04 08 0F 85 83 00 00 00 0F B6 1D 11 99 04 08 31 D3 3A 1D F9 98 04 08 75 72 0F B6 1D 12 99 04 08 31 CB 3A 1D FA 98 04 08 75 61 0F B6 1D 13 99 04 08 31 FB 3A 1D FB 98 04 08 75 50 33 05 14 99 04 08 3A 05 FC 98 04 08 75 42 32 15 15 99 04 08 3A 15 FD 98 04 08 75 34 32 0D 16 99 04 08 3A 0D FE 98 04 08 75 26 89 FB 32 1D 17 99 04 08 3A 1D FF 98 04 08 75 16 C7 04 24 0E 87 04 08 E8 14 FE FF FF 8D 65 F4 31 C0 5B 5E 5F 5D C3 C7 04 24 1C 87 04 08 E8 FE FD FF FF EB E8 31 ED 5E 89 E1 83 E4 F0 50 54 52 68 60 86 04 08 68 70 86 04 08 51 56 68 B0 83 04 08 E8 FB FD FF FF F4 90 90 90 90 90 90 90 90 90 90 B8 07 99 04 08 2D 04 99 04 08 83 F8 06 77 02 F3 C3 B8 00 00 00 00 85 C0 74 F5 55 89 E5 83 EC 18 C7 04 24 04 99 04 08 FF D0 C9 C3 90 8D 74 26 00 B8 04 99 04 08 2D 04 99 04 08 C1 F8 02 89 C2 C1 EA 1F 01 D0 D1 F8 75 02 F3 C3 BA 00 00 00 00 85 D2 74 F5 55 89 E5 83 EC 18 89 44 24 04 C7 04 24 04 99 04 08 FF D2 C9 C3 90 8D B4 26 00 00 00 00 80 3D 04 99 04 08 00 75 13 55 89 E5 83 EC 08 E8 7C FF FF FF C6 05 04 99 04 08 01 C9 F3 C3 66 90 A1 D0 97 04 08 85 C0 74 1E B8 00 00 00 00 85 C0 74 15 55 89 E5 83 EC 18 C7 04 24 D0 97 04 08 FF D0 C9 E9 79 FF FF FF E9 74 FF FF FF 90 90 90 90 55 89 E5 5D C3 8D 74 26 00 8D BC 27 00 00 00 00 55 89 E5 57 56 53 E8 4F 00 00 00 81 C3 4D 12 00 00 83 EC 1C E8 9B FC FF FF 8D BB 04 FF FF FF 8D 83 00 FF FF FF 29 C7 C1 FF 02 85 FF 74 24 31 F6 8B 45 10 89 44 24 08 8B 45 0C 89 44 24 04 8B 45 08 89 04 24 FF 94 B3 00 FF FF FF 83 C6 01 39 FE 72 DE 83 C4 1C 5B 5E 5F 5D C3 8B 1C 24 C3 90 90 55 89 E5 53 83 EC 04 E8 00 00 00 00 5B 81 C3 EC 11 00 00 59 5B C9 C3 00 03 00 00 00 01 00 02 00 25 73 0A 50 6C 65 61 73 65 20 69 6E 70 75 74 20 79 6F 75 72 20 66 6C 61 67 3A 00 25 73 00 59 6F 75 20 61 72 65 20 72 69 67 68 74 00 59 6F 75 20 61 72 65 20 77 72 6F 6E 67 00'''.replace(' ',' ').replace('\n','') b=a.split(' ') print hex(len(b)) c=0x37a for i in b: e=c>>27 d=c&0x80000000 if(d==0x80000000): e|=0xffffffe0 c=int(i,16)^e^32*c c&= 0xffffffff print hex(c)