picoCTF 2018 – Hideout (General Skills) 下

題目: in out error

utilize: 利用

下載檔案後打開來看一下
發現都是01

看不懂先不管他

我們去Shell上面執行一下這個檔案

依照指示輸入

得到結果如下

看起來是個像亂碼又不像亂碼的東西

回頭看提示他說要把stdout跟stderr分開

http://www.runoob.com/linux/linux-shell-io-redirections.html

http://note.qidong.name/2017/07/bash_stdout_stderr/

https://blog.51cto.com/zlong37/1703427

搞懂stdout跟stderr甚麼時候才可以只輸出其中一個後

輸入指令

#不顯示stdout的輸出,只顯示stderr
./in-out-error 1 > /dev/null 
#不顯示stderr的輸出,只顯示stdout
./in-out-error 2 > /dev/null

1表示stdout
2表示stderr

以./in-out-error 1 > /dev/null 來說
就是說我把1 (stdout) redirect into /dev/null
所以stdout就不會顯示螢幕

後來這邊搞很久啊!

因為發現你使用/dev/null之後

他不會顯示提示要你輸入Please may….的話

不過你仍然要輸入這段話(程式已經有正確執行了 只是沒顯示內容)

或者可以用以下方法
:::success

$ cd /problems/in-out-error_3_9eb10797d687f70cfce62e7c9c2bdea6
$ echo "Please may I have the flag?" | ./in-out-error >~/out.txt 2>~/err.txt

This command will redirect
stderr to err.txt in your home directory and stdout to out.txt.
You can then read the files and in err.txt there will be the flag.
(note that the program may crash if you run it locally or from another directory)
:::

題目: learn gdb

這題是要你debug

GDB 的全稱是 GNU Debuger. 是 linux 底下的一種免費的 debug 程式

提示的地方有說要設breakpoints

打開run檔案後看到都是machine code

下載IDA來試試看能不能反組譯
https://www.hex-rays.com/products/ida/support/download_freeware.shtml

不對.. 忘了為什麼要載IDA
直接用gdb才對

簡單的一些用法
首先編譯的時候可以用以下語法

gcc -g filename.c -o filename

再用gdb運行

gdb filename

常用的一些指令
設定breakpoint
b main
表示breakpoint設置在main函數
要執行的話可以用r
r 就會開始執行這個程式
程式到breakpoint那一行是不會執行的
所以如果我們設在b main再按r
他不會執行main那一行
n 表示執行這一行
執行到最後一行結束
會顯示process XXXXX exited normally

其他命令像是
p 是用來顯示變數的值
p varName

如果要重新開始執行
可以再按r就好
然後原本設置的breakpoint仍然還會在

實際執行看看下載的檔案run

(gdb) b main
Breakpoint 1 at 0x4008cd
(gdb) n
The program is not being run.
(gdb) run
Starting program: /root/CTF/picoCTF2018/Hideout/learngdb/run 
Breakpoint 1, 0x00000000004008cd in main ()
(gdb) n
Single stepping until exit from function main,
which has no line number information.
Decrypting the Flag into global variable 'flag_buf'
.....................................
Finished Reading Flag into global variable 'flag_buf'. Exiting.
__libc_start_main (main=0x4008c9 <main>, argc=1, argv=0x7fffffffe138, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe128)
    at ../csu/libc-start.c:342
342    ../csu/libc-start.c: No such file or directory.

看起來b設在main之後 才跑了一行就結束了?

後來發現解題需要反組繹
而似乎很多都用gdb-peda
所已安裝一下gdb-peda
使用與安裝參考以下
https://github.com/longld/peda

解題一樣先b main再r
首先會發現gdb-peda整個不一樣 比較潮

disas main可以反組繹main
發現decrypt_flag
試著將b設在

gdb-peda$ disas main
Dump of assembler code for function main:
   0x00000000004008c9 <+0>:    push   rbp
   0x00000000004008ca <+1>:    mov    rbp,rsp
