感觉难度越来越大了,把进阶题分为几篇来写,以便查看。
0x00 reverse-box
感觉都是没有看到过的题型,看了大佬WP,还是不懂。。。先留着以后再看。
要用GDB命令脚本先放个链接如何写gdb命令脚本。
0x01 IgniteMe
这道题就非常的友好了,拖进IDA。
检查输入的前四个字符为 ' EIS{ ' 最后一个为 ‘ } ’,然后进入sub_4011c0() 关键函数。
这个函数就是交换大小写,然后异或操作之后看与最后的字符串一不一样。
首先双击unk_4420B0选中,按shift+e 把它提取出来(新学的操作,好舒服啊!终于不用手动码了)。
然后就是写脚本异或回去得到v4了,在转换大小写加上 EIS{ 和 } 就是flag了。
脚本如下:
a = "GONDPHyGjPEKruv{{pj]X@rF"
xor_string = [ 0x0D, 0x13, 0x17, 0x11, 0x02, 0x01, 0x20, 0x1D, 0x0C, 0x02,
0x19, 0x2F, 0x17, 0x2B, 0x24, 0x1F, 0x1E, 0x16, 0x09, 0x0F,
0x15, 0x27, 0x13, 0x26, 0x0A, 0x2F, 0x1E, 0x1A, 0x2D, 0x0C,
0x22, 0x04]
s = []
flag = ''
for i in range(len(a)):
s.append(ord(a[i])^ xor_string[i])
for i in range(len(s)):
s[i] -= 72
s[i] = s[i]^0x55
for i in s:
flag += chr(i)
print(flag.lower())
0x02 srm-50
这道题感觉我是非预期解啊,都不分析什么,正解应该是用OD破解找到注册码的。我直接拖进IDA,F12找到报错的字符串,看到if判断语句,将v11[0],v11[1],v11[2],v11[3],v12,v13…………连续字符得到就是flag了。。。。。。
0x03 ReverseMe-120
拖进IDA,看到主函数感觉很简单的亚子。我们看到最后只判断了v13是否与“you_know_how_to_remove_junk_code” 一样,往上分析v13经过了sub_401000()然后与0x25异或。
跟进sub_401000(),分析不出来是什么,,,看了大佬的WP ,这是base64的解密,还是太菜了,没有看出特征。所以将“you_know_how_to_remove_junk_code” 与0x25异或后再base64加密得到flag。
0x04 CRACKME
运行exe,要调入注册码,随便输入有弹窗错误。拖进IDA,发现是MFC写的程序。参考大佬文章,MessageBoxA函数是于创建、显示并操作一个消息对话框的,所以我们找到调用MessageBoxA的函数。
跟进sub_401720 和sub_4016E0发现不了什么,继续查看他们的引用。在这里就可以猜测这两个MessageBoxA一个是显示错误弹窗一个是正确弹窗。输入的数据经过sub_401630()之后才判断对错。所以跟进sub_401630()分析。
这里看了WP有点不懂,为什么伪随机数种子一直都是1 ,伪随机数不变。继续分析 if 里面,就是每隔10个字符判断是否相等。
这里将一个很长的字符串复制给v2+96,看WP说v2就是上面的v3,这一点也有些疑惑,没有看出来。也许是this指针的原因吧。接下来这个长字符串从第二个字符开始到330位,每隔10位取出一个字符。
脚本如下:
a = ";f1K3{c5:efl21t4;1t1zaxpim9}5+?gtux;=vc9v{v7+buhU{bT=-am2q}=fh[xk{y?xrqe{?}l5-sd2-Mo+:j{9=sY[dalvpx?z3{?no{[k5ll{zjsu5[kfla+r6Zg72o0skq6cGl5cw[=d?3v9q5-vkjSv{4sqtg=f0cz{+jurjfl[tb]lrfF1;2}udhb?0g8{om:T4dh;z:oz-Dn=m=ux;o[gs9{+zqx+sq-dsxctcvykUs2oddrt43pwv:f0;njkrb9los6g0{ih?rqantfx$sslqd:rvqixr;j{?o:sn+[i[yA11;gsmr8lm0?3};+iv+Tf:4Gtv2:-20upi0]7?77=;qzx{m-W;0vtueh]ko8d?=w:fbhd{E:;19?p=k:b+}doht6wpEq-z]2qbV1}dh416qw9:xm[;ed;:ecb-0:ni-s4u2kf6]2wn45amzjrun=ofkx-=hmgo-lz;j909=rmo7xcj4le0hxs[i]-vjl[?o12:sv4upio7ma1hRy7556+57krev:hLQ+1cx65z5v5];6n=[p83;n={zm{k2p"
for i in range(1,330,10):
print(a[i],end='')
0x05 tt3441810
这道题看不懂,以为是手动脱壳。搜了官方WP,也是一脸懵逼,这是杂项题吗?
用IDA或者notepad++打开看到十六进制数,在这里看到 ‘fl’ ,{ } 等字样,然后把一些混淆字符去掉得到flag,还得提交括号里面的内容。又看了里面自带的WP,还是学到了xxd使用。
flag{poppopret}
0x06 zorropub
先拖进IDA,分析下程序流程,题的意思很简单,主要是输入饮料的数量和饮料ID经过下面函数在计算MD5,判断其MD5是否相等。
v15 = __readfsqword(0x28u);
seed = 0;
puts("Welcome to Pub Zorro!!");
printf("Straight to the point. How many drinks you want?", a2);
__isoc99_scanf("%d", &v5);
if ( v5 <= 0 )
{
printf("You are too drunk!! Get Out!!", &v5);
exit(-1);
}
printf("OK. I need details of all the drinks. Give me %d drink ids:", (unsigned int)v5);
for ( i = 0; i < v5; ++i )
{
__isoc99_scanf("%d", &v6); //循环输入饮料ID
if ( v6 <= 16 || v6 > 0xFFFF ) //饮料ID在 [16,65535]范围内。
{
puts("Invalid Drink Id.");
printf("Get Out!!", &v6);
exit(-1);
}
seed ^= v6; //随机种子
}
i = seed;
v9 = 0;
while ( i )
{
++v9;
i &= i - 1;
}
if ( v9 != 10 )
{
puts("Looks like its a dangerous combination of drinks right there.");
puts("Get Out, you will get yourself killed");
exit(-1);
}
srand(seed);
MD5_Init((__int64)&v10);
for ( i = 0; i <= 29; ++i )
{
v9 = rand() % 1000;
sprintf(&s, "%d", v9);
v3 = strlen(&s);
MD5_Update((__int64)&v10, (__int64)&s, v3);
v12[i] = v9 ^ LOBYTE(dword_6020C0[i]); //LOBYTE()得到一个16bit数最低(最右边)那个字节
}
v12[i] = 0;
MD5_Final(v11, &v10);
for ( i = 0; i <= 15; ++i )
sprintf(&s1[2 * i], "%02x", (unsigned __int8)v11[i]);
if ( strcmp(s1, "5eba99aff105c9ff6a1a913e343fec67") )
{
puts("Try different mix, This mix is too sloppy");
exit(-1);
}
return printf("\nYou choose right mix and here is your reward: The flag is nullcon{%s}\n", v12);
}
这里我们有两种思路,1.爆破MD5,2.逆向算法。
1.爆破MD5:
先根据饮料ID范围在 [16,65535],和下面check来从中筛选出符合ID的值。
while ( i )
{
++v9;
i &= i - 1;
}
if ( v9 != 10 )
在使用subprocess库,(想使用pwntools的,结果没有搜到pwntools如何杀死进程的函数,看了大佬文章才知道还有subprocess的),将满足ID的数使用subprocess里的communicate()带进去试一试,看能否返回flag。
脚本如下:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from subprocess import *
#这里是找出符合条件的数
a=[]
for i in range(16,65535):
v9 = 0
s=i
while i:
v9 +=1
i &= i-1
if v9 == 10:
a.append(s)
#循环输入符合条件的数,爆破flag
for i in a:
proc = Popen(['./zorro_bin'],stdin=PIPE,stdout=PIPE)
out = proc.communicate(('1\n%s\n' % i).encode('utf-8'))[0]#这里communicate返回的是一个元组,但是元组只有一个元素,所以要加上偏移0。
if "nullcon".encode('utf-8') in out:
print(out)
print(i)
2.逆向算法
这是在官方WP复制的。利用libc.so.6动态链接库得到生成的伪随机数。然后对着IDA的函数复现一遍。得到结果也是输入59306的时候正确。
import ctypes
import os
import sys
import hashlib
libsystem = ctypes.CDLL('libc.so.6')
# Extracted by hand
encryption_key = [
0x03C8, 0x0032, 0x02CE, 0x0302, 0x007F,
0x01B8, 0x037E, 0x0188, 0x0349, 0x027F,
0x005E, 0x0234, 0x0354, 0x01A3, 0x0096,
0x0340, 0x0128, 0x02FC, 0x0300, 0x028E,
0x0126, 0x001B, 0x032A, 0x02F5, 0x015F,
0x0368, 0x01EB, 0x0079, 0x011D, 0x024E
]
need_md5 = '5eba99aff105c9ff6a1a913e343fec67'
mc = 0
while True:
for drink_id_count in range(17, 0xFFFE):
mc += 1
if mc % 1000 == 0:
sys.stdout.write('.')
sys.stdout.flush()
for counter in range(5):
input_1 = counter
seed = 0
drink_ids = []
for x in range(input_1):
drink_id = drink_id_count
drink_ids.append(drink_id)
if drink_id <= 16 or drink_id > 0xFFFF:
continue
else:
seed ^= drink_id
count = seed
some_num = 0
while count > 1:
some_num += 1
count &= (count - 1)
if some_num != 10:
continue
else:
pass
libsystem.srand(seed)
flag = ""
h = hashlib.md5()
for x in range(30):
ran = libsystem.rand()
rand_number = ran % 1000
h.update("%d" % rand_number)
flag += chr((rand_number ^ encryption_key[x])&0xFF)#LOBYTE()得到一个16bit数最低(最右边)那个字节,所以 & 0xff
if h.hexdigest() == need_md5:
print "\nHash -> %s" % h.hexdigest()
print "Found it! Drinks:%d, Drink IDs:%s" % (counter, drink_ids)
raw_input()
0x07 Reversing-x64Elf-100
IDA打开,主函数。分析得到主要逻辑函数为sub_4006FD(),非常简单的逻辑,写脚本得到flag。
signed __int64 __fastcall sub_4006FD(__int64 a1)
{
signed int i; // [rsp+14h] [rbp-24h]
const char *v3; // [rsp+18h] [rbp-20h]
const char *v4; // [rsp+20h] [rbp-18h]
const char *v5; // [rsp+28h] [rbp-10h]
v3 = "Dufhbmf";
v4 = "pG`imos";
v5 = "ewUglpt";
for ( i = 0; i <= 11; ++i )
{
if ( (&v3)[i % 3][2 * (i / 3)] - *(char *)(i + a1) != 1 )
return 1LL;
}
return 0LL;
}
脚本如下:
arr = [
['D','u','f','h','b','m','f'],
['p','G','`','i','m','o','s'],
['e','w','U','g','l','p','t']
]
for i in range(12):
key = arr[i%3][2*int(i/3)]
print(chr(ord(key)-1),end='')
0x08 gametime
这道题主要靠细心,先运行一下看看,在拖进IDA,分析下逻辑。这个游戏就是靠手快,刚好出现规定字符时,按一下对应字符进入下一关,越来越快,这单身一辈子也没有这手速啊。。所以拖进OD,找到些关键跳转下断点,耐心的调试耐心的调试耐心的调试耐心的调试,就出来key了。
这里总结下OD的使用:
- 找到关键字符,在其上方第一个跳转一般为关键跳转,越过正确字符的跳转为关键跳转。
- 在关键跳转处下断点,运行到断点时看将要跳转到哪里去,在判断是否跳转,比如有几个跳转都将向同一个地址跳转,那么这个跳转就改成不跳转,因为游戏出错有一点错误就退出,所以这里一定是指向错误的输出函数,正确的只有一条路,不可能有多个跳转指向它。
- 遇到JMP跳转一般不要改,因为这个是无条件跳转,无论你输入的正确与否,它都将跳转。
- 出现需要循环运行时,不用一个个的按F8,在跳转的下面一行下断点,然后F9直接运行,将会断在刚刚下断点处。
- 边调试边看运行结果,看是否正确输出。
0x09 easyre-153
首先UPX脱壳。IDA打开,看到pipe()和fork()。
pipe函数可用于创建一个管道,以实现进程间的通信。
fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,其返回值是进程号。
v8 = __readgsdword(0x14u);
pipe(pipedes);
v5 = fork();
if ( !v5 )
{
puts("\nOMG!!!! I forgot kid's id");
write(pipedes[1], "69800876143568214356928753", 0x1Du);
puts("Ready to exit ");
exit(0);
}
read(pipedes[0], &buf, 0x1Du);
__isoc99_scanf("%d", &v6);
if ( v6 == v5 )
{
if ( (*(_DWORD *)((_BYTE *)lol + 3) & 0xFF) == 204 )
{
puts(":D");
exit(1);
}
printf("\nYou got the key\n ");
lol(&buf);
}
wait(0);
return 0;
}
但是这里就算输入正确,lol函数也会返回 'flag_is_not_here',所以我们需要使用IDA动态调试
去修改汇编改变跳转得到flag,之前的进程号也可以随便输然后改变后面的关键跳转即可。
得到flag,要加上RCTF。太坑了,我以为我的不是flag,看了WP才知道。