关于vb反汇编

今天做一个VB写的crackme,程序本身很简单,但是却发现一个很奇怪的地方

看如下代码

0F0FEA42  xor eax,eax
0F0FEA44  mov al,byte ptr ds:[esi]
0F0FEA46  inc esi
0F0FEA47  jmp dword ptr ds:[eax*4+0xF0FED94]

这样的代码随处可见,而且程序几乎一直是在msvbvm50这个dll里面执行上述代码,几乎没有回到用户程序领空。后来搜了下,在看雪看到这个系列的文章,总算搞懂了,这里做下记录
VB P-code — 虚拟机的艺术
VB P-code — 调试器的革命
VB P-code — 伪代码的奥秘
这三篇文章讲的很详细,基本上是说:
vb的编译分两种,一是native,也就是直接编译成机器码,而是p-code,编译成字节码,由虚拟机引擎解释执行
上面的程序正是p-code方式,esi指向的是字节码指令所在的地址,mov al,byte ptr ds:[esi]这句就是取操作码,inc esi则是指针指向操作数(对双字节操作码的指令,则是指向第二级操作码),jmp dword ptr ds:[eax*4+0xF0FED94]这句中的0xF0FED94是跳转地址表的首地址。这个内存区域存放着所有vb字节码对应的机器码程序。上述代码正是利用字节码的操作码部分得出对应的机器指令程序段的地址,然后跳过去执行这条字节码的功能。
这也解决了我的问题。msvbvm50这个dll中存储了vb5.0的虚拟机代码,程序不断的通过虚拟机获取用户程序领空的字节码来执行,所以才会一直在msvbvm50.dll里面打转。而用户代码部分则od无法识别,原因就是这些代码都是字节码嘛。
然后又查了查,找到了一个叫vbdecomplier的反编译器,pcode方式的vb程序基本上都能看源码了。。

破解一个简单的程序

再来看一个。

MrBills

这个也是鱼c解密系列od调试篇里面的一个程序

这个系列的教学视频很不错,很基础,适合初学者。虽然里面讲的东西很多以前都学过,但是每个视频总会让我学到一点新知识。但是注意一点,看视频前一定要自己先尝试破解一下那节课的程序。这样才会有收获。

下面是我尝试破解的过程。

首先打开程序

wps4664.tmp[12]

发现标题中有Unregistered这样的字符串,打开about,发现有注册的按钮,并且文字说明当前版本是非注册版。

好,先以标题中的Unregistered为切入点。

od载入,运行。

查找所有参考文本串,发现找不到Unregistered。。但是发现了这么一个字符串

wps4684.tmp[11]

显然,这是注册后才会显示的字符串,双击来到相关代码处。往上找找看跳转,发现了如下代码:

wps4685.tmp[11]

这个je跳转如果成功,就会跳过刚才那个字符串的地方。显然

00402350  |.  E8 144D0000   call    00407069

这句是关键,因为al的值是否为0直接决定je的结果,所以这个call应该是判断是否有注册有关。

所以在402350处下断,重新载入运行。点about按钮触发一下中断,F7步入看看

往下发现该函数的最后几句如下:

00407125  |.  A0 A0765000   mov     al, byte ptr [5076A0]

0040712A  |.  64:890D 00000>mov     dword ptr fs:[0], ecx

00407131  |.  C9            leave

00407132  \.  C3            retn

可见,al的值来自于5076a0这个地址。

好,试着对该地址常量查找参考,发现如下

wps4696.tmp[11]

对这些地方全部下断。看看程序第一次写入这个地址的代码那里如何

下断后重新载入运行。

wps46B6.tmp[11]

断在这里。我们来观察一下,因为目前我们还未实现破解,所以jnz不会跳转,那么已注册版本显然是要实现跳转的。4070cb处的call显然是判断软件是否已经注册的。由于未注册不会跳转,可见,未注册时call 00406fd1返回0,。那么,答案已经很明显了,做如下修改:

wps46C7.tmp[11]

注意红色的两条指令为修改处。