=> 0x00000000004008cd <+4>:    sub    rsp,0x10
   0x00000000004008d1 <+8>:    mov    DWORD PTR [rbp-0x4],edi
   0x00000000004008d4 <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x00000000004008d8 <+15>:    mov    rax,QWORD PTR [rip+0x200af9]        # 0x6013d8 <stdout@@GLIBC_2.2.5>
   0x00000000004008df <+22>:    mov    ecx,0x0
   0x00000000004008e4 <+27>:    mov    edx,0x2
   0x00000000004008e9 <+32>:    mov    esi,0x0
   0x00000000004008ee <+37>:    mov    rdi,rax
   0x00000000004008f1 <+40>:    call   0x400650 <setvbuf@plt>
   0x00000000004008f6 <+45>:    mov    edi,0x4009d0
   0x00000000004008fb <+50>:    call   0x400600 <puts@plt>
   0x0000000000400900 <+55>:    mov    eax,0x0
   0x0000000000400905 <+60>:    call   0x400786 <decrypt_flag>
   0x000000000040090a <+65>:    mov    edi,0x400a08
   0x000000000040090f <+70>:    call   0x400600 <puts@plt>
   0x0000000000400914 <+75>:    mov    eax,0x0
   0x0000000000400919 <+80>:    leave  
   0x000000000040091a <+81>:    ret    
End of assembler dump.

設置brakpoint

gdb-peda$ b *0x000000000040090f
Breakpoint 1 at 0x40090f
gdb-peda$ r
Starting program: /root/CTF/picoCTF2018/Hideout/learngdb/run 
Decrypting the Flag into global variable 'flag_buf'
.....................................
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7ed9904 (<__GI___libc_write+20>:    cmp    rax,0xfffffffffffff000)
RDX: 0x7ffff7faa580 --> 0x0 
RSI: 0x7ffff7fa87e3 --> 0xfaa580000000000a 
RDI: 0x400a08 ("Finished Reading Flag into global variable 'flag_buf'. Exiting.")
RBP: 0x7fffffffe050 --> 0x400920 (<__libc_csu_init>:    push   r15)
RSP: 0x7fffffffe040 --> 0x7fffffffe138 --> 0x7fffffffe421 ("/root/CTF/picoCTF2018/Hideout/learngdb/run")
RIP: 0x40090f (<main+70>:    call   0x400600 <puts@plt>)
R8 : 0xa ('\n')
R9 : 0x6013d8 --> 0x7ffff7fa8760 --> 0xfbad2887 
R10: 0x7ffff7faf500 (0x00007ffff7faf500)
R11: 0x246 
R12: 0x400690 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffe130 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400900 <main+55>:    mov    eax,0x0
   0x400905 <main+60>:    call   0x400786 <decrypt_flag>
   0x40090a <main+65>:    mov    edi,0x400a08
=> 0x40090f <main+70>:    call   0x400600 <puts@plt>
   0x400914 <main+75>:    mov    eax,0x0
   0x400919 <main+80>:    leave  
   0x40091a <main+81>:    ret    
   0x40091b:    nop    DWORD PTR [rax+rax*1+0x0]
