net-force.nl/programming writeup


  从 wechall.net 到 net-force.nl 网站,发现网站的内容不错,里面也有不同类型的挑战题目:Javascript / Java Applets / Cryptography / Exploits / Cracking / Programming / Internet / Steganography,有的还有一定的难度。注册了帐号做题,我也从中学到了不少知识。对网络攻防有兴趣的同学可以尝试下。

 

level 601: Keep walking... 
/*
This is a challenge to test your basic programming skills.

Pseudo code:
Set X = 1
Set Y = 1
Set previous answer = 1

answer = X * Y + previous answer + 3

After that => X + 1 and Y + 1 ('answer' becomes 'previous answer') and repeat this till you have X = 525.

The final answer is the value of 'answer' when X = 525. Fill it in below to check if it's the correct answer. If it is, you will get the password for the challenge page.
*/
  第一题是热身题,直接按照题目所说的写代码就行了。

#include <stdio.h>
 
int main()
{
    int answer = 1;
    for(int x = 1, y = 1; x<=525; ++x, ++y)
        answer += (x*y + 3);
    printf("answer: %d\n", answer);
 
    return 0;
}
answer

 

 level 602: Are you fast enough?

/*
This challenge is a simple calculation.
When you click on the link below you will get a number.
The calculation is: answer = (number * 3 + 2) - 250

Example with number 1500:
4252 = (1500 * 3 + 2) - 250

In this example 4252 is the answer...answer like this: prog2.php?solution=4252
You must do this within 2 seconds. If the answer is correct, you will get the password.
Get a number.
https://net-force.nl/challenge/level602/prog2.php
*/
  限时2秒,手动计算再发送肯定是来不及的。不变的信息先构造好,添加动态信息马上发出去。
  春节回家需要网上买火车票,比别人晚半秒钟下手可能就没票了,所以那些车次,时间,身份证等信息需要事先填写好再刷票。
  javascript 是浏览器用的语言,于是在 Chrome 浏览器的控制台(按 F12 键或 Ctrl + Shift + J 唤出)粘贴以下代码运行。就是用 XMLHttpRequest 构造 HTTP 请求。

    var url = 'https://net-force.nl/challenge/level602/prog2.php';
    var req = new XMLHttpRequest();
    req.open('GET', url, false/*asynchronous*/);
//    req.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
//    req.send(message);
    req.send(null);
    if(req.status == 200)
    {
        var text = req.responseText;
        var number = parseInt(text.split("'", 2)[1]);
        answer = (number * 3 + 2) - 250;
 
        var url_answer = url + '?solution=' + answer;
        req.open('GET', url_answer, false/*asynchronous*/);
        req.send(null);
        console.log(req.responseText);
    }
answer

  上面的 url 写错了,比如多加了 www. 或者写成了 http 协议,服务器可能会返回以下错误。
XMLHttpRequest cannot load https://www.net-force.nl/challenge/level602/prog2.php. Origin https://net-force.nl is not allowed by Access-Control-Allow-Origin.
这个是 Javascript 的同源策略(Same Origin Policy),是由 Netscape 公司提出的一个安全策略。

请求返回的内容如下,除了数字很可能不同。
<p>You will need this number '8616'.</p>
<i>Answer like this: prog2.php?solution=12345</i><br>
我们可以用'作为分割符,得到我们要的数字,找到第二个就不用接着找下去了。代码为:text.split("'", 2)[1]
这种做法我在读 sqlmap 的代码 sqlmap/lib/utils/versioncheck.py 的时候看到过,它用空格作为分隔符获取 Python 的版本字符串。

 

 

level 603: Wanna play?!?!

/*

You have 5 balls that you have to divide (put the balls in the cups) between a number of cups in all possible ways. 

1 cup: 
--- 
|5| 
--- 
1 possibility 

Total: 1 possibility 

2 cups: 
----- 
|1|4| 
----- 
|2|3| 
----- 
|3|2| 
----- 
|4|1| 
----- 
|5|0| 
----- 
|0|5| 
----- 
6 possibilities

Total: 7 possibilities

3 cups:
21 possibilities 
Total: 28 possibilities 

Continue until you have 500 cups. The total of possibilities from 1 to 500 is the answer.

*/

