程序极小,也不能运行也不能用ida,还有个提示 echo $?
用010打开,发现0x20后是一段代码。用pwntools的反编译功能直接反编译
a = open('tiny','rb').read()[0x20:]
print(a)
from pwn import *
context.arch='i386'
print(disasm(a))
'''
0: 5b pop ebx
1: 5b pop ebx
2: 5b pop ebx
3: 31 c9 xor ecx, ecx
5: 51 push ecx
6: 41 inc ecx
7: 41 inc ecx
8: eb 04 jmp 0xe
a: 20 00 ...
c: 01 00 ...
e: 58 pop eax
f: 68 07 52 f4 8a push 0x8af45207
14: 68 63 9a 3f 09 push 0x93f9a63
19: 8b 7c 8b fc mov edi, DWORD PTR [ebx+ecx*4-0x4] ;传入的flag 后半个,前半个,循环右移3位后与栈顶两个异或后为0
1d: c1 cf 03 ror edi, 0x3 ;第一次为 0x5d99429 和 0x93f9a63
20: 68 29 94 d9 05 push 0x5d99429 ;第二次为 0x5d99429 和 0x8af45207
25: 5a pop edx
26: 5e pop esi
27: 31 fe xor esi, edi
29: 31 f2 xor edx, esi
2b: 09 d0 or eax, edx
2d: e2 ea loop 0x19
2f: 31 db xor ebx, ebx
31: 85 c0 test eax, eax
33: 0f 95 c3 setne bl
36: 31 c0 xor eax, eax
38: 40 inc eax
39: cd 80 int 0x80
'''
应了这个比赛的名字:36D里边到底是啥,全靠猜。
开头有ELF但是是32还是64呢?反编译后全是eXX,而且从栈里取数据,应该是32位。
题目的hint是echo $? 在unix系统里表示返回值这里0x36开始将eax清0再加1然后返回,所以结果一定是1,这个提示也就没用了。所以第一个猜想:0x36,0x38没用应该删掉
然后回到开头先是弹了3个到ebx,然后会用到从[ebx+ecx*4-4]里读数据,这个ebx到底是啥,这是第二个猜想:指向输入的flag的指针。 一般逆向都是输入flag然后验证
有了这两个猜想就可以看程序了
先是压两个数到栈,然后进行循环ecx是循环计数器先设置为2,每次loop会减1到0退出
从ebx+X读入一个数字然后循环右移3位 存在edi
再压入一个数然后弹出两个到edx,esi
把edx,esi,edi作异或运算,然后or到eax,eax是返回值,前面猜的是确定flag是否正确的返回值,那么根据unix类的规则,返回值0表示正确,由于eax是或运算,所以它一开始是0,然后两次循环都应该是0,也就是说输入的值循环右移后存在edi的值与栈顶两个值的异或结果相同。
再来看栈的情况,第一次是0x8af45207,0x93f9a63,0x5d99429(循环里后压入的) 也就是后两个的异或与输入值右移3位后相同
0x93f9a63^0x5d99429 == flag[1]>>>3
第二次栈里只有0x8af45207,0x5d99429(循环里后压入的)
0x8af45207^0x5d99429 == flag[0]>>>3
由于python在数字没有固定长度,所以也就没有循环右移指令,只能手工处理
a = (0x93f9a63^0x5d99429)
a = (a<<3 | a>>29)& 0xffffffff
print(bytes.fromhex(hex(a)[2:])[::-1])
a = (0x8af45207^0x5d99429)
a = (a<<3 | a>>29)& 0xffffffff
print(bytes.fromhex(hex(a)[2:])[::-1])
#flag{t1nyPr0g}
最后一个问题,36D杯为啥用flag{}包起来而不是36D{}呢?!