Guessed arguments:
arg[0]: 0x400a08 ("Finished Reading Flag into global variable 'flag_buf'. Exiting.")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe040 --> 0x7fffffffe138 --> 0x7fffffffe421 ("/root/CTF/picoCTF2018/Hideout/learngdb/run")
0008| 0x7fffffffe048 --> 0x100000000 
0016| 0x7fffffffe050 --> 0x400920 (<__libc_csu_init>:    push   r15)
0024| 0x7fffffffe058 --> 0x7ffff7e14bbb (<__libc_start_main+235>:    mov    edi,eax)
0032| 0x7fffffffe060 --> 0x0 
0040| 0x7fffffffe068 --> 0x7fffffffe138 --> 0x7fffffffe421 ("/root/CTF/picoCTF2018/Hideout/learngdb/run")
0048| 0x7fffffffe070 --> 0x100040000 
0056| 0x7fffffffe078 --> 0x4008c9 (<main>:    push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x000000000040090f in main ()
gdb-peda$ p flag_buf
'flag_buf' has unknown type; cast it to its declared type
gdb-peda$ x flag_buf
'flag_buf' has unknown type; cast it to its declared type
gdb-peda$ p flag_buf
'flag_buf' has unknown type; cast it to its declared type
gdb-peda$ p (char*) flag_buf
$1 = 0x6022a0 "picoCTF{gDb_iS_sUp3r_u53fuL_66d5464d}"

:::danger
https://b8807053.pixnet.net/blog/post/336154079-%5B%E8%BD%89%E8%B2%BC%5Dgdb-%E4%BB%8B%E7%B4%B9

以下是 gdb 的常見指令(其中 () 內為簡短指令):
help (h):顯示指令簡短說明。例:help breakpoint
file:開啟檔案。等同於 gdb filename
run (r):執行程式,或是從頭再執行程式。
kill:中止程式的執行。
backtrace (bt):堆疊追蹤。會顯示出上層所有的 frame 的簡略資訊。
print (p):印出變數內容。例:print i,印出變數 i 的內容。
list (l):印出程式碼。若在編譯時沒有加上 -g 參數,list 指令將無作用。
whatis:印出變數的型態。例: whatis i,印出變數 i 的型態。
breakpoint (b, bre, break):設定中斷點
           使用 info breakpoint (info b) 來查看已設定了哪些中斷點。
           在程式被中斷後,使用 info line 來查看正停在哪一行。
continue (c, cont):繼續執行。和 breakpoint 搭配使用。
frame:顯示正在執行的行數、副程式名稱、及其所傳送的參數等等 frame 資訊。
      frame 2:看到 #2,也就是上上一層的 frame 的資訊。
next (n):單步執行,但遇到 frame 時不會進入 frame 中單步執行。
step (s):單步執行。但遇到 frame 時則會進入 frame 中單步執行。
until:直接跑完一個 while 迴圈。
return:中止執行該 frame(視同該 frame 已執行完畢),
       並返回上個 frame 的呼叫點。功用類似 C 裡的 return 指令。
finish:執行完這個 frame。當進入一個過深的 frame 時,如:C 函式庫,
       可能必須下達多個 finish 才能回到原來的進入點。
up:直接回到上一層的 frame,並顯示其 stack 資訊,如進入點及傳入的參數等。
up 2:直接回到上三層的 frame,並顯示其 stack 資訊。
down:直接跳到下一層的 frame,並顯示其 stack 資訊。
     必須使用 up 回到上層的 frame 後,才能用 down 回到該層來。
display:在遇到中斷點時,自動顯示某變數的內容。
undisplay:取消 display,取消自動顯示某變數功能。
commands:在遇到中斷點時要自動執行的指令。
info:顯示一些特定的資訊。如: info break,顯示中斷點,
     info share,顯示共享函式庫資訊。
disable:暫時關閉某個 breakpoint 或 display 之功能。
enable:將被 disable 暫時關閉的功能再啟用。
clear/delete:刪除某個 breakpoint。
set:設定特定參數。如:set env,設定環境變數。也可以拿來修改變數的值。
unset:取消特定參數。如:unset env,刪除環境變數。
show:顯示特定參數。如:show environment,顯示環境變數。
attach PID:載入已執行中的程式以進行除錯。其中的 PID 可由 ps 指令取得。
detach PID:釋放已 attach 的程式。
shell:執行 Shell 指令。如:shell ls,呼叫 sh 以執行 ls 指令。
quit:離開 gdb。或是按下 <Ctrl><C> 也行。
<Enter>:直接執行上個指令

:::
:::success

root@hackercat:~/CTF/picoCTF2018/Hideout# gdb --help
This is the GNU debugger.  Usage:
    gdb [options] [executable-file [core-file or process-id]]
    gdb [options] --args executable-file [inferior-arguments ...]
Selection of debuggee and its files:
  --args             Arguments after executable-file are passed to inferior
  --core=COREFILE    Analyze the core dump COREFILE.
  --exec=EXECFILE    Use EXECFILE as the executable.
  --pid=PID          Attach to running process PID.
  --directory=DIR    Search for source files in DIR.
  --se=FILE          Use FILE as symbol file and executable file.
  --symbols=SYMFILE  Read symbols from SYMFILE.
  --readnow          Fully read symbol files on first access.
  --readnever        Do not read symbol files.
  --write            Set writing into executable and core files.
Initial commands and command files:
  --command=FILE, -x Execute GDB commands from FILE.
  --init-command=FILE, -ix
                     Like -x but execute commands before loading inferior.
  --eval-command=COMMAND, -ex
                     Execute a single GDB command.
                     May be used multiple times and in conjunction
                     with --command.
  --init-eval-command=COMMAND, -iex
                     Like -ex but before loading inferior.
  --nh               Do not read ~/.gdbinit.
  --nx               Do not read any .gdbinit files in any directory.
Output and user interface control:
  --fullname         Output information used by emacs-GDB interface.
  --interpreter=INTERP
                     Select a specific interpreter / user interface
  --tty=TTY          Use TTY for input/output by the program being debugged.
  -w                 Use the GUI interface.
  --nw               Do not use the GUI interface.
  --tui              Use a terminal user interface.
  --dbx              DBX compatibility mode.
  -q, --quiet, --silent
                     Do not print version number on startup.
Operating modes:
  --batch            Exit after processing options.
  --batch-silent     Like --batch, but suppress all gdb stdout output.
  --return-child-result
                     GDB exit code will be the child's exit code.
  --configuration    Print details about GDB configuration and then exit.
  --help             Print this message and then exit.
  --version          Print version information and then exit.
Remote debugging options:
  -b BAUDRATE        Set serial port baud rate used for remote debugging.
  -l TIMEOUT         Set timeout in seconds for remote debugging.
Other options:
  --cd=DIR           Change current directory to DIR.
  --data-directory=DIR, -D
                     Set GDB's data-directory to DIR.
At startup, GDB reads the following init files and executes their commands:
   * system-wide init file: /etc/gdb/gdbinit
For more information, type "help" from within GDB, or consult the
GDB manual (available as on-line info or a printed manual).
Report bugs to "<http://www.gnu.org/software/gdb/bugs/>".

:::

:::info
教學參考
https://ithelp.ithome.com.tw/articles/10188132
https://xtutlab.blogspot.com/2018/03/ida.html

https://www.youtube.com/watch?v=HtNKhBWBvts


題目: roulette

roulette是輪盤的意思
下載兩個檔案
roulette roulette.c
一個是程式碼
另一個chmod +x roulette之後可以執行

OK測了一下遊戲
總之就是個轉輪盤遊戲
下注 選個數字 然後轉輪盤

再去看看程式碼
(有夠長的 = =)

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
#define MAX_NUM_LEN 12
#define HOTSTREAK 3
#define MAX_WINS 16
#define ONE_BILLION 1000000000
#define ROULETTE_SIZE 36
#define ROULETTE_SPINS 128
#define ROULETTE_SLOWS 16
#define NUM_WIN_MSGS 10
#define NUM_LOSE_MSGS 5
long cash = 0;
long wins = 0;
int is_digit(char c) {
    return '0' <= c && c <= '9';
}
long get_long() {
    printf("> ");
    uint64_t l = 0;
    char c = 0;
    while(!is_digit(c))
      c = getchar();
    while(is_digit(c)) {
      if(l >= LONG_MAX) {
    l = LONG_MAX;
    break;
      }
      l *= 10;
      l += c - '0';
      c = getchar();
    }
    while(c != '\n')
      c = getchar();
    return l;
}
long get_rand() {
  long seed;
  FILE *f = fopen("/dev/urandom", "r");
  fread(&seed, sizeof(seed), 1, f);
  fclose(f);
  seed = seed % 5000;
  if (seed < 0) seed = seed * -1;
  srand(seed);
  return seed;
}
long get_bet() {
  while(1) {
    puts("How much will you wager?");
    printf("Current Balance: $%lu \t Current Wins: %lu\n", cash, wins); 
    long bet = get_long(); 
    if(bet <= cash) {
      return bet;
    } else {
      puts("You can't bet more than you have!");
    }
  }
}
long get_choice() {
  while (1) {
    printf("Choose a number (1-%d)\n", ROULETTE_SIZE);
    long choice = get_long();
    if (1 <= choice && choice <= ROULETTE_SIZE) {
      return choice;
    } else {
      puts("Please enter a valid choice.");
    }
  }
}
int print_flag() {
  char flag[48];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("Failed to open the flag file\n");
    return -1;
  }
  fgets(flag, sizeof(flag), file);
  printf("%s", flag);
  return 0;
}
const char *win_msgs[NUM_WIN_MSGS] = {
  "Wow.. Nice One!",
  "You chose correct!",
  "Winner!",
  "Wow, you won!",
  "Alright, now you're cooking!",
  "Darn.. Here you go",
  "Darn, you got it right.",
  "You.. win.. this round...",
  "Congrats!",
  "You're not cheating are you?",
};
const char *lose_msgs1[NUM_LOSE_MSGS] = {
  "WRONG",
  "Nice try..",
  "YOU LOSE",
  "Not this time..",
  "Better luck next time..."
};
const char *lose_msgs2[NUM_LOSE_MSGS] = {
  "Just give up!",
  "It's over for you.",
  "Stop wasting your time.",
  "You're never gonna win",
  "If you keep it up, maybe you'll get the flag in 100000000000 years"
};
void spin_roulette(long spin) {
  int n;
  puts("");
  printf("Roulette  :  ");
  int i, j;
  int s = 12500;
  for (i = 0; i < ROULETTE_SPINS; i++) {
    n = printf("%d", (i%ROULETTE_SIZE)+1);
    usleep(s);
    for (j = 0; j < n; j++) {
      printf("\b \b");
    }
  }
  for (i = ROULETTE_SPINS; i < (ROULETTE_SPINS+ROULETTE_SIZE); i++) {
    n = printf("%d", (i%ROULETTE_SIZE)+1);
    if (((i%ROULETTE_SIZE)+1) == spin) {
      for (j = 0; j < n; j++) {
    printf("\b \b");
      }
      break;
    }
    usleep(s);
    for (j = 0; j < n; j++) {
      printf("\b \b");
    }
  }
  for (int k = 0; k < ROULETTE_SIZE; k++) {
    n = printf("%d", ((i+k)%ROULETTE_SIZE)+1);
    s = 1.1*s;
    usleep(s);
    for (j = 0; j < n; j++) {
      printf("\b \b");
    }
  }
  printf("%ld", spin);
  usleep(s);
  puts("");
  puts("");
}
void play_roulette(long choice, long bet) {
  printf("Spinning the Roulette for a chance to win $%lu!\n", 2*bet);
  long spin = (rand() % ROULETTE_SIZE)+1;
  spin_roulette(spin);
  if (spin == choice) {
    cash += 2*bet;
    puts(win_msgs[rand()%NUM_WIN_MSGS]);
    wins += 1;
  }
  else {
    puts(lose_msgs1[rand()%NUM_LOSE_MSGS]);
    puts(lose_msgs2[rand()%NUM_LOSE_MSGS]);
  }
  puts("");
}
int main(int argc, char *argv[]) {
  setvbuf(stdout, NULL, _IONBF, 0);
  cash = get_rand();
  puts("Welcome to ONLINE ROULETTE!");
  printf("Here, have $%ld to start on the house! You'll lose it all anyways >:)\n", cash);
  puts("");
  long bet;
  long choice;
  while(cash > 0) {
      bet = get_bet();
      cash -= bet;
      choice = get_choice();
      puts("");
      play_roulette(choice, bet);
      if (wins >= MAX_WINS) {
    printf("Wow you won %lu times? Looks like its time for you cash you out.\n", wins);
    printf("Congrats you made $%lu. See you next time!\n", cash);
    exit(-1);
      }
      if(cash > ONE_BILLION) {
    printf("*** Current Balance: $%lu ***\n", cash);
    if (wins >= HOTSTREAK) {
      puts("Wow, I can't believe you did it.. You deserve this flag!");
      print_flag();
      exit(0);
    }
    else {
      puts("Wait a second... You're not even on a hotstreak! Get out of here cheater!");
      exit(-1);
    }
    }
  }
  puts("Haha, lost all the money I gave you already? See ya later!");
  return 0;
}