排列组合问题
5个球放N个杯子的放法有多少中,转换成数学语言描述:
x1+x2+...+xn = 5, x1~xn 均为0或正整数。求方程解的个数。
(x1+1)+(x2+1)+...+(xn+1) = 5 + n
于是等价于
x1+x2+...+xn = n+5, x1~xn 均为正整数,方程解的个数。


x1与x2互换属于不同的解,所以是排列问题而非组合问题。
n+5个杯子排成一列,中间用n-1个栏板隔开,所以答案是 C(n+5, n-1)
因为 C(m, n) = C(m, m-n),所以 C(n+5, n-1) = C(n+5, 6)
我们验证一下,
1个杯子: C(6,6)=1
2个杯子: C(7,6)=C(7,1)=7
3个杯子: C(8,6)=C(8,2)=(8*7)/(1*2)=28
与例子保持吻合,以此类推
500个杯子就是 C(505,6)=505*504*503*502*501*500/6!

#include <stdio.h>
#include <inttypes.h>
 
int main()
{
    int64_t cups = 500;
    int64_t answer = cups*(cups+1)*(cups+2)*(cups+3)*(cups+4)*(cups+5)/(1*2*3*4*5*6);
    printf("answer: %ld\n", answer);
 
    return 0;
}
answer

懒得用手算,还是用程序计算出来的,不过要小心数值溢出问题。

 

 

level604: Aaarrrgghhh!!
/*
Count how many times the sequence '123' is present in aap.txt in all 8 directions.
Example:
113
223
333
In the above text there are 3 times '123'.
*/

#include <stdio.h>

int main()
{
    const char num123[] = "aap.txt";
    FILE* file = fopen(num123, "rt");
    if(!file)
        return -1;

    const int size = 100;
    int array[size][size];
    int ch, count = 0;
    while((ch = fgetc(file)) != EOF)
    {
        if(ch == '\r' || ch == '\n')
            continue;

        ch -= '0';
        if(ch < 1 || ch > 3)
            printf("array[%d][%d] = %d\n", count/size, count%size, (int)ch);
        assert(1 <= ch && ch <= 3);


        array[count/size][count%size] = (ch==3)?4:ch;
        ++count;
    }

    assert(count == size*size);

    int answer = 0;
    for(int i=0; i<size; ++i)
    {
        for(int j=0; j<size; ++j)
        {
            if(array[i][j]!=2)
            continue;

            bool range_h = j>=1 && j<size-1;
            bool range_v = i>=1 && i<size-1;
            if(range_h && array[i][j-1]+array[i][j+1]==5)  // 124 421
                ++answer;

            if(range_v && array[i-1][j]+array[i+1][j]==5)  // 124T 421T
                ++answer;

            if(range_h && range_v)
            {
                if(array[i-1][j-1]+array[i+1][j+1]==5 ||
                    array[i-1][j+1]+array[i+1][j-1]==5)
                ++answer;
            }
        }
    }
    printf("answer: %d\n", answer);
 
    return 0;
}
answer

数不同方向上123出现的次数,这个简单,只是不要数漏了。
我将数字123换成了124,这样等价于判断中间一个数是2,两端的数加起来等于5就可以了。

 

level605: How do you like the EURO?