当然,直接把mov     byte ptr [5076A0], al  改为mov     byte ptr [5076A0], 1逻辑上也是可以的,但是由于指令长度变长,会把下面的跳转指令覆盖掉。。所以不行。

保存一下,看看结果

wps46F7.tmp[11]

看到区别了吧,已经破解了。

这里仅仅实现破解,程序的算法就不逆向了。。(有空再说)

逆向一个非常简单的crackme(2)

再来看一个。

reverseMe

首先打开程序

wpsB4A2.tmp

提示说版本过期之类的。。

od载入

程序很短,不需要跑,直接观察就行

往下拉来到这段

wpsB4E1.tmp

可以看到,程序在初始的一些操作之后,用CreateFile的OPEN_EXISTING这种方式打开一个叫Keyfile.dat的文件,实际上就是检查这个文件是否存在

再往下

wpsB4F2.tmp

发现是读该文件的前70个字节pBytesRead这个指针指向的内存地址存放的是实际读取的字符数

下面一段是关键

004010A9   call    <jmp.&KERNEL32.ReadFile>            ; \ReadFile

004010AE   test    eax, eax

004010B0   jnz     short 004010B4                               ;ReadFile成功时返回非零值

004010B2   jmp     short 004010F7                             ;跳过去则会提示Keyfile is not valid. Sorry.”

004010B4   xor     ebx, ebx

004010B6   xor     esi, esi

004010B8   cmp     dword ptr [402173], 10                 ;key的长度必须>=10h,即16

004010BF   jl      short 004010F7

004010C1   mov     al, byte ptr [ebx+40211A]              ;循环操作读取进来的key

004010C7   cmp     al, 0                                                ;字符串以0结尾,也就是处理到结束

004010C9   je      short 004010D3

004010CB   cmp     al, 47                                              ;ascii码47h=’G’

004010CD   jnz     short 004010D0

004010CF   inc     esi                                                     ;esi是在计算’G’的出现次数

004010D0   inc     ebx

004010D1   jmp     short 004010C1

004010D3   cmp     esi, 8                                                ;’G’至少出现8次才行

004010D6   jl      short 004010F7

004010D8   jmp     00401205                                         ;调到提示成功的地方

由上面的分析可知key的内容是:(1)长度>=16    (2)’G’至少出现8次

这样就可以写出Keyfile.dat了。

wpsB512.tmp

逆向一个非常简单的crackme

 

今天看了下逆向破解的内容,发现大一时学的东西已经忘得差不多了。。本来对这个是挺感兴趣的,但是大二开始入了acm队,专心搞acm去了,所以逆向工程这一块就荒废了,因而至今在逆向破解这一块仍是什么也不懂的弱渣。正好这段时间有空,所以想重新拾起来,然后写下过程,给自己也给想学crack的初学者。

还是先从最简单的开始。

TraceMe
(注:本文写给crack初学者,写的会比较详细。。大牛请直接右上角。。)