來先看看有那些function
都在做些甚麼
get_long() 不太理解
get_rand() 取得一個隨機數
是利用 /dev/urandom
get_bet() 是壓注的金額
et_choice() 選擇壓的數字
print_flag() 印出flag
spin_roulette() 開始轉輪盤

摁…
既然code在這邊
先做著白癡的事情試看看好了
直接改一下勝利的金額

define ONE_BILLION 10

compile之後執行
結果是這樣

後來再看看code
他除了判斷金額以外
會判斷你贏的次數有沒有超過幾次

define HOTSTREAK 0

改成這樣 贏的次數大於等於0就可以了
結果如下

後來想想 似乎哪裡怪怪的
沒有印出flag

直接把print flag的function更換位置也不行

直接開一個檔案flag.txt
也只是把自己的內容印出來

發現我好像在耍智障XDDD
不能改程式才對

是找輸入的漏洞

不過其實這邊我們已經發現幾個關鍵
首先要勝利的話 要金額大於 ONE_BILLION 也就是 1000000000
而勝利次數必須要大於 HOTSTREAK 也就是 3

第一個可以注意到的點是產生隨機亂數的 function
我們發現一件事情
我們一開始的金額是利用get_rand()所產生
而get_rand()會隨機返回一個小魚5000的正整數