/*

If you want to buy something for less than 100 Euro you can pay with different 'units' (coins and bills).
At the moment the 6 best existing units to use are: 1, 2, 5, 10, 20 and 50.

If you want to buy something (that is less than 100 Euro) you have to use some of those units to pay for it.

Now let's go shopping with these 6 units :)
You are going to search for products that are 1 euro untill 99 euro, and you are going to buy one of each. Each time you buy something you choose as less units as possible (you have an infinite amount of units). 

Let's see how many units we need if we use the units 1, 2, 5, 10, 20 and 50:

1 EURO -> only 1 unit: 1
2 EURO -> 1 unit : 2
3 EURO -> 2 units : 1 + 2
..
..
..
98 EURO -> 6 units : 1 + 2 + 5 + 20 + 20 + 50
99 EURO -> 6 units : 2 + 2 + 5 + 20 + 20 + 50

Now we will take the average of the needed units between 1 and 99, which is 3.4 units.

That's quite alright, but we can do better...

Your job is to find the lowest average with 6 different (integer) units. 
Maybe a 3 euro coin is a good idea? And what about a 22 euro bill? Try it out :)

Enter your answer below like this => 1:2:5:10:20:50 (example). 
So from the lowest to the highest unit with a ':' in between.

*/

  这题可以用动态规划算法。动态规划有那么点点像模拟退火,不同的是,动态规划一定会搜索到全局最优解。模拟退火很多时候是在连续区间得到良好的解(不一定是最优解),而动态规划一般是离散数值求最优解。动态规划的搜索空间很大,在慢慢退火中缩小搜索空间。理论上是一种以概率l 收敛于全局最优解的全局优化算法。
  动态规划的思想,根据前一步的状态确定下一步的走向。动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
  六种硬币分别记作 u0、u1、u2、u3、u4、u5,从小到大排列不重复。首先必须有1,否则我们首先就表示不了1。从0到1,从无到有。1就是光,因为上帝说要有光,于是就有了光。u0最小,所以 u0=1 是肯定的。很显然,u5必须不超过最大值的一半,这样可以有半数以上的支付用上u5,否则最大值u5不能得到充分利用。每个硬币面额之间的差值不能太小,太小则稍微大点的支付会消耗很多个硬币;也不能太大,太大了很多支付就表示不了,只能用很多个1才能分解。结论:1=u0<u1<u2<u3<u4<u5<=50
  我们可以为每个支付设定上界。N = (N/u)*u + (N%u)*1(等式有边是C语言的整数除法与取模运算),即支付N元可以用 N/u 个u元硬币和 N%u 个1元硬币完成,于是支付N元最多可以用 f(N)= N/u + N%u 个硬币。
  题目给出了硬币组合 1:2:5:10:20:50 最终消费 340 个硬币。均摊一下,340/99~=3.43。我们还想裁剪u1的搜索空间。很显然,只用 u0:u1 硬币支付,比用 u0:u1:u2:u3:u4:u5 硬币支付花费的硬币多。如果就用 u0:u1 货币,可以想象成u1进制数,于是公式 f(N)= N/u1 + N%u1 的意思就是N表示为u1进制数后,每位数字的和(个位数+十位数+百位数+...)。参照前面的硬币组合,粗略估计(1+u1)/2.0<3.43,得u1<5.86。详细估计,我们有所有支付位数的和 ((0+1+2+...+(u1-2)+(u1-1)) + (1+2+...+(u1-2)+(u1-1)+u1) + (2+3+...+(u1-1)+u1+(u1+1)) + ... < 340
  我们推了这么多逻辑,会有用的。我们需要裁剪一下无意义的搜索空间。你会发现,如果没有这些限制,搜索空间大得程序运行半天都没有结束。

#include <stdio.h>
#include <assert.h>

#ifdef _WIN32
#define snprintf _snprintf
#endif

const int TYPE = 6;
int unit[TYPE] = {1, 2, 5, 10, 20, 50};
const int MAX = 100;  // buy something (that is less 100 Euro)

int min[MAX];

/**
 * we have 6 different units, u0 ~ u5. Each value v can be expressed with  
 * v = r0*u0 + r1*u1 + r2*u2 + r3*u3 + r4*u4 + r5*u5, or 
 * v = (r0, r1, r2, r3, r4, r5) with respect to (u1, u2, u3, u4, u5)
 */
void limit(/*int u0 = 1, */int u1, int u2, int u3, int u4, int u5)
{
    assert(/*u0*/1<u1 && u1<u2 && u2<u3 && u3<u4 && u4<u5);
    const int u0 = 1;
    min[0] = 0;  // 0 = (0, 0, 0, 0, 0)
    min[1] = 1;  // 1 = (1, 0, 0, 0, 0), note that u0 must be 1

    for(int i=2; i<MAX; ++i)
    {
#if 0
             if(i<u1) min[i] = i/u0 + i%u0;
        else if(i<u2) min[i] = i/u1 + i%u1;
        else if(i<u3) min[i] = i/u2 + i%u2;
        else if(i<u4) min[i] = i/u3 + i%u3;
        else if(i<u5) min[i] = i/u4 + i%u4;
        else          min[i] = i/u5 + i%u5;
#else
        // Look, 6 comparisons can be compressed to at most 3!
        // (in mathematics, 3! = 6, no pun intended)
        if(i < u2)
        {
            if(i < u1) min[i] = i/u0 + i%u0;
            else       min[i] = i/u1 + i%u1;
        }
        else if(i < u4)
        {
            if(i < u3) min[i] = i/u2 + i%u2;
            else       min[i] = i/u3 + i%u3;
        }
        else
        {
            if(i < u5) min[i] = i/u4 + i%u4;
            else       min[i] = i/u5 + i%u5;
        }
#endif
    }
}

/**
 * Let's go shopping with these 6 different units: u0 ~ u5.
 * Search for products that are (0, MAX) euro(s) sharp, and buy one of each.
 * Each time I buy something, I choose as less units as possible. (Supposing
 * I have an infinite amount of units). 
 */
void pay(/*int u0 = 1, */int u1, int u2, int u3, int u4, int u5)
{
    limit(/*u0 = 1, */u1, u2, u3, u4, u5);
    const int high = MAX/u5 + MAX%u5;  // at most

    for(int r0=0; r0<u1; ++r0)
    for(int r1=0; r1<u2; ++r1)
    for(int r2=0; r2<u3; ++r2)
    for(int r3=0; r3<u4; ++r3)
    for(int r4=0; r4<u5; ++r4)
    for(int r5=0; r5<high; ++r5)
    {
        int v = r0/*u0=1*/ + r1*u1 + r2*u2 + r3*u3 + r4*u4 + r5*u5;
        if(v >= MAX)
            continue;

        int sum = r0 + r1 + r2 + r3 + r4 + r5;
        if(sum > min[v])
            continue;
        
        if(sum <= min[v])
            min[v] = sum;
    }
}

int all()
{
    int sum = 0;
    for(int i=1; i<MAX; ++i)
        sum += min[i];
    return sum;
}

int main()
{
    pay(/*1, */2, 5, 10, 20, 50);

    const int mid = MAX/2;
    int cnt = all();
    const int u0 = 1;
    for(int u1=2;    u1<6;      ++u1)
    for(int u2=u1+1; u2<=mid-3; ++u2)
    for(int u3=u2+1; u3<=mid-2; ++u3)
    for(int u4=u3+1; u4<=mid-1; ++u4)
    for(int u5=u4+1; u5<=mid;   ++u5)
    {
        pay(/*u0, */u1, u2, u3, u4, u5);
        int tmp = all();
        if(tmp < cnt)
        {
            cnt = tmp;
            printf("find new record [%03d] from %d:%d:%d:%d:%d:%d\n", cnt, u0, u1, u2, u3, u4, u5);
        }
    }

    return 0;
}
answer

  上面的代码还可以优化,因为我们只关心总共花了多少硬币,对分别花几个u0, u1, u2, u3, u4, u5 并不关心。我用 bool 数组 array 标志是否支付过,以及支付用的硬币数量 coin,设置初时状态后多次扫描,第一回的支付合用一个硬币,第二个回合的支付用两个硬币,依次类推。这样扫描,每次找得都是用得最少得硬币。

#include <stdio.h>
#include <string.h>

int main()
{
    const int MAX = 100, mid = MAX/2;  // excluded

    // upper boundary payed with coin u0 only, or assign 340 for 1:2:5:10:20:50.
    int min = (1+(MAX-1))*(MAX-1)/2;

    bool array[MAX + mid] = {false};
    bool *p = array + mid;

    const int u0 = 1;
    for(int u1=2;    u1<6;      ++u1)
    for(int u2=u1+1; u2<=mid-3; ++u2)
    for(int u3=u2+1; u3<=mid-2; ++u3)
    for(int u4=u3+1; u4<=mid-1; ++u4)
    for(int u5=u4+1; u5<=mid;   ++u5)
    {
        memset(p, 0/*false*/, MAX);

        int round = 1;  // 1st round use 1 coin, 2nd round use 2 coins, etc.
        p[u0] = p[u1] = p[u2] = p[u3] = p[u4] = p[u5] = true;
         int coin = 6, unpayed = (MAX-1) - 6;  // initial states

        do
        {
            ++round;
            int filled = 0;
            for(int i=MAX-1; i>0; --i)
            {
                if(!p[i])
                {
                    p[i] = p[i-u0] | p[i-u1] | p[i-u2] | p[i-u3] | p[i-u4] | p[i-u5] | p[i-u2];
                    if(p[i])
                        ++filled;
                }
            }

            coin += filled*round;
            if(coin >= min)  // fast fail
                break;

            unpayed -= filled;
        } while(unpayed > 0);

        if(coin < min)
        {
            min = coin;
            printf("find new record [%03d] from %d:%d:%d:%d:%d:%d\n", min, u0, u1, u2, u3, u4, u5);
        }

    }
    return 0;
}
answer

  上面的代码用了 fast fail,如果在支付的过程中发现了硬币数量比当前最低记录还大,果断放弃计算去搜索下一个解。而且这个必须从大到小搜索才有效 for(int i=MAX-1; i>0; --i),想想为什么。

 

level606: Your highness the queen

/*

N = number of Queens.

You have to put N Queens onto a chess board of N * N squares, so that the Queens don't threaten each other. (horizontally, vertically, diagonally)
How many possible setups are there?

Thus: 
How can I put 4 Queens onto a 4*4 board
How can I put 5 Queens onto a 5*5 board
etc... 

Calculate all setups for 4 to 10 Queens, take the sum of the results - the sum is the password for the Challenge Page!

*/

N皇后问题求解,用递归或者回溯求解,学过算法设计的都应该清楚。

递归算法:
回溯算法:

#include <stdio.h>
#include <math.h>

bool place(int x[], int k)
{
    using std::abs;
    for(int i=1; i<k; ++i)
    {
        if(x[i]==x[k] || abs(x[i]-x[k]) == abs(i-k))
            return false;
    }
    return true;
}

int NQueens(int x[], int n)
{
    int count = 0;
    int k = 1; // put first queen
    x[k] = 0;

    while(k > 0)
    {
        do{ ++x[k]; } while((x[k]<=n && !place(x, k)));
        if(x[k] <= n)
        {
            if(k == n)
            {
                for(int i=1; i<=n; ++i)
                    std::cout<<x[i]<<',';
                std::cout<<'\n';

                ++count;
                //break;  // return if you need only one solution.
                ++x[k];
            }
            else
            {
                ++k;
                x[k]=0;
            }
        }
        else
        {
            x[k]=0;
            --k;
        }
    }

    x[0] = count;  // x[0] can be used to store solution.
    return count;
}

int main()
{
    const int N = 10;
    int a[N+1] = {0}; // start from index 1

    int sum = 0;
    for(int n=4; n<=N; ++n)
        sum += NQueens(a, n);
    
    return 0;
}
backtracing

排列算法:

#include <iostream>
#include <algorithm>
 
bool isAllowed(const std::string& s, int length)
{
    for(int i=0; i<length; ++i)
    {
        for(int j=i+1; j<length; ++j)
        {
            using std:abs;
            if(abs(s[i]-s[j]) == abs(i-j)) // diagonal
                return false;
        }
    }
 
    return true;
}
 
int main()
{
    int queen[15]={0};

    std::string s = "abcdefghijklmnopqrstuvwxyz";

    int sum = 0;
    for(int i=4; i<=10; ++i)
    {
        int round = 0;
        std::string::iterator begin = s.begin();
        std::string::iterator end = s.begin() + i;

        std::sort(begin, end);  // reset
        do
        {
            if(isAllowed(s, i))
            ++round;
        } while(std::next_permutation(begin, end));
        std::cout<<i<<':'<<round<<'\n';

        sum += round;
    }
    std::cout<<"sum: "<<sum<<std::endl;

    return 0;
}
permutation

  选用什么算法求解看自己的偏好。排列算法的每次排列都会从头计算所有的皇后是否冲突,而回溯算法只需要计算当前放置的皇后是否与之前已放置的皇后之间是否有冲突。排列用了 STL 中的 next_permutation 算法,但逻辑看起来较回溯算法简单。next_permutation 是用来计算比当前数大,所有大数中最小的排列数,例如:15243的下一个排列数为15324。

 

level607: Infinite division
/*
Calculate 13155187 / 13417 with 5000 decimals.
The solution is the last 6 numbers (of those 5000 decimals).
*/

初学者成长,一般都会经历写大数运算的程序,这里就不写了。
大数运算,一般语言都会有成熟的库供调用。首选 Python 的 Decimal
https://www.python.org/dev/peps/pep-0237/    https://docs.python.org/3/library/decimal.html
其次 Java 的 BigInteger、BigDecimal,大家可以对比一下。

>>> from decimal import *
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[], traps=[DivisionByZero, InvalidOperation, Overflow])
>>> getcontext().prec = 8
>>> Decimal(13155187) / Decimal(13417)
Decimal('980.48647')
>>> getcontext().prec = 5003
>>> Decimal(13155187) / Decimal(13417)
Decimal('980.486472385779235298501900573898785
...A_LONG_NUMBER_STRING_HERE...
9233807855705')


1/128 = 0.0078125 has a scale of 7 and precision 5
因为整数部分有3位,所以 presicion 需要算到 5003 位。
用 java.math.BigInteger 做大整数的运算:

import java.math.BigDecimal;
 
class Level607
{
    public static void main(String[] args)
    {
        BigDecimal dividend = new BigDecimal(13155187);
        BigDecimal divisor = new BigDecimal(13417);
        int scale = 5000;
        BigDecimal answer = dividend.divide(divisor, scale, BigDecimal.ROUND_HALF_UP);
        String unscaled = answer.unscaledValue().toString();
//        System.out.println(unscaled + " * 10^-"+answer.scale());
        int length = unscaled.length();
        System.out.println(unscaled.substring(length-6, length));
    }
}
 
Java

 

 

level608: Multiple Languages
/*
Download this file.

I don't need to give you any hints, you will get the password automatically...
*/
  目测有 Pascal、C++、Assembly 语言。不想图麻烦来安装新的编译器,用 google 搜索到一些 online IDE,粘贴代码过去,编译并运行,返回结果,如果有语法错误则会提示错误。
https://ideone.com/
http://www.tutorialspoint.com/
  大家可以 google 一下 quine,有一些好玩的东西。比如写一段可以产生自己的代码,再比如可不可以写一个即是C语言,又是D语言的程序(C和D泛指计算机语言),而且用其编译器编译没有语法错误?

  脱 Pascal 语言得 C++ 代码,再脱 C++ 语言得 x86 汇编,原形毕露。

#include <iostream>
#include <fstream>
#include <conio.h>
#include <io.h>
#include <string.h>
 
void part1();
void part2();
 
std::ostringstream file("lastone.txt");
int a,b,c,d,e,f;
 
using std::endl;
 
void main()
{
    file<<"Work out this part manually. It is assembly for the Pentium 2:"<<endl<<endl;
    file<<"Calculation: "<<endl;
    part1();
    file<<"         MOV EAX,A"<<endl;
    file<<"         ADD EAX,B"<<endl;
    file<<"         MOV G,EAX"<<endl;
 
    file<<"         MOV EAX,A"<<endl;
    file<<"         ADD EAX,C"<<endl;
    file<<"         MOV H,EAX"<<endl;
    file<<"         MOV EAX,B"<<endl;
    file<<"         ADD EAX,D"<<endl;
    file<<"         MOV I,EAX"<<endl;
 
    file<<"         MOV EAX,A"<<endl;
    file<<"         ADD EAX,E"<<endl;
    file<<"         MOV J,EAX"<<endl;
    file<<"         MOV EAX,C"<<endl;
    file<<"         ADD EAX,F"<<endl;
    file<<"         MOV K,EAX"<<endl;
    part1();
    part2();
    file<<"The password is the contents of G H I J K"<<endl;
    getch();
}
 
void part1()
{
    file<<"         MOV EAX,C"<<endl;
    file<<"         MOV EBX,D"<<endl;
    file<<"         MOV D,EAX"<<endl;
    file<<"         MOV C,EBX"<<endl;
}
 
void part2()
{
    a=40;
    b=44;
    c=23;
    d=72;
    e=25;
 
    f=43;
    file<<"             "<<endl;
    file<<"A        DW  "<<a<<endl;
    file<<"B        DW  "<<b<<endl;
    file<<"C        DW  "<<c<<endl;
    file<<"D        DW  "<<d<<endl;
    file<<"E        DW  "<<e<<endl;
    file<<"F        DW  "<<f<<endl;
    file<<"             "<<endl;
    file<<"             "<<endl;
    file<<"G        DW  0"<<endl;
    file<<"H        DW  0"<<endl;
    file<<"I        DW  0"<<endl;
    file<<"J        DW  0"<<endl;
    file<<"K        DW  0"<<endl;
    file<<"             "<<endl;
    file<<"             "<<endl;
}
C++
Work out this part manually. It is assembly for the Pentium 2:
 
Calculation:
    MOV EAX,C
    MOV EBX,D
    MOV D,EAX
    MOV C,EBX
    MOV EAX,A
    ADD EAX,B
    MOV G,EAX
    MOV EAX,A
    ADD EAX,C
    MOV H,EAX
    MOV EAX,B
    ADD EAX,D
    MOV I,EAX
    MOV EAX,A
    ADD EAX,E
    MOV J,EAX
    MOV EAX,C
    ADD EAX,F
    MOV K,EAX
    MOV EAX,C
    MOV EBX,D
    MOV D,EAX
    MOV C,EBX

A DW  40
B DW  44
C DW  23
D DW  72
E DW  25
F DW  43


G DW  0
H DW  0
I DW  0
J DW  0
K DW  0
Assembly

MOV EAX,C ; EAX=23
MOV EBX,D ; EBX=72
MOV D,EAX ; D=23
MOV C,EBX ; C=72
MOV EAX,A ; EAX=40
ADD EAX,B ; EAX=84
MOV G,EAX ; G=84
MOV EAX,A ; EAX=40
ADD EAX,C ; EAX=112
MOV H,EAX ; H=112
MOV EAX,B ; EAX=44
ADD EAX,D ; EAX=67
MOV I,EAX ; I=67
MOV EAX,A ; EAX=40
ADD EAX,E ; EAX=65
MOV J,EAX ; J=65
MOV EAX,C ; EAX=72
ADD EAX,F ; EAX=115
MOV K,EAX ; K=115
MOV EAX,C ; EAX=72
MOV EBX,D ; EBX=23
MOV D,EAX ; D=72
MOV C,EBX ; C=23

The password is the contents of G H I J K
=========================================
G H I J K 的值:84 112 67 65 115

const char answer[] = {84, 112, 67, 65, 115};
printf("%s\n", answer);

 

 

  题外话,net-force.nl 网站的题目水平不错,有兴趣的同学可以注册玩玩,可相互切磋和学习。账号可以绑定到 wechall.net 网站,查看得分与排名情况。
  上面选择了 Programming 方向的一些题目。输入答案,然后点击Enter,服务器返回正确或错误提示信息。这样我们可能有机可乘,比如上面的 level607: Infinite division 问题,答案限定在六位数字,假设我们选择穷举攻击,从 000000~999999 共百万种情况
<form action="challenges/solve/" method="post">
  <input type="hidden" name="f_challengeid" value="51">
  <input type="text" name="f_password" size="18">
  <input type="submit" name="submit" value="Enter">
</form>

curl --include --request POST \
  --cookie 'YOUR_COOKIE_GOES_HERE' \
  --data 'f_challengeid=51&f_password=123456&submit=Enter' \
  https://net-force.nl/challenge/solve/

  估算一下,假设网络速度好,一次请求需要0.5秒钟(实际上一次可以批量发送大量请求或者多台机器同时发送请求),最坏需要五十万秒,一天86400秒,五十万就是一星期左右。这样算下来,代价太大,还不如花一点时间直接解题。
  上面curl命令每次重新握手需要时间,很耗时。Chrome 有款插件 POSTMAN 还不错,有UI界面,而且使用简单方便,经常需要和服务器打交道发请求的同学可以安装。
当然,这是在学习,不是在叫你走旁门左道。也有很多网站都会像 level 602: Are you fast enough? 题目一样,每次请求会变换出不同的数字来抵御上面提到的攻击,类似 12306 刷票中机器难以识别的验证码;或者限时作答,过期则无效,这种例子有短信验证码(目前好像一般都是六位数,限时一分钟)。

 

/* 转载请注明出处:http://www.cnblogs.com/martinium/p/net-force_nl_programming.html */

你可能感兴趣的:(net-force.nl/programming writeup)