K[T{S0{R6HKB5X3H]W%HHBT[4]

发现要输入用户名和序列号,先随意输入

4KZ6(%HY79CY36Q%PZUO5LB

错误时弹出一个messagebox,上面有字符串”序列号错误,再来一次!”

好,od载入,看下字符串参考

U~`DIDY`C6X6Q$B__CPXOKQ

好像没这个字符串。。。

然后ctrl+n看下导入表

6YE~1C{HU4_S951SW82G~2P

发现有GetDlgItemTextA,读入文本框输入不是这个就是GetWindowText,所以在这个函数上下断。

}PIJ2M3M207(AH6K32W9ZTY

重新载入,运行,输入用户名”abcde”

和密码123456.点确认。好,进程断在了刚刚下的断点处,F8单步几下回到程序领空。如下图

Z2IHI5SAX}_1VQN3C7J~IN7

好,继续F8往下

004011CA   .  8A4424 4C     mov     al, byte ptr [esp+4C];数据窗口找一下会发现esp+4c是用户名的地址
004011CE   .  84C0          test    al, al
004011D0   .  74 76         je      short 00401248
004011D2   .  83FB 05       cmp     ebx, 5
004011D5   .  7C 71         jl      short 00401248;到这里为止是判断用户名超度是否合法
004011D7   .  8D5424 4C     lea     edx, dword ptr [esp+4C]
004011DB   .  53            push    ebx;用户名长度
004011DC   .  8D8424 A00000>lea     eax, dword ptr [esp+A0];
004011E3   .  52            push    edx;用户名的内存首地址
004011E4   .  50            push    eax;输入的序列号的内存首地址
004011E5   .  E8 56010000   call    00401340 ;由上面的参数可知这个是关键call,目测是生成序列号并比较,看下下面的代码也可以推断

call    00401340,F7跟进这个call

L{HRFFGEZT0V2)5%N[%3ZKH

先来看最下面这段

00401379  |> \56            push    esi                              ; /<%ld>
0040137A  |.  68 78504000   push    00405078                         ; |Format = “%ld”
0040137F  |.  55            push    ebp                              ; |s
00401380  |.  FF15 9C404000 call    dword ptr [<&USER32.wsprintfA>]  ; \wsprintfA;将esi的内容写入到ebp指向的内容
00401386  |.  8B4424 1C     mov     eax, dword ptr [esp+1C]
0040138A  |.  83C4 0C       add     esp, 0C
0040138D  |.  55            push    ebp                              ; /String2
0040138E  |.  50            push    eax                              ; |String1;我们输入的序列号
0040138F  |.  FF15 04404000 call    dword ptr [<&KERNEL32.lstrcmpA>] ; \lstrcmpA;注意这里
00401395  |.  F7D8          neg     eax
00401397  |.  1BC0          sbb     eax, eax
00401399  |.  5F            pop     edi
0040139A  |.  5E            pop     esi
0040139B  |.  40            inc     eax
0040139C  |.  5D            pop     ebp
0040139D  \.  C3            retn

显然edi是正确的序列号,strcmpA将我们输入的序列号与正确的序列号比较。可见上面得出edi的那部分代码就是关键的生成正确序列号的部分

来看一下

00401340  /$  55            push    ebp
00401341  |.  8B6C24 0C     mov     ebp, dword ptr [esp+C];用户名首址
00401345  |.  56            push    esi
00401346  |.  57            push    edi
00401347  |.  8B7C24 18     mov     edi, dword ptr [esp+18];用户名长度
0040134B  |.  B9 03000000   mov     ecx, 3
00401350  |.  33F6          xor     esi, esi
00401352  |.  33C0          xor     eax, eax
00401354  |.  3BF9          cmp     edi, ecx;用户名长度<=3?
00401356  |.  7E 21         jle     short 00401379
00401358  |.  53            push    ebx
00401359  |>  83F8 07       /cmp     eax, 7;这里开始是关键
0040135C  |.  7E 02         |jle     short 00401360
0040135E  |.  33C0          |xor     eax, eax;eax>7时清零
00401360  |>  33D2          |xor     edx, edx
00401362  |.  33DB          |xor     ebx, ebx
00401364  |.  8A1429        |mov     dl, byte ptr [ecx+ebp];可见,每次取用户名的一个字符,而且是第3个开始
00401367  |.  8A98 30504000 |mov     bl, byte ptr [eax+405030];这里是一组常数,eax在0~7循环,所以是8个常数
0040136D  |.  0FAFD3        |imul    edx, ebx
00401370  |.  03F2          |add     esi, edx
00401372  |.  41            |inc     ecx
00401373  |.  40            |inc     eax
00401374  |.  3BCF          |cmp     ecx, edi;直到用户名的字符取完
00401376  |.^ 7C E1         \jl      short 00401359

这里单步运行一下,观察各个寄存器的值,会发现ebp指向用户名,ecx=3,所以其实是每次把用户名从第3个字符(编号从0开始)开始,每个字符和405030这个地址开始的一组常数分别相乘再相加,结果放在esi里面。

数据窗口查看405030这个地址

X`F%%}HR600UR%8]U6@YZ~3

好,这8个数找到了,接下来写出注册机,代码如下:

#include<stdio.h>
#include<string.h>
int b[]={0x0c,0x0a,0x13,0x09,0x0c,0x0b,0x0a,0x08};
int ans;
int main()
{
    int i,j;
    char s[110];
    while(~scanf("请输入用户名:%s",s))
    {
        ans=0;j=0;
        int len=strlen(s);
        if(len<=3){printf("用户名长度至少为4\n");continue;}
        for(i=3;i<=len-1;i++)
        {
            ans+=s[i]*b[j];
            j++;
            if(j==7) j=0;
        }
        printf("序列号是:%d\n",ans);

    }
    return 0;
}

这样就OK了。

git入门笔记

git add xxx.txt                           把文件修改添加到暂存区
git commit -m “版本说明”       把暂存区的所有内容提交到当前分支
git status                                      查看目前工作区状态(有无文件被修改之类)
git diff                                            查看新旧版本的修改之处
git log                                            查看历史版本记录
git reset –hard HEAD^          向前回退一个版本
git reset –hard HEAD^^       向前回退两个版本
git reset –hard HEAD~n        向前回退n个版本
git reflog                                       查看键入的历史命令

git checkout — file
当修改工作区但未add到暂存区时,取消工作区的修改(应该是用暂存区内容覆盖工作区?)
git reset HEAD file
当已经把工作区的修改add到暂存区时,这条命令可以把add到暂存区的修改回退给工作区(应该是用分支的内容覆盖暂存区?)
git remote add origin https://github.com/github用户名/仓库名.git
把“仓库名”作为远程仓库,名字是origin
git push -u origin master
把当前分支master推送到远程库origin
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
git push origin master 以后可以这样推送到远程库origin

要关联一个远程库,使用命令git remote add origin git@server-name/path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;

创建与合并分支:

http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/001375840038939c291467cc7c747b1810aab2fb8863508000

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>

又是熬夜

今天偶然看到一本叫《Windows 内核安全与驱动开发》的书。。。。。。。的电子试读版,看的挺过瘾,熬夜看了半宿,现在又陷入什么都想学的状态了。。但是毕业设计都还没做啊。。

python小练习

接下来打算在这几个oj上用python刷刷水题,巩固一下刚学的python语法。刚开始想用zoj刷,但是zoj水题太少了,刚学python感到力不从心。。
leetcode
codeforces
leetcode165

class Solution:
    # @param version1, a string
    # @param version2, a string
    # @return an integer
    def compareVersion(self, version1, version2):
        a=version1.split(".");b=version2.split(".");
        lena=len(a);lenb=len(b);
        for i in range(lena):a[i]=int(a[i]);
        for i in range(lenb):b[i]=int(b[i]);
        if(lena<lenb):
            for i in range(lenb-lena):a+=[0];
        elif(lena>lenb):
            for i in range(lena-lenb):b+=[0];
        if(a>b):return 1;
        elif(a<b):return -1;
        else:return 0;
        

cf#1a

str=raw_input();l=str.split(" ");
n=int(l[0]);m=int(l[1]);a=int(l[2]);
if(n%a==0): n/=a;
else:n=n/a+1;
if(m%a==0): m/=a;
else:m=m/a+1;
print(n*m);

cf#1b
这题在整形的列值转化为字母表示的列序号时不知道怎么办了。。于是看了下题解。’A'~’Z'要映射为1~26,当余数为0时,特殊处理,0代表’Z',而且商要减一。。目前还不太明白为什么这么做就对。。。弱爆~~

def judge(a):
    flag1=0;flag2=0;flag3=0;
    for i in a:
        if(i=='R'):flag1=1;
        if(i=='C'):flag2=1;
        if('0'<=i<='9' and flag1==1 and flag2==0): flag3=1;
    if(flag1==1 and flag2==1 and flag3==1):return 0;
    else:return 1;
n=int(input());
for i in range(n):
    s=input();
    ret=judge(s);
    if(ret==0):
        s=s.split("R");
        s=s[1].split("C");
        r=int(s[0]);c=int(s[1]);
        tmp=[];
        while(c!=0):
            cc=c%26;
            if(cc!=0):
                tmp+=[cc];c//=26;
            else:
                tmp+=[26];c=c//26-1;
        tmp.reverse();
        for i in tmp:
            print(chr(i+ord("A")-1),end="");
        print(r);
    else:
        r=c=0;
        for i in s:
            if('A'<=i<='Z'):c=c*26+ord(i)-ord("A")+1;
            else:r=r*10+ord(i)-ord("0");
        print("R%dC%d" % (r,c));

cf#2a

n=int(input());
name=[];rou=[];score=[];win=[];
for i in range(n):
    tmp=(input()).split(" ");
    tmp[1]=int(tmp[1]);
    rou.append(tmp);
    if(tmp[0] not in name):
        name.append(tmp[0]);
        score.append(tmp[1]);
        win.append(0);
    else:
        index=name.index(tmp[0]);
        score[index]+=tmp[1];
ans=max(score);
for i in range(len(score)):
    if(score[i]==ans):
        win[i]=1;
        score[i]=0;
for a in rou:
    index=name.index(a[0]);
    score[index]+=a[1];
    if(score[index]>=ans and win[index]==1):
        print(a[0]);
        break;

cf#3a

def judge(s,t):
    sx=s[0];sy=s[1];
    tx=t[0];ty=t[1];
    if(sx==tx and sy==ty):return "end";
    if(tx>sx and ty>sy):
        s[0]+=1;s[1]+=1;
        return "RU";
    if(tx>sx and ty<sy):
        s[0]+=1;s[1]-=1;
        return "RD";
    if(tx<sx and ty>sy):
        s[0]-=1;s[1]+=1;
        return "LU";
    if(tx<sx and ty<sy):
        s[0]-=1;s[1]-=1;
        return "LD";
    if(tx==sx and ty<sy):
        s[1]-=1;
        return "D";
    if(tx==sx and ty>sy):
        s[1]+=1;
        
        return "U";
    if(tx<sx and ty==sy):
        s[0]-=1;
        return "L";
    if(tx>sx and ty==sy):
        s[0]+=1;
        return "R";
s=input();t=input();
s=[s[0],s[1]];t=[t[0],t[1]];
s[0]=ord(s[0])-ord('a')+1;s[1]=int(s[1]);
t[0]=ord(t[0])-ord('a')+1;t[1]=int(t[1]);
cnt=0;ans=[];
move=judge(s,t);
while(move!="end"):
    ans.append(move);
    cnt+=1;
    move=judge(s,t);
print(cnt);
for i in ans:print(i);

cf#4a

w=int(input());
if(w%2==0 and w>2):print("YES");
else:print("NO");

cf#5a
现在一般能一次性编译通过了,开始对用python写代码有点感觉啦。

name=[];ans=0;
while(1):
    try:
        s=input();
        if(s[0]=='+'):name.append(s[1:]);
        elif(s[0]=='-'):name.remove(s[1:]);
        else:
            tmp=s.split(":");
            ans+=(len(tmp[1])*len(name));
    except:
        break;
print(ans);

cf#5b
要求将给定的文本居中显示,做这道题使我发现python这种脚本处理文本非常方便。

def change(i):
    global a;global flag;
    l=len(a[i]);
    tmp="";
    space=maxlen-l;
    for j in range(space//2):tmp+=' ';
    if(space%2==0):
        a[i]='*'+tmp+a[i]+tmp+'*';
    else:
        if(flag==0):a[i]='*'+tmp+a[i]+tmp+' *';
        else:       a[i]='*'+tmp+' '+a[i]+tmp+'*';
        flag^=1;
a=[];
maxlen=0;
while(1):
    try:
        tmp=input();
        le=len(tmp);
        if(le>maxlen):maxlen=le;
        a.append(tmp);
    except:
        break;
cnt=len(a);
flag=0;
for i in range(cnt):
    change(i);
tmp="";
for i in range(maxlen+2):tmp+='*';
a.insert(0,tmp);a.append(tmp);
for i in a:print(i);

cf#6a

def judge(li):
    li.sort();
    if(li[0]+li[1]>li[2]):return 1;
    elif(li[0]+li[1]==li[2]):return 0;
    else:return -1;
a=input().split(" ");
for i in range(4):
    a[i]=int(a[i]);
flag1=flag2=0;
for i in range(4):
    for j in range(i+1,4):
        for k in range(j+1,4):
            ret=judge([a[i],a[j],a[k]]);
            if(ret==1):flag1=1;break;
            elif(ret==0):flag2=1;
if(flag1==1):print("TRIANGLE");
elif(flag2==1):print("SEGMENT");
else:print("IMPOSSIBLE");

cf#6b

def judge(x,y):
    if(0<=x<=n-1 and 0<=y<=m-1):return 1;
    else:return 0;

def cal(x,y):
    global room;global color;
    if(judge(x+1,y)):
        if('A'<=room[x+1][y]<='Z' and room[x+1][y]!=c):
            color.add(room[x+1][y]);
    if(judge(x-1,y)):
        if('A'<=room[x-1][y]<='Z' and room[x-1][y]!=c):
            color.add(room[x-1][y]);
    if(judge(x,y+1)):
        if('A'<=room[x][y+1]<='Z' and room[x][y+1]!=c):
            color.add(room[x][y+1]);
    if(judge(x,y-1)):
        if('A'<=room[x][y-1]<='Z' and room[x][y-1]!=c):
            color.add(room[x][y-1]);

        
a=input().split(" ");
n=int(a[0]);m=int(a[1]);c=a[2];
color=set();
room=[];
for i in range(n):
    room.append(list(input()));
for i in range(n):
    for j in range(m):
        if(room[i][j]==c):
            cal(i,j);
print(len(color));

cf#7a

def judge(i,flag):
    global a;
    paint=1;
    if(flag==0):
        for j in range(8):
            if(a[i][j]=='W'):
                paint=0;
                break;
    else:
        for j in range(8):
            if(a[j][i]=='W'):
                paint=0;
                break;
    return paint;
a=[];
ans=0;
for i in range(8):
    r=list(input());
    a.append(r);
for i in range(8):ans+=judge(i,0);
for i in range(8):ans+=judge(i,1);
if(ans==16):print(8);
else:       print(ans);

cf#7b

def alloc(s):
    global m;global size;
    cnt=0;
    for i in range(1,size+2):
        if(m[i]==0):
            cnt+=1;
            if(cnt==s):
                for j in range(i-cnt+1,i+1):m[j]=1;
                return [i-cnt+1,i];
        else:cnt=0;
    return [-1];

def erase(begin,end):
    global m;
    for i in range(begin,end+1):m[i]=0;
    return;

a=input().split(" ");
t=int(a[0]);size=int(a[1]);
m=[0];
b=0;
block=[0];
for i in range(1,size+1):m.append(0);
m.append(1);
while(t!=0):
    com=input().split(" ");
    if(com[0]=="alloc"):
        ret=alloc(int(com[1]));
        if(ret[0]!=-1):
            b+=1;
            ret.append(1);
            block.append(ret);
            print(b);
        else:print("NULL");
    elif(com[0]=="erase"):
        x=int(com[1]);
        if(x<=0 or x>b or block[x][2]==0):print("ILLEGAL_ERASE_ARGUMENT");
        else:
            block[x][2]=0;
            erase(block[x][0],block[x][1]);
    else:
        if(len(block)==0):continue;
        use=0;
        for i in range(1,len(block)):
            if(block[i][2]==0):continue;
            use+=block[i][1]-block[i][0]+1;
            blank=0;
            for j in range(1,block[i][0]):
                if(m[j]==0): blank+=1;
            block[i][0]-=blank;block[i][1]-=blank;
        for i in range(1,use+1):m[i]=1;
        for i in range(use+1,size+1):m[i]=0;
    t-=1;

cf#8a
python处理字符串真的很方便,做这题的时候开始用s.reverse(),结果发现字符串对象没有这个方法。。然后get了一个新技能。
也就是字符串的反向切片:s[begin:end:-step](begin>end)
begin大于end表示此时想反向切片,此时步进要设为负值。
正向切片时不用显式的写出步进字段。
反向切片时则一定写写出步进字段,特别的,如果是对整个字符串进行切片,则begin和end都可以省略。
注意一点:end是不包括在切片内的,即半开半闭区间[begin,end)

s=input();
s1=input();
s2=input();
flag1=flag2=0;
pos1=s.find(s1);
if(pos1>=0):
    pos2=s[pos1+len(s1):].find(s2);
    if(pos2>=0):flag1=1;
s=s[::-1];
pos1=s.find(s1);
if(pos1>=0):
    pos2=s[pos1+len(s1):].find(s2);
    if(pos2>=0):flag2=1;
if(flag1==1 and flag2==1):print("both");
elif(flag1==1):print("forward");
elif(flag2==1):print("backward");
else:print("fantasy");

cf#8b
这道水题让我收获挺多。
首先是让我知道了python中dict的关键字和set的元素不能是list但可以是tuple。
其次,由于python的for语句的特性(貌似上界无法动态改变),所以只好用while或者递归来实现bfs(当然也可以直接用collections里面的deque,但是效率不知怎么样,所以还是用数list模拟)
再者,这道题开始考虑欠佳,以为排除几种特殊情况即可,实际上“特殊情况”很多。。所以把给出路径上的方格设为empty,其余设为obstruct,这样就形成了“最好情况”,然后bfs算最短路径,如果这种情况下都不是最短路径,就一定不存在一种情况它是最短路径

def judge(x,y):
    global m;
    if(m[x][y]==0):return 1;
    return 0;

def bfs():
    global dest;
    q=[];
    q.append([100,100,0]);
    iq=1;
    i=0;
    vis=set();vis.add((100,100));
    while(i<=iq-1):
        x=q[i][0];y=q[i][1];dep=q[i][2];
        if(x==dest[0] and y==dest[1]):return dep;
        else:
            if(judge(x+1,y)==1 and ((x+1,y) not in vis)):
                q.append([x+1,y,dep+1]);iq+=1;vis.add((x+1,y));
            if(judge(x-1,y)==1 and ((x-1,y) not in vis)):
                q.append([x-1,y,dep+1]);iq+=1;vis.add((x-1,y));
            if(judge(x,y+1)==1 and ((x,y+1) not in vis)):
                q.append([x,y+1,dep+1]);iq+=1;vis.add((x,y+1));
            if(judge(x,y-1)==1 and ((x,y-1) not in vis)):
                q.append([x,y-1,dep+1]);iq+=1;vis.add((x,y-1));
        i+=1;

move=input();
m=[];
dest=[100,100];
for i in range(210):
    m.append([]);
    for j in range(210):m[i].append(1);
m[100][100]=0;
for i in move:
    if(i=="U"):dest[1]+=1;
    if(i=="D"):dest[1]-=1;
    if(i=="L"):dest[0]-=1;
    if(i=="R"):dest[0]+=1;
    m[dest[0]][dest[1]]=0;
if(bfs()==len(move)):print("OK");
else:print("BUG");

cf#9a

def gcd(a,b):
    if(a<b):a^=b;b^=a;a^=b;
    if(b==0):return a;
    return  gcd(b,a%b);
a=input().split(" ");
x=int(a[0]);y=int(a[1]);
tmp=max(x,y);
if(tmp==1):print("1/1");
else:
    zi=6-tmp+1;
    g=gcd(zi,6);
    print("%d/%d" % (zi//g,6//g));

cf#9b
这道题不错,学到了python的数学运算,特别是开方,原先不知道怎么开方,查了下可以用math.sqrt(),但是直接**0.5更方便。
第二个是list的sort方法,可以通过参数key实现多关键字排序,非常方便。

inp=input().split();
n=int(inp[0]);vb=int(inp[1]);vs=int(inp[2]);
x=input().split();
for i in range(n):x[i]=int(x[i]);
dest=input().split();
dest[0]=int(dest[0]);dest[1]=int(dest[1]);
ans=[];
for i in range(1,n):
    tmp=(((dest[0]-x[i])**2)+(dest[1]**2))**0.5;
    ans.append((x[i]/vb+tmp/vs,tmp,i+1));
ans.sort(key=lambda x:(x[0],x[1]));
print(ans[0][2]);