再來 我們看到隨機產生亂數裡面用到seed
我自己是當初在學ML 有碰到seed 才有一點概念

long get_rand() {
  long seed;
  FILE *f = fopen("/dev/urandom", "r");
  fread(&seed, sizeof(seed), 1, f);
  fclose(f);
  seed = seed % 5000;
  if (seed < 0) seed = seed * -1;
  srand(seed);
  return seed;
}
...
int main(int argc, char *argv[]) {
  setvbuf(stdout, NULL, _IONBF, 0);
  cash = get_rand();
  puts("Welcome to ONLINE ROULETTE!");
  printf("Here, have $%ld to start on the house! You'll lose it all anyways >:)\n", cash);
  puts("");

:::success
這邊建議先稍微了解隨機數的產生
看一下c/c++的rand()跟srand()

先寫一個小程式

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   int a,b;
   for(a=1;a<5;a++)
   {
      b= rand();
      printf("%d\n",b);
   }
   return 0;
}

上面的程式會印出五個亂數
不過會發現其實這五個數字 怎麼每次執行都會返回一樣結果
其實原因就是在於產生隨機數的初始值 初始值都一樣
導致每次執行程式 都會產生一樣的結果

其實也就是所謂的亂數種子 seed
為了讓我們的亂數不會每次都一樣
我們需要一個函數 srand()

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   int a,b;
   srand(6);
   for(a=1;a<5;a++)
   {
      b= rand();
      printf("%d\n",b);
   }
   return 0;
}

當我們去更改srand(6);裡面的值的時候
會發現執行程式後的結果不一樣了
可是每次執行 其實還是會一樣
所以一般產生隨機數會讓srand使用時間相關參數

讓srand裡面塞一個隨時間而變得值
這樣亂數種子seed就不會是一個定值

一個簡單方法就是利用time(NULL)

#include<time.h> 
time(NULL)

http://it-easy.tw/crand/
:::

:::info
接著了解一下get_rand這個function

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
int main()
{
    long seed;
    FILE *f = fopen("/dev/urandom", "r");
    fread(&seed, sizeof(seed), 1, f);
    fclose(f);
    seed = seed % 5000;
    if (seed < 0) seed = seed * -1;
    srand(seed);
    printf("%d\n",seed);
}

如預料的 會返回一個小於5000的正整數
如果我們知道seed的話 我們就可以知道rand產生的數

所以先做個簡單測試
把剛剛亂數產生的程式 srand裡面的數字改成我們一開始取得的cash
發現我們可以成功預測到第一個數字 …
但是只有第一個會正確?!

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   int a,b,c;
   srand(2572);
   for(a=1;a<5;a++)
   {
      b= rand();
      c = (b%36)+1;
      printf("%d\n",c);
   }
   return 0;
}

後來發現 我每次印出4個隨機數
假設編號1,2,3,4好了
第1個都會中 贏的話下次會是第3個 輸的話下次會是第4個
似乎有種規律 再看看code
發現問題點在

void play_roulette(long choice, long bet) {
  printf("Spinning the Roulette for a chance to win $%lu!\n", 2*bet);
  long spin = (rand() % ROULETTE_SIZE)+1;
  spin_roulette(spin);
  if (spin == choice) {
    cash += 2*bet;
    puts(win_msgs[rand()%NUM_WIN_MSGS]);
    wins += 1;
  }
  else {
    puts(lose_msgs1[rand()%NUM_LOSE_MSGS]);
    puts(lose_msgs2[rand()%NUM_LOSE_MSGS]);
  }
  puts("");
}

注意到沒有 win的話會再調用一次rand
而lose的話會調用兩次

這樣就好搞定了
把程式改寫成

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   int a;
   long b,c;
   srand(158);
   for(a=0;a<10;a++)
   {
      b= rand();
      c = (b%36)+1;
      if (a % 2 ==0)
      {
         printf("%d\n",c);
      }
   }
   return 0;
}

:::

OK好 測試過後確定可以一值贏下去
但是這樣贏下去要贏到勝利金額 手大概會按斷掉

第二個問題點呢
就在於get_long()

long get_long() {
    printf("> ");
    uint64_t l = 0;
    char c = 0;
    while(!is_digit(c))
      c = getchar();
    while(is_digit(c)) {
      if(l >= LONG_MAX) {
    l = LONG_MAX;
    break;
      }
      l *= 10;
      l += c - '0';
      c = getchar();
    }
    while(c != '\n')
      c = getchar();
    return l;
}

這個函數的返回值l為uint64_t
但是在get_long的時候宣告是long
long的範圍是-2,147,483,648 至 2,147,483,647
uint64_t的範圍是 0 至 18446744073709551615
https://anal02.pixnet.net/blog/post/118652454-uint8_t%2C-uint16_t%2C-uint32_t%2C-uint64_t
https://docs.microsoft.com/zh-tw/cpp/cpp/data-type-ranges?view=vs-2019

所以存在一個可能倒置溢出成負數的風險
其中可以看出

      if(l >= LONG_MAX) {
    l = LONG_MAX;

這一個段目的在於不讓數字超過LONG_MAX

http://tw.gitbook.net/c_standard_library/limits_h.html

可是下面做了一個動作讓l*10
也就是他的順序錯誤了 導致問題發生
所以我們只要輸入超過2,147,483,647的數字就可以了
譬如
2147483648

不過要注意 要在最後一次勝利輸入這個數字才行
不然會被說是作弊 還記得嗎XD

見鬼了XD 發現我錯了 不是第三次勝利 要在第四次
而且要輸入 2247483647 ?
後來發現也不對阿R 幹
怎麼每次第四次贏 錢都沒有
後來終於才發現一個問題點…
首先押注的時候
你的cash已經會被扣掉bet
所以溢出的時候 你的cash會加上很多錢

  while(cash > 0) {
      bet = get_bet();
      cash -= bet;

可是當你贏的時候
你的cash會被加上兩倍的bet
但是溢出的時候 你的bet是一個很大的負數
所以會變成沒錢

  if (spin == choice) {
    cash += 2*bet;

所以注意最後一次應該要輸才行
畢竟我們前面已經贏過三次所以也算是滿足條件

成功取得flag
picoCTF{1_h0p3_y0u_f0uNd_b0tH_bUg5_67c08f03}


題目: store

題目指示要連線到2018shell.picoctf.com 43581

看起來這是一個購買flag的遊戲

然後你有1100元

要購買一個Real Flag需要100000元

購買一個假的Flag需要1000元

好~ 那這邊看不出甚麼沒關係

先看一下題目附的code

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int con;
    con = 0;
    int account_balance = 1100;
    while(con == 0){
        printf("Welcome to the Store App V1.0\n");
        printf("World's Most Secure Purchasing App\n");
        printf("\n[1] Check Account Balance\n");
        printf("\n[2] Buy Stuff\n");
        printf("\n[3] Exit\n");
        int menu;
        printf("\n Enter a menu selection\n");
        fflush(stdin);
        scanf("%d", &menu);
        if(menu == 1){
            printf("\n\n\n Balance: %d \n\n\n", account_balance);
        }
        else if(menu == 2){
            printf("Current Auctions\n");
            printf("[1] I Can't Believe its not a Flag!\n");
            printf("[2] Real Flag\n");
            int auction_choice;
            fflush(stdin);
            scanf("%d", &auction_choice);
            if(auction_choice == 1){
                printf("Imitation Flags cost 1000 each, how many would you like?\n");
                int number_flags = 0;
                fflush(stdin);
                scanf("%d", &number_flags);
                if(number_flags > 0){
                    int total_cost = 0;
                    total_cost = 1000*number_flags;
                    printf("\nYour total cost is: %d\n", total_cost);
                    if(total_cost <= account_balance){
                        account_balance = account_balance - total_cost;
                        printf("\nYour new balance: %d\n\n", account_balance);
                    }
                    else{
                        printf("Not enough funds\n");
                    }

                }


            }
            else if(auction_choice == 2){
                printf("A genuine Flag costs 100000 dollars, and we only have 1 in stock\n");
                printf("Enter 1 to purchase");
                int bid = 0;
                fflush(stdin);
                scanf("%d", &bid);
                if(bid == 1){
                    if(account_balance > 100000){
                        printf("YOUR FLAG IS:\n");
                        }
                    else{
                        printf("\nNot enough funds for transaction\n\n\n");
                    }}
            }
        }
        else{
            con = 1;
        }
    }
    return 0;
}

看完整個code之後, 看起來沒甚麼問題

有個特別的地方是

  total_cost = 1000*number_flags;
  printf("\nYour total cost is: %d\n", total_cost);
  if(total_cost <= account_balance){
      account_balance = account_balance - total_cost;
      printf("\nYour new balance: %d\n\n", account_balance);

買假的FLAG時的花費是total_cost = 1000*number_flags;

所以買完之後我們帳戶的錢是account_balance = account_balance – total_cost;

C語言中INT的上限值是2147483647

所以我們如果輸入一個number_flags使得total_cost超過INT上限會如何?


參考
https://docs.microsoft.com/zh-tw/cpp/c-language/cpp-integer-limits?view=vs-2017

可以發現overflow the integer之後
turn the total cost to a negative number

這樣就有錢買Real flag了

發佈留言